mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
Merge pull request #95 from AmanTahiliani/fix/live-stint-history-merge
fix(live): merge stint deltas instead of replacing stint history
This commit is contained in:
@@ -472,6 +472,62 @@ func TestProcessTopicTimingAppData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The feed sends stints as sparse deltas keyed by stint index. Replacing the
|
||||
// slice on each delta collapsed pit history to one entry and pinned tyre age
|
||||
// near zero — observed live at lap 49 of a 70-lap race, where every driver
|
||||
// reported a single stint of age 0 despite having pitted.
|
||||
func TestProcessTopicTimingAppDataMergesSparseStintDeltas(t *testing.T) {
|
||||
state := live.NewState()
|
||||
state.ProcessTopic("TimingAppData", json.RawMessage(`{
|
||||
"Lines": {"4": {"Stints": {"0": {"Compound": "MEDIUM", "New": "true", "TotalLaps": 0}}}}
|
||||
}`))
|
||||
// Stint 0 runs to 18 laps, then the driver pits onto a new hard.
|
||||
state.ProcessTopic("TimingAppData", json.RawMessage(`{
|
||||
"Lines": {"4": {"Stints": {"0": {"TotalLaps": 18}}}}
|
||||
}`))
|
||||
state.ProcessTopic("TimingAppData", json.RawMessage(`{
|
||||
"Lines": {"4": {"Stints": {"1": {"Compound": "HARD", "New": "true", "TotalLaps": 0}}}}
|
||||
}`))
|
||||
state.ProcessTopic("TimingAppData", json.RawMessage(`{
|
||||
"Lines": {"4": {"Stints": {"1": {"TotalLaps": 12}}}}
|
||||
}`))
|
||||
|
||||
snap := state.Snapshot()
|
||||
stints := snap.Stints["4"]
|
||||
if len(stints) != 2 {
|
||||
t.Fatalf("expected 2 stints after a pit stop, got %d: %+v", len(stints), stints)
|
||||
}
|
||||
if stints[0].Compound != "MEDIUM" || stints[0].Laps != 18 {
|
||||
t.Errorf("first stint lost across deltas: %+v", stints[0])
|
||||
}
|
||||
if stints[1].Compound != "HARD" || stints[1].Laps != 12 {
|
||||
t.Errorf("second stint = %+v", stints[1])
|
||||
}
|
||||
if tyre := snap.Tyres["4"]; tyre.Compound != "HARD" || tyre.Age != 12 {
|
||||
t.Errorf("current tyre should track the latest stint, got %+v", tyre)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessTopicTimingAppDataIgnoresNonNumericStintKeys(t *testing.T) {
|
||||
state := live.NewState()
|
||||
state.ProcessTopic("TimingAppData", json.RawMessage(`{
|
||||
"Lines": {"4": {"Stints": {"0": {"Compound": "SOFT", "New": "true", "TotalLaps": 9}}}}
|
||||
}`))
|
||||
// "_kf" is a feed key-frame marker, not a stint index. Parsing it as 0
|
||||
// would overwrite the real first stint.
|
||||
state.ProcessTopic("TimingAppData", json.RawMessage(`{
|
||||
"Lines": {"4": {"Stints": {"_kf": {"Compound": "HARD", "TotalLaps": 99}}}}
|
||||
}`))
|
||||
|
||||
stints := state.Snapshot().Stints["4"]
|
||||
if len(stints) != 1 {
|
||||
t.Fatalf("expected 1 stint, got %d: %+v", len(stints), stints)
|
||||
}
|
||||
if stints[0].Compound != "SOFT" || stints[0].Laps != 9 {
|
||||
t.Errorf("key-frame marker corrupted stint 0: %+v", stints[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessTopicTimingStats(t *testing.T) {
|
||||
state := live.NewState()
|
||||
state.Drivers["55"] = live.LiveDriverData{RacingNumber: "55"}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -404,22 +405,42 @@ func (s *State) ProcessTopic(topic string, data json.RawMessage) bool {
|
||||
Stints json.RawMessage `json:"Stints"`
|
||||
}
|
||||
if json.Unmarshal(lineRaw, &line) == nil && line.Stints != nil {
|
||||
var driverStints []LiveStintData
|
||||
// The feed sends stints as sparse deltas keyed by stint index:
|
||||
// a mid-stint update is just {"1": {"TotalLaps": 14}}. Merge
|
||||
// each entry into the stint it addresses. Replacing the slice
|
||||
// wholesale discarded every earlier stint, so pit history
|
||||
// collapsed to one entry and tyre age stuck near zero for the
|
||||
// whole race.
|
||||
driverStints := append([]LiveStintData(nil), s.Stints[num]...)
|
||||
changed := false
|
||||
for _, sRaw := range indexedRawValues(line.Stints) {
|
||||
var st struct {
|
||||
Compound string `json:"Compound"`
|
||||
New string `json:"New"`
|
||||
TotalLaps int `json:"TotalLaps"`
|
||||
Compound *string `json:"Compound"`
|
||||
New *string `json:"New"`
|
||||
TotalLaps *int `json:"TotalLaps"`
|
||||
}
|
||||
if json.Unmarshal(sRaw.Raw, &st) == nil && st.Compound != "" {
|
||||
driverStints = append(driverStints, LiveStintData{
|
||||
Compound: st.Compound,
|
||||
New: st.New == "true" || st.New == "True",
|
||||
Laps: st.TotalLaps,
|
||||
})
|
||||
if json.Unmarshal(sRaw.Raw, &st) != nil {
|
||||
continue
|
||||
}
|
||||
if st.Compound == nil && st.New == nil && st.TotalLaps == nil {
|
||||
continue
|
||||
}
|
||||
if len(driverStints) > 0 {
|
||||
for len(driverStints) <= sRaw.Index {
|
||||
driverStints = append(driverStints, LiveStintData{})
|
||||
}
|
||||
entry := &driverStints[sRaw.Index]
|
||||
if st.Compound != nil && *st.Compound != "" {
|
||||
entry.Compound = *st.Compound
|
||||
}
|
||||
if st.New != nil {
|
||||
entry.New = *st.New == "true" || *st.New == "True"
|
||||
}
|
||||
if st.TotalLaps != nil {
|
||||
entry.Laps = *st.TotalLaps
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
if changed {
|
||||
s.Stints[num] = driverStints
|
||||
lastStint := driverStints[len(driverStints)-1]
|
||||
t := s.Tyres[num]
|
||||
@@ -874,8 +895,13 @@ func indexedRawValues(raw json.RawMessage) []indexedRaw {
|
||||
if err := json.Unmarshal(raw, &obj); err == nil {
|
||||
values := make([]indexedRaw, 0, len(obj))
|
||||
for k, v := range obj {
|
||||
i := 0
|
||||
fmt.Sscanf(k, "%d", &i)
|
||||
// Keys are array indices in the feed's delta form. Non-numeric keys
|
||||
// are feed metadata — "_kf" (key frame) is the common one — and must
|
||||
// not be folded in as index 0, which would clobber the first entry.
|
||||
i, err := strconv.Atoi(k)
|
||||
if err != nil || i < 0 {
|
||||
continue
|
||||
}
|
||||
values = append(values, indexedRaw{Index: i, Raw: v})
|
||||
}
|
||||
sort.Slice(values, func(i, j int) bool {
|
||||
|
||||
Reference in New Issue
Block a user