cricinfo-cli-go 0.1.0

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.
Files changed (121) hide show
  1. package/AGENTS.md +63 -0
  2. package/CONTRIBUTORS.md +75 -0
  3. package/LICENSE +21 -0
  4. package/Makefile +131 -0
  5. package/README.md +130 -0
  6. package/bin/cricinfo.js +44 -0
  7. package/cmd/cricinfo/main.go +15 -0
  8. package/go.mod +10 -0
  9. package/go.sum +10 -0
  10. package/internal/app/app.go +11 -0
  11. package/internal/app/app_test.go +122 -0
  12. package/internal/buildinfo/buildinfo.go +16 -0
  13. package/internal/cli/analysis.go +262 -0
  14. package/internal/cli/analysis_test.go +175 -0
  15. package/internal/cli/competitions.go +154 -0
  16. package/internal/cli/competitions_test.go +165 -0
  17. package/internal/cli/leagues.go +297 -0
  18. package/internal/cli/leagues_test.go +194 -0
  19. package/internal/cli/matches.go +403 -0
  20. package/internal/cli/matches_test.go +413 -0
  21. package/internal/cli/players.go +263 -0
  22. package/internal/cli/players_test.go +384 -0
  23. package/internal/cli/root.go +141 -0
  24. package/internal/cli/search.go +119 -0
  25. package/internal/cli/teams.go +214 -0
  26. package/internal/cli/teams_test.go +192 -0
  27. package/internal/cricinfo/analysis.go +1401 -0
  28. package/internal/cricinfo/analysis_phase15_test.go +267 -0
  29. package/internal/cricinfo/client.go +471 -0
  30. package/internal/cricinfo/client_test.go +280 -0
  31. package/internal/cricinfo/cmd/fixture-refresh/main.go +145 -0
  32. package/internal/cricinfo/competitions.go +405 -0
  33. package/internal/cricinfo/competitions_phase13_test.go +234 -0
  34. package/internal/cricinfo/coverage_ledger.go +122 -0
  35. package/internal/cricinfo/coverage_ledger_test.go +253 -0
  36. package/internal/cricinfo/decode.go +115 -0
  37. package/internal/cricinfo/decode_test.go +100 -0
  38. package/internal/cricinfo/entity_index.go +618 -0
  39. package/internal/cricinfo/entity_index_test.go +175 -0
  40. package/internal/cricinfo/fixture_matrix.go +243 -0
  41. package/internal/cricinfo/fixture_matrix_test.go +49 -0
  42. package/internal/cricinfo/fixtures_test.go +264 -0
  43. package/internal/cricinfo/historical_hydration.go +1641 -0
  44. package/internal/cricinfo/historical_phase14_test.go +542 -0
  45. package/internal/cricinfo/leagues.go +1210 -0
  46. package/internal/cricinfo/leagues_phase12_test.go +324 -0
  47. package/internal/cricinfo/live_leagues_test.go +169 -0
  48. package/internal/cricinfo/live_matches_test.go +203 -0
  49. package/internal/cricinfo/live_matrix_test.go +118 -0
  50. package/internal/cricinfo/live_players_test.go +122 -0
  51. package/internal/cricinfo/live_search_test.go +86 -0
  52. package/internal/cricinfo/live_smoke_test.go +213 -0
  53. package/internal/cricinfo/live_teams_test.go +104 -0
  54. package/internal/cricinfo/matches.go +1508 -0
  55. package/internal/cricinfo/matches_phase7_test.go +207 -0
  56. package/internal/cricinfo/matches_phase9_test.go +253 -0
  57. package/internal/cricinfo/normalize_entities.go +1727 -0
  58. package/internal/cricinfo/normalize_leagues.go +346 -0
  59. package/internal/cricinfo/players.go +1332 -0
  60. package/internal/cricinfo/players_phase10_test.go +174 -0
  61. package/internal/cricinfo/players_phase11_test.go +373 -0
  62. package/internal/cricinfo/render_contract.go +1088 -0
  63. package/internal/cricinfo/render_phase4_test.go +633 -0
  64. package/internal/cricinfo/renderer.go +1689 -0
  65. package/internal/cricinfo/resolver.go +813 -0
  66. package/internal/cricinfo/resolver_test.go +244 -0
  67. package/internal/cricinfo/teams.go +603 -0
  68. package/internal/cricinfo/teams_phase8_test.go +231 -0
  69. package/internal/cricinfo/testdata/fixtures/README.md +43 -0
  70. package/internal/cricinfo/testdata/fixtures/aux-competition-metadata/broadcasts.json +11 -0
  71. package/internal/cricinfo/testdata/fixtures/aux-competition-metadata/officials.json +150 -0
  72. package/internal/cricinfo/testdata/fixtures/details-plays/detail-110.json +157 -0
  73. package/internal/cricinfo/testdata/fixtures/details-plays/detail-52545007.json +145 -0
  74. package/internal/cricinfo/testdata/fixtures/details-plays/detail-52559021.json +143 -0
  75. package/internal/cricinfo/testdata/fixtures/details-plays/plays.json +15 -0
  76. package/internal/cricinfo/testdata/fixtures/endpoint-matrix.tsv +19 -0
  77. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/fow-1.json +12 -0
  78. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/fow.json +42 -0
  79. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/innings-1-2.json +38 -0
  80. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/partnership-1.json +31 -0
  81. package/internal/cricinfo/testdata/fixtures/innings-fow-partnerships/partnerships.json +42 -0
  82. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar-offdays.json +20 -0
  83. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar-ondays.json +21 -0
  84. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/calendar.json +14 -0
  85. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-2025.json +13 -0
  86. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-group-1.json +13 -0
  87. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-groups.json +11 -0
  88. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-type-1.json +13 -0
  89. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/season-types.json +11 -0
  90. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/seasons.json +30 -0
  91. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings-item-1.json +72 -0
  92. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings-root.json +3 -0
  93. package/internal/cricinfo/testdata/fixtures/leagues-seasons-standings/standings.json +15 -0
  94. package/internal/cricinfo/testdata/fixtures/matches-competitions/competition.json +460 -0
  95. package/internal/cricinfo/testdata/fixtures/matches-competitions/event-1529474.json +86 -0
  96. package/internal/cricinfo/testdata/fixtures/matches-competitions/matchcards-1527966.json +368 -0
  97. package/internal/cricinfo/testdata/fixtures/matches-competitions/situation-1529474.json +10 -0
  98. package/internal/cricinfo/testdata/fixtures/players/athlete-1361257-statistics.json +126 -0
  99. package/internal/cricinfo/testdata/fixtures/players/athlete-1361257.json +113 -0
  100. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores-1-1-statistics-0.json +208 -0
  101. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores-1-2-statistics-0.json +252 -0
  102. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-linescores.json +74 -0
  103. package/internal/cricinfo/testdata/fixtures/players/roster-1361257-statistics-0.json +1008 -0
  104. package/internal/cricinfo/testdata/fixtures/root-discovery/events.json +72 -0
  105. package/internal/cricinfo/testdata/fixtures/root-discovery/root.json +28 -0
  106. package/internal/cricinfo/testdata/fixtures/team-competitor/competitor-789643.json +40 -0
  107. package/internal/cricinfo/testdata/fixtures/team-competitor/leaders-789643.json +353 -0
  108. package/internal/cricinfo/testdata/fixtures/team-competitor/records-789643.json +91 -0
  109. package/internal/cricinfo/testdata/fixtures/team-competitor/roster-1147772-object.json +231 -0
  110. package/internal/cricinfo/testdata/fixtures/team-competitor/roster-1147772.json +235 -0
  111. package/internal/cricinfo/testdata/fixtures/team-competitor/roster-789643.json +322 -0
  112. package/internal/cricinfo/testdata/fixtures/team-competitor/scores-789643.json +19 -0
  113. package/internal/cricinfo/testdata/fixtures/team-competitor/statistics-789643.json +629 -0
  114. package/internal/cricinfo/testdata/fixtures/team-competitor/team-789643-athletes.json +7 -0
  115. package/internal/cricinfo/testdata/fixtures/team-competitor/team-789643.json +67 -0
  116. package/internal/cricinfo/testdata/golden/match-empty.golden +1 -0
  117. package/internal/cricinfo/testdata/golden/match-list.golden +2 -0
  118. package/internal/cricinfo/testdata/golden/match-partial.golden +3 -0
  119. package/internal/cricinfo/types.go +54 -0
  120. package/package.json +51 -0
  121. package/scripts/postinstall.js +153 -0
