cricinfo-cli-go 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/internal/cli/matches.go +126 -2
- package/internal/cli/matches_test.go +82 -0
- package/internal/cli/search.go +156 -0
- package/internal/cricinfo/analysis.go +393 -93
- package/internal/cricinfo/analysis_phase15_test.go +38 -0
- package/internal/cricinfo/client.go +23 -2
- package/internal/cricinfo/coverage_ledger_test.go +2 -22
- package/internal/cricinfo/entity_index.go +27 -0
- package/internal/cricinfo/historical_hydration.go +82 -42
- package/internal/cricinfo/matches.go +1641 -88
- package/internal/cricinfo/matches_phase7_test.go +11 -4
- package/internal/cricinfo/normalize_entities.go +83 -35
- package/internal/cricinfo/players.go +236 -2
- package/internal/cricinfo/render_contract.go +191 -49
- package/internal/cricinfo/renderer.go +613 -19
- package/internal/cricinfo/resolver.go +134 -13
- package/internal/cricinfo/teams.go +109 -6
- package/internal/cricinfo/testdata/coverage/cricinfo-field-path-catalog.txt +2536 -0
- package/internal/cricinfo/testdata/coverage/cricinfo-working-templates.tsv +56 -0
- package/package.json +1 -1
|
@@ -101,11 +101,18 @@ func TestMatchServicePhase7ScorecardAndSituation(t *testing.T) {
|
|
|
101
101
|
if situationResult.Kind != EntityMatchSituation {
|
|
102
102
|
t.Fatalf("expected situation kind %q, got %q", EntityMatchSituation, situationResult.Kind)
|
|
103
103
|
}
|
|
104
|
-
if situationResult.Status
|
|
105
|
-
t.Fatalf("expected sparse situation to
|
|
104
|
+
if situationResult.Status == ResultStatusEmpty {
|
|
105
|
+
t.Fatalf("expected sparse situation to synthesize live fallback data")
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
situation, ok := situationResult.Data.(*MatchSituation)
|
|
108
|
+
if !ok {
|
|
109
|
+
t.Fatalf("expected situation data type *MatchSituation, got %T", situationResult.Data)
|
|
110
|
+
}
|
|
111
|
+
if situation.Live == nil {
|
|
112
|
+
t.Fatalf("expected synthesized live situation view")
|
|
113
|
+
}
|
|
114
|
+
if len(situation.Live.RecentBalls) == 0 {
|
|
115
|
+
t.Fatalf("expected recent balls in synthesized live situation view")
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
|
|
@@ -223,6 +223,7 @@ func NormalizeTeamRosterEntries(data []byte, team Team, scope TeamScope, matchID
|
|
|
223
223
|
PlayerRef: athleteRef,
|
|
224
224
|
DisplayName: displayName,
|
|
225
225
|
TeamID: team.ID,
|
|
226
|
+
TeamName: nonEmpty(team.ShortName, team.Name, team.ID),
|
|
226
227
|
TeamRef: team.Ref,
|
|
227
228
|
MatchID: strings.TrimSpace(matchID),
|
|
228
229
|
Scope: scope,
|
|
@@ -260,6 +261,7 @@ func NormalizeTeamAthletePage(data []byte, team Team) ([]TeamRosterEntry, error)
|
|
|
260
261
|
PlayerID: refIDs(playerRef)["athleteId"],
|
|
261
262
|
PlayerRef: playerRef,
|
|
262
263
|
TeamID: team.ID,
|
|
264
|
+
TeamName: nonEmpty(team.ShortName, team.Name, team.ID),
|
|
263
265
|
TeamRef: team.Ref,
|
|
264
266
|
Scope: TeamScopeGlobal,
|
|
265
267
|
})
|
|
@@ -670,12 +672,18 @@ func NormalizeDeliveryEvent(data []byte) (*DeliveryEvent, error) {
|
|
|
670
672
|
dismissal := mapField(payload, "dismissal")
|
|
671
673
|
batsman := mapField(payload, "batsman")
|
|
672
674
|
bowler := mapField(payload, "bowler")
|
|
675
|
+
otherBatsman := mapField(payload, "otherBatsman")
|
|
676
|
+
otherBowler := mapField(payload, "otherBowler")
|
|
673
677
|
fielder := mapField(dismissal, "fielder")
|
|
674
678
|
batsmanRef := nonEmpty(nestedRef(payload, "batsman", "athlete"), refFromField(payload, "batsman"))
|
|
675
679
|
bowlerRef := nonEmpty(nestedRef(payload, "bowler", "athlete"), refFromField(payload, "bowler"))
|
|
680
|
+
otherBatsmanRef := nonEmpty(nestedRef(payload, "otherBatsman", "athlete"), refFromField(payload, "otherBatsman"))
|
|
681
|
+
otherBowlerRef := nonEmpty(nestedRef(payload, "otherBowler", "athlete"), refFromField(payload, "otherBowler"))
|
|
676
682
|
fielderRef := nonEmpty(nestedRef(payload, "dismissal", "fielder", "athlete"), nestedRef(payload, "dismissal", "fielder"))
|
|
677
683
|
batsmanID := nonEmpty(stringField(batsman, "playerId"), stringField(batsman, "id"), refIDs(batsmanRef)["athleteId"])
|
|
678
684
|
bowlerID := nonEmpty(stringField(bowler, "playerId"), stringField(bowler, "id"), refIDs(bowlerRef)["athleteId"])
|
|
685
|
+
otherBatsmanID := nonEmpty(stringField(otherBatsman, "playerId"), stringField(otherBatsman, "id"), refIDs(otherBatsmanRef)["athleteId"])
|
|
686
|
+
otherBowlerID := nonEmpty(stringField(otherBowler, "playerId"), stringField(otherBowler, "id"), refIDs(otherBowlerRef)["athleteId"])
|
|
679
687
|
fielderID := nonEmpty(stringField(fielder, "playerId"), stringField(fielder, "id"), refIDs(fielderRef)["athleteId"])
|
|
680
688
|
teamRef := refFromField(payload, "team")
|
|
681
689
|
teamIDs := refIDs(teamRef)
|
|
@@ -685,41 +693,65 @@ func NormalizeDeliveryEvent(data []byte) (*DeliveryEvent, error) {
|
|
|
685
693
|
bbbTimestamp := int64Field(payload, "bbbTimestamp")
|
|
686
694
|
|
|
687
695
|
event := &DeliveryEvent{
|
|
688
|
-
Ref:
|
|
689
|
-
ID:
|
|
690
|
-
LeagueID:
|
|
691
|
-
EventID:
|
|
692
|
-
CompetitionID:
|
|
693
|
-
MatchID:
|
|
694
|
-
TeamID:
|
|
695
|
-
Period:
|
|
696
|
-
PeriodText:
|
|
697
|
-
OverNumber:
|
|
698
|
-
BallNumber:
|
|
699
|
-
ScoreValue:
|
|
700
|
-
ShortText:
|
|
701
|
-
Text:
|
|
702
|
-
HomeScore:
|
|
703
|
-
AwayScore:
|
|
704
|
-
BatsmanRef:
|
|
705
|
-
BowlerRef:
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
696
|
+
Ref: ref,
|
|
697
|
+
ID: nonEmpty(stringField(payload, "id"), ids["detailId"]),
|
|
698
|
+
LeagueID: ids["leagueId"],
|
|
699
|
+
EventID: ids["eventId"],
|
|
700
|
+
CompetitionID: ids["competitionId"],
|
|
701
|
+
MatchID: ids["competitionId"],
|
|
702
|
+
TeamID: nonEmpty(teamIDs["teamId"], teamIDs["competitorId"]),
|
|
703
|
+
Period: intField(payload, "period"),
|
|
704
|
+
PeriodText: stringField(payload, "periodText"),
|
|
705
|
+
OverNumber: intField(over, "number"),
|
|
706
|
+
BallNumber: intField(over, "ball"),
|
|
707
|
+
ScoreValue: intField(payload, "scoreValue"),
|
|
708
|
+
ShortText: stringField(payload, "shortText"),
|
|
709
|
+
Text: stringField(payload, "text"),
|
|
710
|
+
HomeScore: stringField(payload, "homeScore"),
|
|
711
|
+
AwayScore: stringField(payload, "awayScore"),
|
|
712
|
+
BatsmanRef: batsmanRef,
|
|
713
|
+
BowlerRef: bowlerRef,
|
|
714
|
+
OtherBatsmanRef: otherBatsmanRef,
|
|
715
|
+
OtherBowlerRef: otherBowlerRef,
|
|
716
|
+
BatsmanPlayerID: batsmanID,
|
|
717
|
+
BowlerPlayerID: bowlerID,
|
|
718
|
+
OtherBatsmanID: otherBatsmanID,
|
|
719
|
+
OtherBowlerID: otherBowlerID,
|
|
720
|
+
FielderPlayerID: fielderID,
|
|
721
|
+
AthletePlayerIDs: athletePlayerIDs,
|
|
722
|
+
BatsmanRuns: intField(batsman, "runs"),
|
|
723
|
+
BatsmanTotalRuns: intField(batsman, "totalRuns"),
|
|
724
|
+
BatsmanBalls: intField(batsman, "faced"),
|
|
725
|
+
BatsmanFours: intField(batsman, "fours"),
|
|
726
|
+
BatsmanSixes: intField(batsman, "sixes"),
|
|
727
|
+
OtherBatterRuns: intField(otherBatsman, "totalRuns"),
|
|
728
|
+
OtherBatterBalls: intField(otherBatsman, "faced"),
|
|
729
|
+
OtherBatterFours: intField(otherBatsman, "fours"),
|
|
730
|
+
OtherBatterSixes: intField(otherBatsman, "sixes"),
|
|
731
|
+
BowlerOvers: floatField(bowler, "overs"),
|
|
732
|
+
BowlerBalls: intField(bowler, "balls"),
|
|
733
|
+
BowlerMaidens: intField(bowler, "maidens"),
|
|
734
|
+
BowlerConceded: intField(bowler, "conceded"),
|
|
735
|
+
BowlerWickets: intField(bowler, "wickets"),
|
|
736
|
+
OtherBowlerOvers: floatField(otherBowler, "overs"),
|
|
737
|
+
OtherBowlerBalls: intField(otherBowler, "balls"),
|
|
738
|
+
OtherBowlerMaidens: intField(otherBowler, "maidens"),
|
|
739
|
+
OtherBowlerConceded: intField(otherBowler, "conceded"),
|
|
740
|
+
OtherBowlerWickets: intField(otherBowler, "wickets"),
|
|
741
|
+
Sequence: intField(payload, "sequence"),
|
|
742
|
+
PlayType: playType,
|
|
743
|
+
Dismissal: dismissal,
|
|
744
|
+
DismissalType: stringField(dismissal, "type"),
|
|
745
|
+
DismissalName: nonEmpty(stringField(dismissal, "name"), stringField(dismissal, "type")),
|
|
746
|
+
DismissalCard: stringField(dismissal, "dismissalCard"),
|
|
747
|
+
DismissalText: stringField(dismissal, "text"),
|
|
748
|
+
SpeedKPH: floatField(payload, "speedKPH"),
|
|
749
|
+
XCoordinate: xCoordinate,
|
|
750
|
+
YCoordinate: yCoordinate,
|
|
751
|
+
BBBTimestamp: bbbTimestamp,
|
|
752
|
+
CoordinateX: xCoordinate,
|
|
753
|
+
CoordinateY: yCoordinate,
|
|
754
|
+
Timestamp: bbbTimestamp,
|
|
723
755
|
Extensions: extensionsFromMap(payload,
|
|
724
756
|
"$ref", "id", "period", "periodText", "over", "scoreValue", "shortText", "text", "homeScore", "awayScore",
|
|
725
757
|
"batsman", "bowler", "playType", "dismissal", "speedKPH", "xCoordinate", "yCoordinate", "bbbTimestamp",
|
|
@@ -1033,6 +1065,16 @@ func NormalizePartnership(data []byte) (*Partnership, error) {
|
|
|
1033
1065
|
),
|
|
1034
1066
|
}
|
|
1035
1067
|
|
|
1068
|
+
if partnership.WicketNumber == 0 {
|
|
1069
|
+
partnership.WicketNumber = parseInt(ids["partnershipId"])
|
|
1070
|
+
}
|
|
1071
|
+
if partnership.Runs == 0 && partnership.End.Runs > partnership.Start.Runs {
|
|
1072
|
+
partnership.Runs = partnership.End.Runs - partnership.Start.Runs
|
|
1073
|
+
}
|
|
1074
|
+
if partnership.Overs == 0 && partnership.End.Overs > partnership.Start.Overs {
|
|
1075
|
+
partnership.Overs = partnership.End.Overs - partnership.Start.Overs
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1036
1078
|
return partnership, nil
|
|
1037
1079
|
}
|
|
1038
1080
|
|
|
@@ -1097,6 +1139,12 @@ func NormalizeFallOfWicket(data []byte) (*FallOfWicket, error) {
|
|
|
1097
1139
|
if fow.WicketNumber == 0 {
|
|
1098
1140
|
fow.WicketNumber = parseInt(ids["fowId"])
|
|
1099
1141
|
}
|
|
1142
|
+
if fow.Runs == 0 && fow.RunsScored > 0 {
|
|
1143
|
+
fow.Runs = fow.RunsScored
|
|
1144
|
+
}
|
|
1145
|
+
if fow.RunsScored == 0 && fow.Runs > 0 {
|
|
1146
|
+
fow.RunsScored = fow.Runs
|
|
1147
|
+
}
|
|
1100
1148
|
|
|
1101
1149
|
return fow, nil
|
|
1102
1150
|
}
|
|
@@ -546,9 +546,36 @@ func (s *PlayerService) statistics(ctx context.Context, query string, opts Playe
|
|
|
546
546
|
playerStats.PlayerRef = strings.TrimSpace(lookup.player.Ref)
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
warnings := append([]string{}, lookup.warnings...)
|
|
550
|
+
if playerStatsLooksPlaceholder(playerStats, resolved) {
|
|
551
|
+
fallbackResolved, fallbackStats, fallbackWarnings, ok := s.fallbackPlayerStatsFromMatchContext(ctx, lookup, opts)
|
|
552
|
+
warnings = append(warnings, fallbackWarnings...)
|
|
553
|
+
if ok {
|
|
554
|
+
warnings = append(warnings, "global athlete statistics were placeholder; using match-context statistics")
|
|
555
|
+
compact := compactWarnings(warnings)
|
|
556
|
+
result := NewDataResult(EntityPlayerStats, *fallbackStats)
|
|
557
|
+
if len(compact) > 0 {
|
|
558
|
+
result = NewPartialResult(EntityPlayerStats, *fallbackStats, compact...)
|
|
559
|
+
}
|
|
560
|
+
if fallbackResolved != nil {
|
|
561
|
+
result.RequestedRef = fallbackResolved.RequestedRef
|
|
562
|
+
result.CanonicalRef = fallbackResolved.CanonicalRef
|
|
563
|
+
}
|
|
564
|
+
return result, nil
|
|
565
|
+
}
|
|
566
|
+
warnings = append(warnings, fmt.Sprintf("global athlete statistics for %q appear placeholder and no match-context fallback was found", nonEmpty(lookup.player.DisplayName, lookup.entity.Name, query)))
|
|
567
|
+
result := NewDataResult(EntityPlayerStats, *playerStats)
|
|
568
|
+
if compact := compactWarnings(warnings); len(compact) > 0 {
|
|
569
|
+
result = NewPartialResult(EntityPlayerStats, *playerStats, compact...)
|
|
570
|
+
}
|
|
571
|
+
result.RequestedRef = resolved.RequestedRef
|
|
572
|
+
result.CanonicalRef = resolved.CanonicalRef
|
|
573
|
+
return result, nil
|
|
574
|
+
}
|
|
575
|
+
|
|
549
576
|
result := NewDataResult(EntityPlayerStats, *playerStats)
|
|
550
|
-
if
|
|
551
|
-
result = NewPartialResult(EntityPlayerStats, *playerStats,
|
|
577
|
+
if compact := compactWarnings(warnings); len(compact) > 0 {
|
|
578
|
+
result = NewPartialResult(EntityPlayerStats, *playerStats, compact...)
|
|
552
579
|
}
|
|
553
580
|
result.RequestedRef = resolved.RequestedRef
|
|
554
581
|
result.CanonicalRef = resolved.CanonicalRef
|
|
@@ -603,6 +630,213 @@ func (s *PlayerService) resolvePlayerLookup(ctx context.Context, query string, o
|
|
|
603
630
|
}, nil
|
|
604
631
|
}
|
|
605
632
|
|
|
633
|
+
func playerStatsLooksPlaceholder(stats *PlayerStatistics, resolved *ResolvedDocument) bool {
|
|
634
|
+
if stats == nil {
|
|
635
|
+
return true
|
|
636
|
+
}
|
|
637
|
+
if hasMeaningfulStatValues(stats.Categories) {
|
|
638
|
+
return false
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
playerRefID := strings.TrimSpace(refIDs(stats.PlayerRef)["athleteId"])
|
|
642
|
+
if playerRefID == "" {
|
|
643
|
+
return true
|
|
644
|
+
}
|
|
645
|
+
if strings.Contains(strings.TrimSpace(stats.Ref), "/athletes//statistics") {
|
|
646
|
+
return true
|
|
647
|
+
}
|
|
648
|
+
if resolved != nil && strings.Contains(strings.TrimSpace(resolved.CanonicalRef), "/athletes//statistics") {
|
|
649
|
+
return true
|
|
650
|
+
}
|
|
651
|
+
return false
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
func hasMeaningfulStatValues(categories []StatCategory) bool {
|
|
655
|
+
for _, category := range categories {
|
|
656
|
+
for _, stat := range category.Stats {
|
|
657
|
+
display := strings.TrimSpace(stat.DisplayValue)
|
|
658
|
+
if display != "" && display != "-" && display != "0" && display != "0.0" && display != "0.00" {
|
|
659
|
+
return true
|
|
660
|
+
}
|
|
661
|
+
if statAsFloat(stat) > 0 {
|
|
662
|
+
return true
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return false
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
func (s *PlayerService) fallbackPlayerStatsFromMatchContext(
|
|
670
|
+
ctx context.Context,
|
|
671
|
+
lookup *playerLookup,
|
|
672
|
+
opts PlayerLookupOptions,
|
|
673
|
+
) (*ResolvedDocument, *PlayerStatistics, []string, bool) {
|
|
674
|
+
if lookup == nil || s == nil || s.resolver == nil {
|
|
675
|
+
return nil, nil, nil, false
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
warnings := make([]string, 0)
|
|
679
|
+
matchHelper := &MatchService{client: s.client, resolver: s.resolver}
|
|
680
|
+
candidateIDs := compactWarnings([]string{
|
|
681
|
+
strings.TrimSpace(lookup.player.ID),
|
|
682
|
+
strings.TrimSpace(lookup.entity.ID),
|
|
683
|
+
strings.TrimSpace(refIDs(lookup.player.Ref)["athleteId"]),
|
|
684
|
+
})
|
|
685
|
+
candidateNames := compactWarnings([]string{
|
|
686
|
+
strings.TrimSpace(lookup.player.DisplayName),
|
|
687
|
+
strings.TrimSpace(lookup.player.FullName),
|
|
688
|
+
strings.TrimSpace(lookup.player.Name),
|
|
689
|
+
strings.TrimSpace(lookup.player.BattingName),
|
|
690
|
+
strings.TrimSpace(lookup.player.FieldingName),
|
|
691
|
+
strings.TrimSpace(lookup.entity.Name),
|
|
692
|
+
strings.TrimSpace(lookup.entity.ShortName),
|
|
693
|
+
})
|
|
694
|
+
playerQuery := nonEmpty(
|
|
695
|
+
strings.TrimSpace(lookup.player.ID),
|
|
696
|
+
strings.TrimSpace(lookup.player.DisplayName),
|
|
697
|
+
strings.TrimSpace(lookup.player.FullName),
|
|
698
|
+
strings.TrimSpace(lookup.player.BattingName),
|
|
699
|
+
strings.TrimSpace(lookup.player.FieldingName),
|
|
700
|
+
strings.TrimSpace(lookup.entity.Name),
|
|
701
|
+
)
|
|
702
|
+
seenMatch := map[string]struct{}{}
|
|
703
|
+
candidateMatches := make([]Match, 0, 40)
|
|
704
|
+
appendCandidateMatch := func(match Match) {
|
|
705
|
+
key := nonEmpty(strings.TrimSpace(match.ID), strings.TrimSpace(match.Ref))
|
|
706
|
+
if key == "" {
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
if _, ok := seenMatch[key]; ok {
|
|
710
|
+
return
|
|
711
|
+
}
|
|
712
|
+
seenMatch[key] = struct{}{}
|
|
713
|
+
candidateMatches = append(candidateMatches, match)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
appendMatchesFromListResult := func(result NormalizedResult, source string) {
|
|
717
|
+
if result.Status == ResultStatusError {
|
|
718
|
+
if strings.TrimSpace(result.Message) != "" {
|
|
719
|
+
warnings = append(warnings, fmt.Sprintf("%s match-context source failed: %s", source, result.Message))
|
|
720
|
+
}
|
|
721
|
+
return
|
|
722
|
+
}
|
|
723
|
+
warnings = append(warnings, result.Warnings...)
|
|
724
|
+
for _, item := range result.Items {
|
|
725
|
+
switch typed := item.(type) {
|
|
726
|
+
case Match:
|
|
727
|
+
appendCandidateMatch(typed)
|
|
728
|
+
case *Match:
|
|
729
|
+
if typed != nil {
|
|
730
|
+
appendCandidateMatch(*typed)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
liveResult, liveErr := matchHelper.Live(ctx, MatchListOptions{
|
|
737
|
+
Limit: 24,
|
|
738
|
+
LeagueID: strings.TrimSpace(opts.LeagueID),
|
|
739
|
+
})
|
|
740
|
+
if liveErr != nil {
|
|
741
|
+
warnings = append(warnings, fmt.Sprintf("match-context live source failed: %v", liveErr))
|
|
742
|
+
} else {
|
|
743
|
+
appendMatchesFromListResult(liveResult, "live")
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
listResult, listErr := matchHelper.List(ctx, MatchListOptions{
|
|
747
|
+
Limit: 36,
|
|
748
|
+
LeagueID: strings.TrimSpace(opts.LeagueID),
|
|
749
|
+
})
|
|
750
|
+
if listErr != nil {
|
|
751
|
+
warnings = append(warnings, fmt.Sprintf("match-context list source failed: %v", listErr))
|
|
752
|
+
} else {
|
|
753
|
+
appendMatchesFromListResult(listResult, "list")
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if len(candidateMatches) == 0 {
|
|
757
|
+
matchSearch, err := s.resolver.Search(ctx, EntityMatch, "", ResolveOptions{
|
|
758
|
+
Limit: 24,
|
|
759
|
+
LeagueID: strings.TrimSpace(opts.LeagueID),
|
|
760
|
+
})
|
|
761
|
+
if err != nil {
|
|
762
|
+
warnings = append(warnings, fmt.Sprintf("match-context fallback unavailable: %v", err))
|
|
763
|
+
return nil, nil, compactWarnings(warnings), false
|
|
764
|
+
}
|
|
765
|
+
warnings = append(warnings, matchSearch.Warnings...)
|
|
766
|
+
|
|
767
|
+
for _, entity := range matchSearch.Entities {
|
|
768
|
+
ref := buildMatchRef(entity)
|
|
769
|
+
if ref == "" {
|
|
770
|
+
continue
|
|
771
|
+
}
|
|
772
|
+
matchResolved, matchErr := matchHelper.resolveRefChainResilient(ctx, ref)
|
|
773
|
+
if matchErr != nil {
|
|
774
|
+
warnings = append(warnings, fmt.Sprintf("match %s: %v", ref, matchErr))
|
|
775
|
+
continue
|
|
776
|
+
}
|
|
777
|
+
match, normalizeErr := NormalizeMatch(matchResolved.Body)
|
|
778
|
+
if normalizeErr != nil {
|
|
779
|
+
warnings = append(warnings, fmt.Sprintf("match %s: %v", matchResolved.CanonicalRef, normalizeErr))
|
|
780
|
+
continue
|
|
781
|
+
}
|
|
782
|
+
appendCandidateMatch(*match)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if len(candidateMatches) == 0 {
|
|
787
|
+
warnings = append(warnings, "match-context fallback found no recent matches")
|
|
788
|
+
return nil, nil, compactWarnings(warnings), false
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
for _, match := range candidateMatches {
|
|
792
|
+
team, roster, routeWarnings, found := s.findPlayerRosterEntry(ctx, match, playerQuery, candidateIDs, candidateNames)
|
|
793
|
+
warnings = append(warnings, routeWarnings...)
|
|
794
|
+
if !found {
|
|
795
|
+
continue
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
team = s.enrichTeamIdentityFromIndex(team)
|
|
799
|
+
roster = s.enrichRosterEntryFromIndex(roster)
|
|
800
|
+
statsRef := rosterPlayerStatisticsRef(match, team, roster)
|
|
801
|
+
if statsRef == "" {
|
|
802
|
+
warnings = append(warnings, fmt.Sprintf("match %s has no roster statistics route for player %s", match.ID, nonEmpty(roster.PlayerID, lookup.player.ID)))
|
|
803
|
+
continue
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
statsResolved, categories, statsErr := s.fetchStatCategories(ctx, statsRef)
|
|
807
|
+
if statsErr != nil {
|
|
808
|
+
warnings = append(warnings, fmt.Sprintf("player statistics %s: %v", statsRef, statsErr))
|
|
809
|
+
continue
|
|
810
|
+
}
|
|
811
|
+
if !hasMeaningfulStatValues(categories) {
|
|
812
|
+
warnings = append(warnings, fmt.Sprintf("player statistics %s returned no meaningful values", statsResolved.CanonicalRef))
|
|
813
|
+
continue
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
playerID := nonEmpty(strings.TrimSpace(lookup.player.ID), strings.TrimSpace(lookup.entity.ID), strings.TrimSpace(roster.PlayerID))
|
|
817
|
+
playerRef := nonEmpty(strings.TrimSpace(lookup.player.Ref), strings.TrimSpace(roster.PlayerRef))
|
|
818
|
+
stats := &PlayerStatistics{
|
|
819
|
+
Ref: statsResolved.CanonicalRef,
|
|
820
|
+
PlayerID: playerID,
|
|
821
|
+
PlayerRef: playerRef,
|
|
822
|
+
SplitID: "match-context",
|
|
823
|
+
Name: "Match Context",
|
|
824
|
+
Abbreviation: "LIVE",
|
|
825
|
+
Categories: categories,
|
|
826
|
+
Extensions: map[string]any{
|
|
827
|
+
"source": "match-context-fallback",
|
|
828
|
+
"matchId": strings.TrimSpace(match.ID),
|
|
829
|
+
"teamId": strings.TrimSpace(team.ID),
|
|
830
|
+
"teamName": teamDisplayLabel(team),
|
|
831
|
+
"statisticsRef": strings.TrimSpace(statsResolved.CanonicalRef),
|
|
832
|
+
},
|
|
833
|
+
}
|
|
834
|
+
return statsResolved, stats, compactWarnings(warnings), true
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return nil, nil, compactWarnings(warnings), false
|
|
838
|
+
}
|
|
839
|
+
|
|
606
840
|
func (s *PlayerService) enrichPlayerProfile(ctx context.Context, player *Player) {
|
|
607
841
|
if s == nil || s.resolver == nil || player == nil {
|
|
608
842
|
return
|