mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
fix: make weekend context transitions truthful
This commit is contained in:
@@ -136,21 +136,6 @@ func (s *Service) ResolveWeekendContext(evidence LiveEvidence) (WeekendContext,
|
||||
champMeetings := championshipMeetings(meetings, byMeeting)
|
||||
out.TotalChampionshipRounds = len(champMeetings)
|
||||
|
||||
var previous, next, defaultAnalysis *contextCandidate
|
||||
for i := range candidates {
|
||||
c := &candidates[i]
|
||||
completionEligible := c.start.IsZero() || !c.start.After(now) || c.archived
|
||||
if c.complete && completionEligible && (previous == nil || candidateTime(*c).After(candidateTime(*previous))) {
|
||||
previous = c
|
||||
}
|
||||
if c.complete && hasMeaningfulAnalysis(c.counts) && (c.start.IsZero() || !c.start.After(now)) && (defaultAnalysis == nil || candidateTime(*c).After(candidateTime(*defaultAnalysis))) {
|
||||
defaultAnalysis = c
|
||||
}
|
||||
if !c.start.IsZero() && !c.start.Before(now) && (next == nil || c.start.Before(next.start)) {
|
||||
next = c
|
||||
}
|
||||
}
|
||||
|
||||
var active *contextCandidate
|
||||
if evidence.Active {
|
||||
for i := range candidates {
|
||||
@@ -162,14 +147,21 @@ func (s *Service) ResolveWeekendContext(evidence LiveEvidence) (WeekendContext,
|
||||
if active == nil {
|
||||
active = syntheticLiveCandidate(evidence, now)
|
||||
}
|
||||
if next != nil && active.session.SessionKey != 0 && next.session.SessionKey == active.session.SessionKey {
|
||||
next = nil
|
||||
for i := range candidates {
|
||||
c := &candidates[i]
|
||||
if c.session.SessionKey != active.session.SessionKey && c.start.After(now) && (next == nil || c.start.Before(next.start)) {
|
||||
next = c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var previous, next, defaultAnalysis *contextCandidate
|
||||
for i := range candidates {
|
||||
c := &candidates[i]
|
||||
isActive := active != nil && active.session.SessionKey != 0 && c.session.SessionKey == active.session.SessionKey
|
||||
completionEligible := c.start.IsZero() || !c.start.After(now) || c.archived
|
||||
if !isActive && c.complete && completionEligible && (previous == nil || candidateTime(*c).After(candidateTime(*previous))) {
|
||||
previous = c
|
||||
}
|
||||
if !isActive && c.complete && hasMeaningfulAnalysis(c.counts) && (c.start.IsZero() || !c.start.After(now)) && (defaultAnalysis == nil || candidateTime(*c).After(candidateTime(*defaultAnalysis))) {
|
||||
defaultAnalysis = c
|
||||
}
|
||||
if !isActive && !c.start.IsZero() && !c.start.Before(now) && (next == nil || c.start.Before(next.start)) {
|
||||
next = c
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +182,7 @@ func (s *Service) ResolveWeekendContext(evidence LiveEvidence) (WeekendContext,
|
||||
out.TemporalState = TemporalSessionLive
|
||||
} else {
|
||||
out.FocusMeeting = chooseFocusMeeting(meetings, previous, next)
|
||||
out.TemporalState = classifyTemporalState(now, previous, next, candidates, champMeetings)
|
||||
out.TemporalState = classifyTemporalState(now, previous, next, candidates, champMeetings, championshipScheduleUnknown(champMeetings, byMeeting))
|
||||
}
|
||||
if out.FocusMeeting != nil {
|
||||
out.ChampionshipRound = championshipRound(champMeetings, int(out.FocusMeeting.MeetingKey))
|
||||
@@ -347,13 +339,18 @@ func chooseFocusMeeting(meetings []store.Meeting, previous, next *contextCandida
|
||||
return nil
|
||||
}
|
||||
|
||||
func classifyTemporalState(now time.Time, previous, next *contextCandidate, candidates []contextCandidate, championship []store.Meeting) TemporalState {
|
||||
func classifyTemporalState(now time.Time, previous, next *contextCandidate, candidates []contextCandidate, championship []store.Meeting, scheduleUnknown bool) TemporalState {
|
||||
var latestStarted *contextCandidate
|
||||
for i := range candidates {
|
||||
if !candidates[i].start.IsZero() && !candidates[i].start.After(now) && (latestStarted == nil || candidates[i].start.After(latestStarted.start)) {
|
||||
latestStarted = &candidates[i]
|
||||
}
|
||||
}
|
||||
// Once a new meeting enters its preparation window, an ingest gap from an
|
||||
// older meeting must not keep the product stuck in settling.
|
||||
if next != nil && next.start.Sub(now) <= preSessionWindow && (latestStarted == nil || latestStarted.meeting.MeetingKey != next.meeting.MeetingKey) {
|
||||
return TemporalPreSession
|
||||
}
|
||||
if latestStarted != nil && !latestStarted.complete && !latestStarted.end.IsZero() {
|
||||
if now.Before(latestStarted.end) {
|
||||
return TemporalPreSession
|
||||
@@ -369,12 +366,25 @@ func classifyTemporalState(now time.Time, previous, next *contextCandidate, cand
|
||||
if next != nil && next.start.Sub(now) <= preSessionWindow {
|
||||
return TemporalPreSession
|
||||
}
|
||||
if next == nil && len(championship) > 0 {
|
||||
if next == nil && len(championship) > 0 && !scheduleUnknown {
|
||||
return TemporalSeasonComplete
|
||||
}
|
||||
return TemporalBetweenWeekends
|
||||
}
|
||||
|
||||
func championshipScheduleUnknown(meetings []store.Meeting, sessions map[int][]store.Session) bool {
|
||||
for _, meeting := range meetings {
|
||||
for _, session := range sessions[meeting.MeetingKey] {
|
||||
if !session.IsCancelled && isChampionshipRace(session) {
|
||||
if _, ok := parseContextTime(session.DateStart); !ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func meetingFinalSession(previous contextCandidate, candidates []contextCandidate) bool {
|
||||
latest := previous.start
|
||||
for _, c := range candidates {
|
||||
|
||||
@@ -256,3 +256,66 @@ func TestResolveWeekendContextPartialFutureSchedule(t *testing.T) {
|
||||
t.Fatalf("partial display range = %+v", got.FocusMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWeekendContextActiveSessionIsNotCompletedOrDefault(t *testing.T) {
|
||||
now, _ := time.Parse(time.RFC3339, "2026-07-05T15:00:00Z")
|
||||
svc := contextService(t, now)
|
||||
addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false)
|
||||
addContextSession(t, svc, 10, 1, "Qualifying", "2026-07-04T14:00:00Z", "2026-07-04T15:00:00Z", false)
|
||||
completeContextSession(t, svc, 10, 1)
|
||||
addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false)
|
||||
completeContextSession(t, svc, 11, 1)
|
||||
|
||||
got, err := svc.ResolveWeekendContext(LiveEvidence{Active: true, MeetingName: "British Grand Prix", CircuitName: "British Grand Prix", SessionName: "Race", SessionType: "Race", ObservedAt: now})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.ActiveSession == nil || got.ActiveSession.Session.SessionKey != 11 {
|
||||
t.Fatalf("active = %+v", got.ActiveSession)
|
||||
}
|
||||
if got.PreviousCompletedSession == nil || got.PreviousCompletedSession.Session.SessionKey != 10 {
|
||||
t.Fatalf("previous = %+v, want earlier completed session", got.PreviousCompletedSession)
|
||||
}
|
||||
if got.DefaultAnalysisSession == nil || got.DefaultAnalysisSession.Session.SessionKey != 10 {
|
||||
t.Fatalf("default = %+v, want earlier completed session", got.DefaultAnalysisSession)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWeekendContextOldIncompleteSessionDoesNotSuppressNextWeekend(t *testing.T) {
|
||||
now, _ := time.Parse(time.RFC3339, "2026-07-16T12:00:00Z")
|
||||
svc := contextService(t, now)
|
||||
addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false)
|
||||
addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false)
|
||||
addContextMeeting(t, svc, 2, "Belgian Grand Prix", "2026-07-17T09:00:00Z", "2026-07-19T16:00:00Z", false)
|
||||
addContextSession(t, svc, 21, 2, "Practice 1", "2026-07-17T09:00:00Z", "2026-07-17T10:00:00Z", false)
|
||||
addContextSession(t, svc, 22, 2, "Race", "2026-07-19T14:00:00Z", "2026-07-19T16:00:00Z", false)
|
||||
|
||||
got, err := svc.ResolveWeekendContext(LiveEvidence{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.TemporalState != TemporalPreSession {
|
||||
t.Fatalf("state = %s, want %s; context=%+v", got.TemporalState, TemporalPreSession, got)
|
||||
}
|
||||
if got.FocusMeeting == nil || got.FocusMeeting.MeetingKey != 2 {
|
||||
t.Fatalf("focus = %+v, want Belgian weekend", got.FocusMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWeekendContextMissingScheduleDoesNotClaimSeasonComplete(t *testing.T) {
|
||||
now, _ := time.Parse(time.RFC3339, "2026-07-01T12:00:00Z")
|
||||
svc := contextService(t, now)
|
||||
addContextMeeting(t, svc, 1, "British Grand Prix", "", "", false)
|
||||
addContextSession(t, svc, 11, 1, "Race", "", "", false)
|
||||
|
||||
got, err := svc.ResolveWeekendContext(LiveEvidence{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.TemporalState != TemporalBetweenWeekends {
|
||||
t.Fatalf("state = %s, want limited %s context", got.TemporalState, TemporalBetweenWeekends)
|
||||
}
|
||||
if got.TotalChampionshipRounds != 1 {
|
||||
t.Fatalf("total rounds = %d, want scheduled round retained", got.TotalChampionshipRounds)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user