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)
|
champMeetings := championshipMeetings(meetings, byMeeting)
|
||||||
out.TotalChampionshipRounds = len(champMeetings)
|
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
|
var active *contextCandidate
|
||||||
if evidence.Active {
|
if evidence.Active {
|
||||||
for i := range candidates {
|
for i := range candidates {
|
||||||
@@ -162,14 +147,21 @@ func (s *Service) ResolveWeekendContext(evidence LiveEvidence) (WeekendContext,
|
|||||||
if active == nil {
|
if active == nil {
|
||||||
active = syntheticLiveCandidate(evidence, now)
|
active = syntheticLiveCandidate(evidence, now)
|
||||||
}
|
}
|
||||||
if next != nil && active.session.SessionKey != 0 && next.session.SessionKey == active.session.SessionKey {
|
}
|
||||||
next = nil
|
|
||||||
for i := range candidates {
|
var previous, next, defaultAnalysis *contextCandidate
|
||||||
c := &candidates[i]
|
for i := range candidates {
|
||||||
if c.session.SessionKey != active.session.SessionKey && c.start.After(now) && (next == nil || c.start.Before(next.start)) {
|
c := &candidates[i]
|
||||||
next = c
|
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
|
out.TemporalState = TemporalSessionLive
|
||||||
} else {
|
} else {
|
||||||
out.FocusMeeting = chooseFocusMeeting(meetings, previous, next)
|
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 {
|
if out.FocusMeeting != nil {
|
||||||
out.ChampionshipRound = championshipRound(champMeetings, int(out.FocusMeeting.MeetingKey))
|
out.ChampionshipRound = championshipRound(champMeetings, int(out.FocusMeeting.MeetingKey))
|
||||||
@@ -347,13 +339,18 @@ func chooseFocusMeeting(meetings []store.Meeting, previous, next *contextCandida
|
|||||||
return nil
|
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
|
var latestStarted *contextCandidate
|
||||||
for i := range candidates {
|
for i := range candidates {
|
||||||
if !candidates[i].start.IsZero() && !candidates[i].start.After(now) && (latestStarted == nil || candidates[i].start.After(latestStarted.start)) {
|
if !candidates[i].start.IsZero() && !candidates[i].start.After(now) && (latestStarted == nil || candidates[i].start.After(latestStarted.start)) {
|
||||||
latestStarted = &candidates[i]
|
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 latestStarted != nil && !latestStarted.complete && !latestStarted.end.IsZero() {
|
||||||
if now.Before(latestStarted.end) {
|
if now.Before(latestStarted.end) {
|
||||||
return TemporalPreSession
|
return TemporalPreSession
|
||||||
@@ -369,12 +366,25 @@ func classifyTemporalState(now time.Time, previous, next *contextCandidate, cand
|
|||||||
if next != nil && next.start.Sub(now) <= preSessionWindow {
|
if next != nil && next.start.Sub(now) <= preSessionWindow {
|
||||||
return TemporalPreSession
|
return TemporalPreSession
|
||||||
}
|
}
|
||||||
if next == nil && len(championship) > 0 {
|
if next == nil && len(championship) > 0 && !scheduleUnknown {
|
||||||
return TemporalSeasonComplete
|
return TemporalSeasonComplete
|
||||||
}
|
}
|
||||||
return TemporalBetweenWeekends
|
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 {
|
func meetingFinalSession(previous contextCandidate, candidates []contextCandidate) bool {
|
||||||
latest := previous.start
|
latest := previous.start
|
||||||
for _, c := range candidates {
|
for _, c := range candidates {
|
||||||
|
|||||||
@@ -256,3 +256,66 @@ func TestResolveWeekendContextPartialFutureSchedule(t *testing.T) {
|
|||||||
t.Fatalf("partial display range = %+v", got.FocusMeeting)
|
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