@@ -0,0 +1,1088 @@
1
+ package cricinfo
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "net/url"
7
+ "path"
8
+ "strconv"
9
+ "strings"
10
+ )
11
+
12
+ // EntityKind identifies normalized Cricinfo entity families.
13
+ type EntityKind string
14
+
15
+ const (
16
+ EntityMatch EntityKind = "match"
17
+ EntityMatchScorecard EntityKind = "match_scorecard"
18
+ EntityMatchSituation EntityKind = "match_situation"
19
+ EntityCompetition EntityKind = "competition"
20
+ EntityCompOfficial EntityKind = "competition_official"
21
+ EntityCompBroadcast EntityKind = "competition_broadcast"
22
+ EntityCompTicket EntityKind = "competition_ticket"
23
+ EntityCompOdds EntityKind = "competition_odds"
24
+ EntityCompMetadata EntityKind = "competition_metadata"
25
+ EntityPlayer EntityKind = "player"
26
+ EntityPlayerStats EntityKind = "player_statistics"
27
+ EntityPlayerMatch EntityKind = "player_match"
28
+ EntityPlayerInnings EntityKind = "player_innings"
29
+ EntityPlayerDismissal EntityKind = "player_dismissal"
30
+ EntityPlayerDelivery EntityKind = "player_delivery"
31
+ EntityNewsArticle EntityKind = "news_article"
32
+ EntityTeam EntityKind = "team"
33
+ EntityTeamRoster EntityKind = "team_roster"
34
+ EntityTeamScore EntityKind = "team_score"
35
+ EntityTeamLeaders EntityKind = "team_leaders"
36
+ EntityTeamStatistics EntityKind = "team_statistics"
37
+ EntityTeamRecords EntityKind = "team_records"
38
+ EntityLeague EntityKind = "league"
39
+ EntitySeason EntityKind = "season"
40
+ EntityCalendarDay EntityKind = "calendar_day"
41
+ EntitySeasonType EntityKind = "season_type"
42
+ EntitySeasonGroup EntityKind = "season_group"
43
+ EntityStandingsGroup EntityKind = "standings_group"
44
+ EntityInnings EntityKind = "innings"
45
+ EntityDeliveryEvent EntityKind = "delivery_event"
46
+ EntityStatCategory EntityKind = "stat_category"
47
+ EntityPartnership EntityKind = "partnership"
48
+ EntityFallOfWicket EntityKind = "fall_of_wicket"
49
+ EntityAnalysisDismiss EntityKind = "analysis_dismissal"
50
+ EntityAnalysisBowl EntityKind = "analysis_bowling"
51
+ EntityAnalysisBat EntityKind = "analysis_batting"
52
+ EntityAnalysisPart EntityKind = "analysis_partnership"
53
+ )
54
+
55
+ // ResultStatus standardizes output states across render formats.
56
+ type ResultStatus string
57
+
58
+ const (
59
+ ResultStatusOK ResultStatus = "ok"
60
+ ResultStatusEmpty ResultStatus = "empty"
61
+ ResultStatusPartial ResultStatus = "partial"
62
+ ResultStatusError ResultStatus = "error"
63
+ )
64
+
65
+ // TransportError is the normalized transport failure payload.
66
+ type TransportError struct {
67
+ Message string `json:"message"`
68
+ StatusCode int `json:"statusCode,omitempty"`
69
+ URL string `json:"url,omitempty"`
70
+ }
71
+
72
+ // NormalizedResult is the render boundary contract used by commands.
73
+ type NormalizedResult struct {
74
+ Kind EntityKind `json:"kind"`
75
+ Status ResultStatus `json:"status"`
76
+ RequestedRef string `json:"requestedRef,omitempty"`
77
+ CanonicalRef string `json:"canonicalRef,omitempty"`
78
+ Message string `json:"message,omitempty"`
79
+ Warnings []string `json:"warnings,omitempty"`
80
+ Error *TransportError `json:"error,omitempty"`
81
+ Data any `json:"data,omitempty"`
82
+ Items []any `json:"items,omitempty"`
83
+ }
84
+
85
+ // Match is the normalized core match shape.
86
+ type Match struct {
87
+ Ref string `json:"ref,omitempty"`
88
+ ID string `json:"id,omitempty"`
89
+ UID string `json:"uid,omitempty"`
90
+ LeagueID string `json:"leagueId,omitempty"`
91
+ EventID string `json:"eventId,omitempty"`
92
+ CompetitionID string `json:"competitionId,omitempty"`
93
+ Description string `json:"description,omitempty"`
94
+ ShortDescription string `json:"shortDescription,omitempty"`
95
+ Note string `json:"note,omitempty"`
96
+ MatchState string `json:"matchState,omitempty"`
97
+ Date string `json:"date,omitempty"`
98
+ EndDate string `json:"endDate,omitempty"`
99
+ VenueName string `json:"venueName,omitempty"`
100
+ VenueSummary string `json:"venueSummary,omitempty"`
101
+ ScoreSummary string `json:"scoreSummary,omitempty"`
102
+ StatusRef string `json:"statusRef,omitempty"`
103
+ DetailsRef string `json:"detailsRef,omitempty"`
104
+ Teams []Team `json:"teams,omitempty"`
105
+ Extensions map[string]any `json:"extensions,omitempty"`
106
+ }
107
+
108
+ // MatchScorecard is the normalized scorecard view grouped by batting, bowling, and partnerships cards.
109
+ type MatchScorecard struct {
110
+ Ref string `json:"ref,omitempty"`
111
+ LeagueID string `json:"leagueId,omitempty"`
112
+ EventID string `json:"eventId,omitempty"`
113
+ CompetitionID string `json:"competitionId,omitempty"`
114
+ MatchID string `json:"matchId,omitempty"`
115
+ BattingCards []BattingCard `json:"battingCards,omitempty"`
116
+ BowlingCards []BowlingCard `json:"bowlingCards,omitempty"`
117
+ PartnershipCards []PartnershipCard `json:"partnershipCards,omitempty"`
118
+ Extensions map[string]any `json:"extensions,omitempty"`
119
+ }
120
+
121
+ // BattingCard is a normalized batting card section from matchcards.
122
+ type BattingCard struct {
123
+ InningsNumber int `json:"inningsNumber,omitempty"`
124
+ TeamName string `json:"teamName,omitempty"`
125
+ Runs string `json:"runs,omitempty"`
126
+ Total string `json:"total,omitempty"`
127
+ Extras string `json:"extras,omitempty"`
128
+ Players []BattingCardEntry `json:"players,omitempty"`
129
+ }
130
+
131
+ // BattingCardEntry is a player row in a batting card.
132
+ type BattingCardEntry struct {
133
+ PlayerID string `json:"playerId,omitempty"`
134
+ PlayerName string `json:"playerName,omitempty"`
135
+ Dismissal string `json:"dismissal,omitempty"`
136
+ Runs string `json:"runs,omitempty"`
137
+ BallsFaced string `json:"ballsFaced,omitempty"`
138
+ Fours string `json:"fours,omitempty"`
139
+ Sixes string `json:"sixes,omitempty"`
140
+ Href string `json:"href,omitempty"`
141
+ }
142
+
143
+ // BowlingCard is a normalized bowling card section from matchcards.
144
+ type BowlingCard struct {
145
+ InningsNumber int `json:"inningsNumber,omitempty"`
146
+ TeamName string `json:"teamName,omitempty"`
147
+ Players []BowlingCardEntry `json:"players,omitempty"`
148
+ }
149
+
150
+ // BowlingCardEntry is a bowler row in a bowling card.
151
+ type BowlingCardEntry struct {
152
+ PlayerID string `json:"playerId,omitempty"`
153
+ PlayerName string `json:"playerName,omitempty"`
154
+ Overs string `json:"overs,omitempty"`
155
+ Maidens string `json:"maidens,omitempty"`
156
+ Conceded string `json:"conceded,omitempty"`
157
+ Wickets string `json:"wickets,omitempty"`
158
+ EconomyRate string `json:"economyRate,omitempty"`
159
+ NBW string `json:"nbw,omitempty"`
160
+ Href string `json:"href,omitempty"`
161
+ }
162
+
163
+ // PartnershipCard is a normalized partnerships card section from matchcards.
164
+ type PartnershipCard struct {
165
+ InningsNumber int `json:"inningsNumber,omitempty"`
166
+ TeamName string `json:"teamName,omitempty"`
167
+ Players []PartnershipCardEntry `json:"players,omitempty"`
168
+ }
169
+
170
+ // PartnershipCardEntry is a row in a partnerships card.
171
+ type PartnershipCardEntry struct {
172
+ PartnershipRuns string `json:"partnershipRuns,omitempty"`
173
+ PartnershipOvers string `json:"partnershipOvers,omitempty"`
174
+ PartnershipWicketName string `json:"partnershipWicketName,omitempty"`
175
+ FOWType string `json:"fowType,omitempty"`
176
+ Player1Name string `json:"player1Name,omitempty"`
177
+ Player1Runs string `json:"player1Runs,omitempty"`
178
+ Player2Name string `json:"player2Name,omitempty"`
179
+ Player2Runs string `json:"player2Runs,omitempty"`
180
+ }
181
+
182
+ // MatchSituation is the normalized match situation payload.
183
+ type MatchSituation struct {
184
+ Ref string `json:"ref,omitempty"`
185
+ LeagueID string `json:"leagueId,omitempty"`
186
+ EventID string `json:"eventId,omitempty"`
187
+ CompetitionID string `json:"competitionId,omitempty"`
188
+ MatchID string `json:"matchId,omitempty"`
189
+ OddsRef string `json:"oddsRef,omitempty"`
190
+ Data map[string]any `json:"data,omitempty"`
191
+ Extensions map[string]any `json:"extensions,omitempty"`
192
+ }
193
+
194
+ // Competition is the normalized competition metadata root view.
195
+ type Competition struct {
196
+ Ref string `json:"ref,omitempty"`
197
+ ID string `json:"id,omitempty"`
198
+ LeagueID string `json:"leagueId,omitempty"`
199
+ EventID string `json:"eventId,omitempty"`
200
+ CompetitionID string `json:"competitionId,omitempty"`
201
+ Description string `json:"description,omitempty"`
202
+ ShortDescription string `json:"shortDescription,omitempty"`
203
+ Date string `json:"date,omitempty"`
204
+ EndDate string `json:"endDate,omitempty"`
205
+ MatchState string `json:"matchState,omitempty"`
206
+ VenueName string `json:"venueName,omitempty"`
207
+ VenueSummary string `json:"venueSummary,omitempty"`
208
+ ScoreSummary string `json:"scoreSummary,omitempty"`
209
+ StatusRef string `json:"statusRef,omitempty"`
210
+ DetailsRef string `json:"detailsRef,omitempty"`
211
+ MatchcardsRef string `json:"matchcardsRef,omitempty"`
212
+ SituationRef string `json:"situationRef,omitempty"`
213
+ OfficialsRef string `json:"officialsRef,omitempty"`
214
+ BroadcastsRef string `json:"broadcastsRef,omitempty"`
215
+ TicketsRef string `json:"ticketsRef,omitempty"`
216
+ OddsRef string `json:"oddsRef,omitempty"`
217
+ Teams []Team `json:"teams,omitempty"`
218
+ Extensions map[string]any `json:"extensions,omitempty"`
219
+ }
220
+
221
+ // CompetitionMetadataEntry is a normalized row from officials/broadcasts/tickets/odds resources.
222
+ type CompetitionMetadataEntry struct {
223
+ Ref string `json:"ref,omitempty"`
224
+ ID string `json:"id,omitempty"`
225
+ DisplayName string `json:"displayName,omitempty"`
226
+ Name string `json:"name,omitempty"`
227
+ Role string `json:"role,omitempty"`
228
+ Type string `json:"type,omitempty"`
229
+ Order int `json:"order,omitempty"`
230
+ Text string `json:"text,omitempty"`
231
+ Value string `json:"value,omitempty"`
232
+ Href string `json:"href,omitempty"`
233
+ Extensions map[string]any `json:"extensions,omitempty"`
234
+ }
235
+
236
+ // CompetitionMetadataSummary aggregates auxiliary competition metadata routes.
237
+ type CompetitionMetadataSummary struct {
238
+ Competition Competition `json:"competition"`
239
+ Officials []CompetitionMetadataEntry `json:"officials,omitempty"`
240
+ Broadcasts []CompetitionMetadataEntry `json:"broadcasts,omitempty"`
241
+ Tickets []CompetitionMetadataEntry `json:"tickets,omitempty"`
242
+ Odds []CompetitionMetadataEntry `json:"odds,omitempty"`
243
+ }
244
+
245
+ // Player is the normalized core player shape.
246
+ type Player struct {
247
+ Ref string `json:"ref,omitempty"`
248
+ ID string `json:"id,omitempty"`
249
+ UID string `json:"uid,omitempty"`
250
+ GUID string `json:"guid,omitempty"`
251
+ Type string `json:"type,omitempty"`
252
+ Name string `json:"name,omitempty"`
253
+ FirstName string `json:"firstName,omitempty"`
254
+ MiddleName string `json:"middleName,omitempty"`
255
+ LastName string `json:"lastName,omitempty"`
256
+ DisplayName string `json:"displayName,omitempty"`
257
+ FullName string `json:"fullName,omitempty"`
258
+ ShortName string `json:"shortName,omitempty"`
259
+ BattingName string `json:"battingName,omitempty"`
260
+ FieldingName string `json:"fieldingName,omitempty"`
261
+ Gender string `json:"gender,omitempty"`
262
+ Age int `json:"age,omitempty"`
263
+ DateOfBirth string `json:"dateOfBirth,omitempty"`
264
+ DateOfBirthDisplay string `json:"dateOfBirthDisplay,omitempty"`
265
+ Active bool `json:"active"`
266
+ Position string `json:"position,omitempty"`
267
+ PositionRef string `json:"positionRef,omitempty"`
268
+ PositionAbbreviation string `json:"positionAbbreviation,omitempty"`
269
+ Styles []PlayerStyle `json:"styles,omitempty"`
270
+ Team *PlayerAffiliation `json:"team,omitempty"`
271
+ MajorTeams []PlayerAffiliation `json:"majorTeams,omitempty"`
272
+ Debuts []PlayerDebut `json:"debuts,omitempty"`
273
+ NewsRef string `json:"newsRef,omitempty"`
274
+ Extensions map[string]any `json:"extensions,omitempty"`
275
+ }
276
+
277
+ // PlayerStyle captures batting/bowling handedness or discipline metadata.
278
+ type PlayerStyle struct {
279
+ Type string `json:"type,omitempty"`
280
+ Description string `json:"description,omitempty"`
281
+ ShortDescription string `json:"shortDescription,omitempty"`
282
+ }
283
+
284
+ // PlayerAffiliation captures a player-team relationship from profile payloads.
285
+ type PlayerAffiliation struct {
286
+ ID string `json:"id,omitempty"`
287
+ Ref string `json:"ref,omitempty"`
288
+ Name string `json:"name,omitempty"`
289
+ }
290
+
291
+ // PlayerDebut captures a debut reference exposed by the athlete profile.
292
+ type PlayerDebut struct {
293
+ ID string `json:"id,omitempty"`
294
+ Ref string `json:"ref,omitempty"`
295
+ Name string `json:"name,omitempty"`
296
+ }
297
+
298
+ // PlayerStatistics keeps the upstream grouped split/category structure intact.
299
+ type PlayerStatistics struct {
300
+ Ref string `json:"ref,omitempty"`
301
+ PlayerID string `json:"playerId,omitempty"`
302
+ PlayerRef string `json:"playerRef,omitempty"`
303
+ SplitID string `json:"splitId,omitempty"`
304
+ Name string `json:"name,omitempty"`
305
+ Abbreviation string `json:"abbreviation,omitempty"`
306
+ Categories []StatCategory `json:"categories,omitempty"`
307
+ Extensions map[string]any `json:"extensions,omitempty"`
308
+ }
309
+
310
+ // PlayerMatch summarizes player-in-match context from roster stats and innings/detail routes.
311
+ type PlayerMatch struct {
312
+ PlayerID string `json:"playerId,omitempty"`
313
+ PlayerRef string `json:"playerRef,omitempty"`
314
+ PlayerName string `json:"playerName,omitempty"`
315
+ MatchID string `json:"matchId,omitempty"`
316
+ CompetitionID string `json:"competitionId,omitempty"`
317
+ EventID string `json:"eventId,omitempty"`
318
+ LeagueID string `json:"leagueId,omitempty"`
319
+ TeamID string `json:"teamId,omitempty"`
320
+ TeamName string `json:"teamName,omitempty"`
321
+ StatisticsRef string `json:"statisticsRef,omitempty"`
322
+ LinescoresRef string `json:"linescoresRef,omitempty"`
323
+ Batting []StatCategory `json:"batting,omitempty"`
324
+ Bowling []StatCategory `json:"bowling,omitempty"`
325
+ Fielding []StatCategory `json:"fielding,omitempty"`
326
+ Summary PlayerMatchSummary `json:"summary,omitempty"`
327
+ Extensions map[string]any `json:"extensions,omitempty"`
328
+ }
329
+
330
+ // PlayerMatchSummary exposes high-value batting/bowling and dismissal fields for agent reasoning.
331
+ type PlayerMatchSummary struct {
332
+ DismissalName string `json:"dismissalName,omitempty"`
333
+ DismissalCard string `json:"dismissalCard,omitempty"`
334
+ BallsFaced int `json:"ballsFaced,omitempty"`
335
+ StrikeRate float64 `json:"strikeRate,omitempty"`
336
+ Dots int `json:"dots,omitempty"`
337
+ EconomyRate float64 `json:"economyRate,omitempty"`
338
+ Maidens int `json:"maidens,omitempty"`
339
+ FoursConceded int `json:"foursConceded,omitempty"`
340
+ SixesConceded int `json:"sixesConceded,omitempty"`
341
+ Wides int `json:"wides,omitempty"`
342
+ Noballs int `json:"noballs,omitempty"`
343
+ BowlerPlayerID string `json:"bowlerPlayerId,omitempty"`
344
+ FielderPlayerID string `json:"fielderPlayerId,omitempty"`
345
+ }
346
+
347
+ // PlayerInnings is a normalized player-specific innings split row.
348
+ type PlayerInnings struct {
349
+ Ref string `json:"ref,omitempty"`
350
+ PlayerID string `json:"playerId,omitempty"`
351
+ PlayerName string `json:"playerName,omitempty"`
352
+ MatchID string `json:"matchId,omitempty"`
353
+ CompetitionID string `json:"competitionId,omitempty"`
354
+ EventID string `json:"eventId,omitempty"`
355
+ LeagueID string `json:"leagueId,omitempty"`
356
+ TeamID string `json:"teamId,omitempty"`
357
+ TeamName string `json:"teamName,omitempty"`
358
+ InningsNumber int `json:"inningsNumber,omitempty"`
359
+ Period int `json:"period,omitempty"`
360
+ Order int `json:"order,omitempty"`
361
+ IsBatting bool `json:"isBatting"`
362
+ StatisticsRef string `json:"statisticsRef,omitempty"`
363
+ Batting []StatCategory `json:"batting,omitempty"`
364
+ Bowling []StatCategory `json:"bowling,omitempty"`
365
+ Fielding []StatCategory `json:"fielding,omitempty"`
366
+ Summary PlayerMatchSummary `json:"summary,omitempty"`
367
+ Extensions map[string]any `json:"extensions,omitempty"`
368
+ }
369
+
370
+ // PlayerDismissal is a dismissal-focused first-class output view.
371
+ type PlayerDismissal struct {
372
+ PlayerID string `json:"playerId,omitempty"`
373
+ PlayerName string `json:"playerName,omitempty"`
374
+ MatchID string `json:"matchId,omitempty"`
375
+ CompetitionID string `json:"competitionId,omitempty"`
376
+ EventID string `json:"eventId,omitempty"`
377
+ LeagueID string `json:"leagueId,omitempty"`
378
+ TeamID string `json:"teamId,omitempty"`
379
+ TeamName string `json:"teamName,omitempty"`
380
+ InningsNumber int `json:"inningsNumber,omitempty"`
381
+ Period int `json:"period,omitempty"`
382
+ WicketNumber int `json:"wicketNumber,omitempty"`
383
+ FOW string `json:"fow,omitempty"`
384
+ Over string `json:"over,omitempty"`
385
+ DetailRef string `json:"detailRef,omitempty"`
386
+ DetailShortText string `json:"detailShortText,omitempty"`
387
+ DetailText string `json:"detailText,omitempty"`
388
+ DismissalName string `json:"dismissalName,omitempty"`
389
+ DismissalCard string `json:"dismissalCard,omitempty"`
390
+ DismissalType string `json:"dismissalType,omitempty"`
391
+ DismissalText string `json:"dismissalText,omitempty"`
392
+ BallsFaced int `json:"ballsFaced,omitempty"`
393
+ StrikeRate float64 `json:"strikeRate,omitempty"`
394
+ BatsmanPlayerID string `json:"batsmanPlayerId,omitempty"`
395
+ BowlerPlayerID string `json:"bowlerPlayerId,omitempty"`
396
+ FielderPlayerID string `json:"fielderPlayerId,omitempty"`
397
+ }
398
+
399
+ // NewsArticle is a normalized Cricinfo article/story payload.
400
+ type NewsArticle struct {
401
+ Ref string `json:"ref,omitempty"`
402
+ ID string `json:"id,omitempty"`
403
+ UID string `json:"uid,omitempty"`
404
+ Type string `json:"type,omitempty"`
405
+ Headline string `json:"headline,omitempty"`
406
+ Title string `json:"title,omitempty"`
407
+ LinkText string `json:"linkText,omitempty"`
408
+ Byline string `json:"byline,omitempty"`
409
+ Description string `json:"description,omitempty"`
410
+ Published string `json:"published,omitempty"`
411
+ LastModified string `json:"lastModified,omitempty"`
412
+ WebURL string `json:"webUrl,omitempty"`
413
+ APIURL string `json:"apiUrl,omitempty"`
414
+ Extensions map[string]any `json:"extensions,omitempty"`
415
+ }
416
+
417
+ // Team is the normalized core team or competitor shape.
418
+ type Team struct {
419
+ Ref string `json:"ref,omitempty"`
420
+ ID string `json:"id,omitempty"`
421
+ UID string `json:"uid,omitempty"`
422
+ Name string `json:"name,omitempty"`
423
+ ShortName string `json:"shortName,omitempty"`
424
+ Abbreviation string `json:"abbreviation,omitempty"`
425
+ ScoreSummary string `json:"scoreSummary,omitempty"`
426
+ Type string `json:"type,omitempty"`
427
+ HomeAway string `json:"homeAway,omitempty"`
428
+ Order int `json:"order,omitempty"`
429
+ Winner bool `json:"winner"`
430
+ ScoreRef string `json:"scoreRef,omitempty"`
431
+ RosterRef string `json:"rosterRef,omitempty"`
432
+ LeadersRef string `json:"leadersRef,omitempty"`
433
+ StatisticsRef string `json:"statisticsRef,omitempty"`
434
+ RecordRef string `json:"recordRef,omitempty"`
435
+ LinescoresRef string `json:"linescoresRef,omitempty"`
436
+ Extensions map[string]any `json:"extensions,omitempty"`
437
+ }
438
+
439
+ // TeamScope indicates whether a team resource comes from global team endpoints or match-scoped competitor endpoints.
440
+ type TeamScope string
441
+
442
+ const (
443
+ TeamScopeGlobal TeamScope = "global"
444
+ TeamScopeMatch TeamScope = "match"
445
+ )
446
+
447
+ // TeamRosterEntry is a normalized roster player entry with player-command bridge references.
448
+ type TeamRosterEntry struct {
449
+ PlayerID string `json:"playerId,omitempty"`
450
+ PlayerRef string `json:"playerRef,omitempty"`
451
+ DisplayName string `json:"displayName,omitempty"`
452
+ TeamID string `json:"teamId,omitempty"`
453
+ TeamRef string `json:"teamRef,omitempty"`
454
+ MatchID string `json:"matchId,omitempty"`
455
+ Scope TeamScope `json:"scope,omitempty"`
456
+ Captain bool `json:"captain"`
457
+ Starter bool `json:"starter"`
458
+ Active bool `json:"active"`
459
+ ActiveName string `json:"activeName,omitempty"`
460
+ PositionRef string `json:"positionRef,omitempty"`
461
+ LinescoresRef string `json:"linescoresRef,omitempty"`
462
+ StatisticsRef string `json:"statisticsRef,omitempty"`
463
+ Extensions map[string]any `json:"extensions,omitempty"`
464
+ }
465
+
466
+ // TeamScore is the normalized team score response.
467
+ type TeamScore struct {
468
+ Ref string `json:"ref,omitempty"`
469
+ TeamID string `json:"teamId,omitempty"`
470
+ MatchID string `json:"matchId,omitempty"`
471
+ Scope TeamScope `json:"scope,omitempty"`
472
+ DisplayValue string `json:"displayValue,omitempty"`
473
+ Value string `json:"value,omitempty"`
474
+ Place string `json:"place,omitempty"`
475
+ Source string `json:"source,omitempty"`
476
+ Winner bool `json:"winner"`
477
+ Extensions map[string]any `json:"extensions,omitempty"`
478
+ }
479
+
480
+ // TeamLeaders groups category-based leaderboards for one team.
481
+ type TeamLeaders struct {
482
+ Ref string `json:"ref,omitempty"`
483
+ TeamID string `json:"teamId,omitempty"`
484
+ TeamName string `json:"teamName,omitempty"`
485
+ MatchID string `json:"matchId,omitempty"`
486
+ Scope TeamScope `json:"scope,omitempty"`
487
+ Name string `json:"name,omitempty"`
488
+ Categories []TeamLeaderCategory `json:"categories,omitempty"`
489
+ Extensions map[string]any `json:"extensions,omitempty"`
490
+ }
491
+
492
+ // TeamLeaderCategory is one leaderboard category (for example runs or wickets).
493
+ type TeamLeaderCategory struct {
494
+ Name string `json:"name,omitempty"`
495
+ DisplayName string `json:"displayName,omitempty"`
496
+ ShortName string `json:"shortName,omitempty"`
497
+ Abbreviation string `json:"abbreviation,omitempty"`
498
+ Leaders []TeamLeaderEntry `json:"leaders,omitempty"`
499
+ Extensions map[string]any `json:"extensions,omitempty"`
500
+ }
501
+
502
+ // TeamLeaderEntry is one player row within a team leaderboard category.
503
+ type TeamLeaderEntry struct {
504
+ Order int `json:"order,omitempty"`
505
+ DisplayValue string `json:"displayValue,omitempty"`
506
+ Value string `json:"value,omitempty"`
507
+ AthleteID string `json:"athleteId,omitempty"`
508
+ AthleteName string `json:"athleteName,omitempty"`
509
+ AthleteRef string `json:"athleteRef,omitempty"`
510
+ TeamRef string `json:"teamRef,omitempty"`
511
+ StatisticsRef string `json:"statisticsRef,omitempty"`
512
+ Runs string `json:"runs,omitempty"`
513
+ Wickets string `json:"wickets,omitempty"`
514
+ Overs string `json:"overs,omitempty"`
515
+ Maidens string `json:"maidens,omitempty"`
516
+ EconomyRate string `json:"economyRate,omitempty"`
517
+ Balls string `json:"balls,omitempty"`
518
+ Fours string `json:"fours,omitempty"`
519
+ Sixes string `json:"sixes,omitempty"`
520
+ Extensions map[string]any `json:"extensions,omitempty"`
521
+ }
522
+
523
+ // League is the normalized core league shape.
524
+ type League struct {
525
+ Ref string `json:"ref,omitempty"`
526
+ ID string `json:"id,omitempty"`
527
+ UID string `json:"uid,omitempty"`
528
+ Name string `json:"name,omitempty"`
529
+ Slug string `json:"slug,omitempty"`
530
+ SeasonRef string `json:"seasonRef,omitempty"`
531
+ Extensions map[string]any `json:"extensions,omitempty"`
532
+ }
533
+
534
+ // Season is the normalized core season shape.
535
+ type Season struct {
536
+ Ref string `json:"ref,omitempty"`
537
+ ID string `json:"id,omitempty"`
538
+ LeagueID string `json:"leagueId,omitempty"`
539
+ Year int `json:"year,omitempty"`
540
+ Extensions map[string]any `json:"extensions,omitempty"`
541
+ }
542
+
543
+ // CalendarDay is the normalized league calendar day shape.
544
+ type CalendarDay struct {
545
+ Ref string `json:"ref,omitempty"`
546
+ LeagueID string `json:"leagueId,omitempty"`
547
+ Date string `json:"date,omitempty"`
548
+ DayType string `json:"dayType,omitempty"`
549
+ StartDate string `json:"startDate,omitempty"`
550
+ EndDate string `json:"endDate,omitempty"`
551
+ Sections []string `json:"sections,omitempty"`
552
+ Extensions map[string]any `json:"extensions,omitempty"`
553
+ }
554
+
555
+ // SeasonType is the normalized season type shape.
556
+ type SeasonType struct {
557
+ Ref string `json:"ref,omitempty"`
558
+ ID string `json:"id,omitempty"`
559
+ LeagueID string `json:"leagueId,omitempty"`
560
+ SeasonID string `json:"seasonId,omitempty"`
561
+ Name string `json:"name,omitempty"`
562
+ Abbreviation string `json:"abbreviation,omitempty"`
563
+ StartDate string `json:"startDate,omitempty"`
564
+ EndDate string `json:"endDate,omitempty"`
565
+ HasGroups bool `json:"hasGroups"`
566
+ HasStandings bool `json:"hasStandings"`
567
+ GroupsRef string `json:"groupsRef,omitempty"`
568
+ Extensions map[string]any `json:"extensions,omitempty"`
569
+ }
570
+
571
+ // SeasonGroup is the normalized season group shape.
572
+ type SeasonGroup struct {
573
+ Ref string `json:"ref,omitempty"`
574
+ ID string `json:"id,omitempty"`
575
+ LeagueID string `json:"leagueId,omitempty"`
576
+ SeasonID string `json:"seasonId,omitempty"`
577
+ TypeID string `json:"typeId,omitempty"`
578
+ Name string `json:"name,omitempty"`
579
+ Abbreviation string `json:"abbreviation,omitempty"`
580
+ StandingsRef string `json:"standingsRef,omitempty"`
581
+ Extensions map[string]any `json:"extensions,omitempty"`
582
+ }
583
+
584
+ // StandingsGroup is the normalized standings-group shape.
585
+ type StandingsGroup struct {
586
+ Ref string `json:"ref,omitempty"`
587
+ ID string `json:"id,omitempty"`
588
+ LeagueID string `json:"leagueId,omitempty"`
589
+ SeasonID string `json:"seasonId,omitempty"`
590
+ GroupID string `json:"groupId,omitempty"`
591
+ Entries []Team `json:"entries,omitempty"`
592
+ Extensions map[string]any `json:"extensions,omitempty"`
593
+ }
594
+
595
+ // Innings is the normalized innings shape.
596
+ type Innings struct {
597
+ Ref string `json:"ref,omitempty"`
598
+ ID string `json:"id,omitempty"`
599
+ LeagueID string `json:"leagueId,omitempty"`
600
+ EventID string `json:"eventId,omitempty"`
601
+ CompetitionID string `json:"competitionId,omitempty"`
602
+ MatchID string `json:"matchId,omitempty"`
603
+ TeamID string `json:"teamId,omitempty"`
604
+ TeamName string `json:"teamName,omitempty"`
605
+ InningsNumber int `json:"inningsNumber,omitempty"`
606
+ Period int `json:"period,omitempty"`
607
+ Runs int `json:"runs,omitempty"`
608
+ Wickets int `json:"wickets,omitempty"`
609
+ Overs float64 `json:"overs,omitempty"`
610
+ Score string `json:"score,omitempty"`
611
+ Description string `json:"description,omitempty"`
612
+ Target int `json:"target,omitempty"`
613
+ IsBatting bool `json:"isBatting"`
614
+ IsCurrent bool `json:"isCurrent"`
615
+ Fours int `json:"fours,omitempty"`
616
+ Sixes int `json:"sixes,omitempty"`
617
+ StatisticsRef string `json:"statisticsRef,omitempty"`
618
+ LeadersRef string `json:"leadersRef,omitempty"`
619
+ PartnershipsRef string `json:"partnershipsRef,omitempty"`
620
+ FallOfWicketRef string `json:"fallOfWicketRef,omitempty"`
621
+ OverTimeline []InningsOver `json:"overTimeline,omitempty"`
622
+ WicketTimeline []InningsWicket `json:"wicketTimeline,omitempty"`
623
+ Extensions map[string]any `json:"extensions,omitempty"`
624
+ }
625
+
626
+ // InningsOver is one over summary in an innings timeline.
627
+ type InningsOver struct {
628
+ Number int `json:"number,omitempty"`
629
+ Runs int `json:"runs,omitempty"`
630
+ WicketCount int `json:"wicketCount,omitempty"`
631
+ Wickets []InningsWicket `json:"wickets,omitempty"`
632
+ Extensions map[string]any `json:"extensions,omitempty"`
633
+ }
634
+
635
+ // InningsWicket is a normalized wicket event from period statistics or FOW resources.
636
+ type InningsWicket struct {
637
+ Number int `json:"number,omitempty"`
638
+ FOW string `json:"fow,omitempty"`
639
+ Over string `json:"over,omitempty"`
640
+ WicketOver float64 `json:"wicketOver,omitempty"`
641
+ FOWType string `json:"fowType,omitempty"`
642
+ Runs int `json:"runs,omitempty"`
643
+ RunsScored int `json:"runsScored,omitempty"`
644
+ BallsFaced int `json:"ballsFaced,omitempty"`
645
+ StrikeRate float64 `json:"strikeRate,omitempty"`
646
+ DismissalCard string `json:"dismissalCard,omitempty"`
647
+ ShortText string `json:"shortText,omitempty"`
648
+ DetailRef string `json:"detailRef,omitempty"`
649
+ DetailShortText string `json:"detailShortText,omitempty"`
650
+ DetailText string `json:"detailText,omitempty"`
651
+ AthleteRef string `json:"athleteRef,omitempty"`
652
+ Extensions map[string]any `json:"extensions,omitempty"`
653
+ }
654
+
655
+ // DeliveryEvent is the normalized ball-level event shape.
656
+ type DeliveryEvent struct {
657
+ Ref string `json:"ref,omitempty"`
658
+ ID string `json:"id,omitempty"`
659
+ LeagueID string `json:"leagueId,omitempty"`
660
+ EventID string `json:"eventId,omitempty"`
661
+ CompetitionID string `json:"competitionId,omitempty"`
662
+ MatchID string `json:"matchId,omitempty"`
663
+ TeamID string `json:"teamId,omitempty"`
664
+ Period int `json:"period,omitempty"`
665
+ PeriodText string `json:"periodText,omitempty"`
666
+ OverNumber int `json:"overNumber,omitempty"`
667
+ BallNumber int `json:"ballNumber,omitempty"`
668
+ ScoreValue int `json:"scoreValue,omitempty"`
669
+ ShortText string `json:"shortText,omitempty"`
670
+ Text string `json:"text,omitempty"`
671
+ HomeScore string `json:"homeScore,omitempty"`
672
+ AwayScore string `json:"awayScore,omitempty"`
673
+ BatsmanRef string `json:"batsmanRef,omitempty"`
674
+ BowlerRef string `json:"bowlerRef,omitempty"`
675
+ BatsmanPlayerID string `json:"batsmanPlayerId,omitempty"`
676
+ BowlerPlayerID string `json:"bowlerPlayerId,omitempty"`
677
+ FielderPlayerID string `json:"fielderPlayerId,omitempty"`
678
+ AthletePlayerIDs []string `json:"athletePlayerIds,omitempty"`
679
+ Involvement []string `json:"involvement,omitempty"`
680
+ PlayType map[string]any `json:"playType,omitempty"`
681
+ Dismissal map[string]any `json:"dismissal,omitempty"`
682
+ DismissalType string `json:"dismissalType,omitempty"`
683
+ DismissalName string `json:"dismissalName,omitempty"`
684
+ DismissalCard string `json:"dismissalCard,omitempty"`
685
+ DismissalText string `json:"dismissalText,omitempty"`
686
+ SpeedKPH float64 `json:"speedKPH,omitempty"`
687
+ XCoordinate *float64 `json:"xCoordinate"`
688
+ YCoordinate *float64 `json:"yCoordinate"`
689
+ BBBTimestamp int64 `json:"bbbTimestamp"`
690
+ CoordinateX *float64 `json:"coordinateX,omitempty"`
691
+ CoordinateY *float64 `json:"coordinateY,omitempty"`
692
+ Timestamp int64 `json:"timestamp,omitempty"`
693
+ Extensions map[string]any `json:"extensions,omitempty"`
694
+ }
695
+
696
+ // StatCategory is the normalized grouped statistics shape.
697
+ type StatCategory struct {
698
+ Name string `json:"name,omitempty"`
699
+ DisplayName string `json:"displayName,omitempty"`
700
+ ShortName string `json:"shortName,omitempty"`
701
+ Abbreviation string `json:"abbreviation,omitempty"`
702
+ Summary string `json:"summary,omitempty"`
703
+ Stats []StatValue `json:"stats,omitempty"`
704
+ Extensions map[string]any `json:"extensions,omitempty"`
705
+ }
706
+
707
+ // StatValue is a normalized statistic entry.
708
+ type StatValue struct {
709
+ Name string `json:"name,omitempty"`
710
+ DisplayName string `json:"displayName,omitempty"`
711
+ ShortName string `json:"shortName,omitempty"`
712
+ Description string `json:"description,omitempty"`
713
+ Abbreviation string `json:"abbreviation,omitempty"`
714
+ DisplayValue string `json:"displayValue,omitempty"`
715
+ Value any `json:"value,omitempty"`
716
+ Type string `json:"type,omitempty"`
717
+ Extensions map[string]any `json:"extensions,omitempty"`
718
+ }
719
+
720
+ // Partnership is the normalized partnership shape.
721
+ type Partnership struct {
722
+ Ref string `json:"ref,omitempty"`
723
+ ID string `json:"id,omitempty"`
724
+ MatchID string `json:"matchId,omitempty"`
725
+ TeamID string `json:"teamId,omitempty"`
726
+ TeamName string `json:"teamName,omitempty"`
727
+ InningsID string `json:"inningsId,omitempty"`
728
+ Period string `json:"period,omitempty"`
729
+ Order int `json:"order,omitempty"`
730
+ WicketNumber int `json:"wicketNumber,omitempty"`
731
+ WicketName string `json:"wicketName,omitempty"`
732
+ FOWType string `json:"fowType,omitempty"`
733
+ Overs float64 `json:"overs,omitempty"`
734
+ Runs int `json:"runs,omitempty"`
735
+ RunRate float64 `json:"runRate,omitempty"`
736
+ Start PartnershipSnapshot `json:"start,omitempty"`
737
+ End PartnershipSnapshot `json:"end,omitempty"`
738
+ Batsmen []PartnershipBatsman `json:"batsmen,omitempty"`
739
+ Extensions map[string]any `json:"extensions,omitempty"`
740
+ }
741
+
742
+ // PartnershipSnapshot captures start/end score markers for a partnership.
743
+ type PartnershipSnapshot struct {
744
+ Overs float64 `json:"overs,omitempty"`
745
+ Runs int `json:"runs,omitempty"`
746
+ Wickets int `json:"wickets,omitempty"`
747
+ }
748
+
749
+ // PartnershipBatsman captures an individual batter contribution in a partnership.
750
+ type PartnershipBatsman struct {
751
+ AthleteRef string `json:"athleteRef,omitempty"`
752
+ Balls int `json:"balls,omitempty"`
753
+ Runs int `json:"runs,omitempty"`
754
+ }
755
+
756
+ // FallOfWicket is the normalized wicket-fall shape.
757
+ type FallOfWicket struct {
758
+ Ref string `json:"ref,omitempty"`
759
+ ID string `json:"id,omitempty"`
760
+ MatchID string `json:"matchId,omitempty"`
761
+ TeamID string `json:"teamId,omitempty"`
762
+ TeamName string `json:"teamName,omitempty"`
763
+ InningsID string `json:"inningsId,omitempty"`
764
+ Period string `json:"period,omitempty"`
765
+ WicketNumber int `json:"wicketNumber,omitempty"`
766
+ WicketOver float64 `json:"wicketOver,omitempty"`
767
+ FOWType string `json:"fowType,omitempty"`
768
+ Runs int `json:"runs,omitempty"`
769
+ RunsScored int `json:"runsScored,omitempty"`
770
+ BallsFaced int `json:"ballsFaced,omitempty"`
771
+ AthleteRef string `json:"athleteRef,omitempty"`
772
+ Extensions map[string]any `json:"extensions,omitempty"`
773
+ }
774
+
775
+ // AnalysisScope captures the resolved analysis traversal scope.
776
+ type AnalysisScope struct {
777
+ Mode string `json:"mode"`
778
+ RequestedLeagueID string `json:"requestedLeagueId,omitempty"`
779
+ LeagueID string `json:"leagueId,omitempty"`
780
+ LeagueName string `json:"leagueName,omitempty"`
781
+ Seasons []string `json:"seasons,omitempty"`
782
+ MatchIDs []string `json:"matchIds,omitempty"`
783
+ MatchCount int `json:"matchCount"`
784
+ DateFrom string `json:"dateFrom,omitempty"`
785
+ DateTo string `json:"dateTo,omitempty"`
786
+ TypeQuery string `json:"type,omitempty"`
787
+ GroupQuery string `json:"group,omitempty"`
788
+ HydrationMetric HydrationMetrics `json:"hydrationMetrics,omitempty"`
789
+ }
790
+
791
+ // AnalysisFilters captures user-level row filters.
792
+ type AnalysisFilters struct {
793
+ TeamQuery string `json:"team,omitempty"`
794
+ PlayerQuery string `json:"player,omitempty"`
795
+ DismissalType string `json:"dismissalType,omitempty"`
796
+ Innings int `json:"innings,omitempty"`
797
+ Period int `json:"period,omitempty"`
798
+ }
799
+
800
+ // AnalysisRow is one ranked row in an analysis response.
801
+ type AnalysisRow struct {
802
+ Rank int `json:"rank"`
803
+ Key string `json:"key"`
804
+ Metric string `json:"metric,omitempty"`
805
+ Value float64 `json:"value"`
806
+ Count int `json:"count,omitempty"`
807
+ Matches int `json:"matches,omitempty"`
808
+ PlayerID string `json:"playerId,omitempty"`
809
+ PlayerName string `json:"playerName,omitempty"`
810
+ TeamID string `json:"teamId,omitempty"`
811
+ TeamName string `json:"teamName,omitempty"`
812
+ LeagueID string `json:"leagueId,omitempty"`
813
+ SeasonID string `json:"seasonId,omitempty"`
814
+ DismissalType string `json:"dismissalType,omitempty"`
815
+ InningsNumber int `json:"inningsNumber,omitempty"`
816
+ Period int `json:"period,omitempty"`
817
+ Extras map[string]any `json:"extras,omitempty"`
818
+ }
819
+
820
+ // AnalysisView is the stable agent-friendly analysis payload.
821
+ type AnalysisView struct {
822
+ Command string `json:"command"`
823
+ Metric string `json:"metric,omitempty"`
824
+ Scope AnalysisScope `json:"scope"`
825
+ GroupBy []string `json:"groupBy"`
826
+ Filters AnalysisFilters `json:"filters,omitempty"`
827
+ Rows []AnalysisRow `json:"rows"`
828
+ }
829
+
830
+ // NewDataResult creates a successful single-entity result.
831
+ func NewDataResult(kind EntityKind, data any) NormalizedResult {
832
+ return NormalizedResult{
833
+ Kind: kind,
834
+ Status: ResultStatusOK,
835
+ Data: data,
836
+ }
837
+ }
838
+
839
+ // NewListResult creates a list result and auto-normalizes empty state.
840
+ func NewListResult(kind EntityKind, items []any) NormalizedResult {
841
+ if len(items) == 0 {
842
+ return NormalizedResult{
843
+ Kind: kind,
844
+ Status: ResultStatusEmpty,
845
+ Message: fmt.Sprintf("no %s found", kindPlural(kind)),
846
+ Items: []any{},
847
+ }
848
+ }
849
+
850
+ return NormalizedResult{
851
+ Kind: kind,
852
+ Status: ResultStatusOK,
853
+ Items: items,
854
+ }
855
+ }
856
+
857
+ // NewPartialResult creates a partial-data result for single entity responses.
858
+ func NewPartialResult(kind EntityKind, data any, warnings ...string) NormalizedResult {
859
+ return NormalizedResult{
860
+ Kind: kind,
861
+ Status: ResultStatusPartial,
862
+ Data: data,
863
+ Warnings: compactWarnings(warnings),
864
+ Message: "partial data returned",
865
+ }
866
+ }
867
+
868
+ // NewPartialListResult creates a partial-data list result.
869
+ func NewPartialListResult(kind EntityKind, items []any, warnings ...string) NormalizedResult {
870
+ if len(items) == 0 {
871
+ result := NewListResult(kind, items)
872
+ result.Status = ResultStatusPartial
873
+ result.Warnings = compactWarnings(warnings)
874
+ if result.Message == "" {
875
+ result.Message = "partial data returned"
876
+ }
877
+ return result
878
+ }
879
+
880
+ return NormalizedResult{
881
+ Kind: kind,
882
+ Status: ResultStatusPartial,
883
+ Items: items,
884
+ Warnings: compactWarnings(warnings),
885
+ Message: "partial data returned",
886
+ }
887
+ }
888
+
889
+ // NewTransportErrorResult standardizes transport failure payloads.
890
+ func NewTransportErrorResult(kind EntityKind, requestedRef string, err error) NormalizedResult {
891
+ if err == nil {
892
+ err = errors.New("unknown transport error")
893
+ }
894
+
895
+ transportErr := &TransportError{Message: "transport error"}
896
+
897
+ var statusErr *HTTPStatusError
898
+ if errors.As(err, &statusErr) {
899
+ transportErr.StatusCode = statusErr.StatusCode
900
+ transportErr.URL = statusErr.URL
901
+ transportErr.Message = fmt.Sprintf("transport error: status %d", statusErr.StatusCode)
902
+ } else {
903
+ transportErr.Message = fmt.Sprintf("transport error: %v", err)
904
+ }
905
+
906
+ if requestedRef == "" {
907
+ requestedRef = transportErr.URL
908
+ }
909
+
910
+ return NormalizedResult{
911
+ Kind: kind,
912
+ Status: ResultStatusError,
913
+ RequestedRef: requestedRef,
914
+ Message: transportErr.Message,
915
+ Error: transportErr,
916
+ }
917
+ }
918
+
919
+ func kindPlural(kind EntityKind) string {
920
+ switch kind {
921
+ case EntityMatch:
922
+ return "matches"
923
+ case EntityMatchScorecard:
924
+ return "match scorecards"
925
+ case EntityMatchSituation:
926
+ return "match situations"
927
+ case EntityCompetition:
928
+ return "competitions"
929
+ case EntityCompOfficial:
930
+ return "competition officials"
931
+ case EntityCompBroadcast:
932
+ return "competition broadcasts"
933
+ case EntityCompTicket:
934
+ return "competition tickets"
935
+ case EntityCompOdds:
936
+ return "competition odds"
937
+ case EntityCompMetadata:
938
+ return "competition metadata views"
939
+ case EntityPlayer:
940
+ return "players"
941
+ case EntityPlayerStats:
942
+ return "player statistics"
943
+ case EntityPlayerMatch:
944
+ return "player match views"
945
+ case EntityPlayerInnings:
946
+ return "player innings"
947
+ case EntityPlayerDismissal:
948
+ return "player dismissals"
949
+ case EntityPlayerDelivery:
950
+ return "player deliveries"
951
+ case EntityNewsArticle:
952
+ return "news articles"
953
+ case EntityTeam:
954
+ return "teams"
955
+ case EntityTeamRoster:
956
+ return "team roster entries"
957
+ case EntityTeamScore:
958
+ return "team scores"
959
+ case EntityTeamLeaders:
960
+ return "team leaderboards"
961
+ case EntityTeamStatistics:
962
+ return "team statistics categories"
963
+ case EntityTeamRecords:
964
+ return "team record categories"
965
+ case EntityLeague:
966
+ return "leagues"
967
+ case EntitySeason:
968
+ return "seasons"
969
+ case EntityCalendarDay:
970
+ return "calendar days"
971
+ case EntitySeasonType:
972
+ return "season types"
973
+ case EntitySeasonGroup:
974
+ return "season groups"
975
+ case EntityStandingsGroup:
976
+ return "standings groups"
977
+ case EntityInnings:
978
+ return "innings"
979
+ case EntityDeliveryEvent:
980
+ return "delivery events"
981
+ case EntityStatCategory:
982
+ return "stat categories"
983
+ case EntityPartnership:
984
+ return "partnerships"
985
+ case EntityFallOfWicket:
986
+ return "fall-of-wicket entries"
987
+ case EntityAnalysisDismiss:
988
+ return "analysis dismissals"
989
+ case EntityAnalysisBowl:
990
+ return "analysis bowling rows"
991
+ case EntityAnalysisBat:
992
+ return "analysis batting rows"
993
+ case EntityAnalysisPart:
994
+ return "analysis partnerships"
995
+ default:
996
+ return "results"
997
+ }
998
+ }
999
+
1000
+ func compactWarnings(warnings []string) []string {
1001
+ out := make([]string, 0, len(warnings))
1002
+ for _, warning := range warnings {
1003
+ warning = strings.TrimSpace(warning)
1004
+ if warning == "" {
1005
+ continue
1006
+ }
1007
+ out = append(out, warning)
1008
+ }
1009
+ if len(out) == 0 {
1010
+ return nil
1011
+ }
1012
+ return out
1013
+ }
1014
+
1015
+ func refIDs(raw string) map[string]string {
1016
+ ids := map[string]string{}
1017
+ u, err := url.Parse(strings.TrimSpace(raw))
1018
+ if err != nil {
1019
+ return ids
1020
+ }
1021
+ segments := strings.Split(strings.Trim(path.Clean(u.Path), "/"), "/")
1022
+ for i := 0; i+1 < len(segments); i++ {
1023
+ key := segments[i]
1024
+ value := segments[i+1]
1025
+ switch key {
1026
+ case "leagues":
1027
+ ids["leagueId"] = value
1028
+ case "events":
1029
+ ids["eventId"] = value
1030
+ case "competitions":
1031
+ ids["competitionId"] = value
1032
+ case "competitors":
1033
+ ids["competitorId"] = value
1034
+ case "teams":
1035
+ ids["teamId"] = value
1036
+ case "athletes":
1037
+ ids["athleteId"] = value
1038
+ case "seasons":
1039
+ ids["seasonId"] = value
1040
+ case "types":
1041
+ ids["typeId"] = value
1042
+ case "groups":
1043
+ ids["groupId"] = value
1044
+ case "standings":
1045
+ ids["standingsId"] = value
1046
+ case "linescores":
1047
+ if _, ok := ids["inningsId"]; !ok {
1048
+ ids["inningsId"] = value
1049
+ } else {
1050
+ ids["periodId"] = value
1051
+ }
1052
+ case "details":
1053
+ ids["detailId"] = value
1054
+ case "partnerships":
1055
+ ids["partnershipId"] = value
1056
+ case "fow":
1057
+ ids["fowId"] = value
1058
+ }
1059
+ }
1060
+ return ids
1061
+ }
1062
+
1063
+ func parseYear(raw string) int {
1064
+ raw = strings.TrimSpace(raw)
1065
+ if raw == "" {
1066
+ return 0
1067
+ }
1068
+ year, err := strconv.Atoi(raw)
1069
+ if err != nil {
1070
+ return 0
1071
+ }
1072
+ if year < 1800 || year > 3000 {
1073
+ return 0
1074
+ }
1075
+ return year
1076
+ }
1077
+
1078
+ func parseInt(raw string) int {
1079
+ raw = strings.TrimSpace(raw)
1080
+ if raw == "" {
1081
+ return 0
1082
+ }
1083
+ value, err := strconv.Atoi(raw)
1084
+ if err != nil {
1085
+ return 0
1086
+ }
1087
+ return value
1088
+ }