fitzroy 1.4.2 → 1.6.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.
package/dist/cli.js CHANGED
@@ -509,6 +509,9 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
509
509
  roundNumber,
510
510
  team: teamName,
511
511
  competition: "AFLM",
512
+ date: null,
513
+ homeTeam: null,
514
+ awayTeam: null,
512
515
  playerId: `FW_${basic.player.replace(/\s+/g, "_")}`,
513
516
  givenName: firstName,
514
517
  surname,
@@ -548,6 +551,12 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
548
551
  totalPossessions: null,
549
552
  timeOnGroundPercentage: adv?.timeOnGroundPercentage ?? null,
550
553
  ratingPoints: null,
554
+ position: null,
555
+ goalEfficiency: null,
556
+ shotEfficiency: null,
557
+ interchangeCounts: null,
558
+ brownlowVotes: null,
559
+ supercoachScore: basic.supercoachPoints,
551
560
  dreamTeamPoints: basic.dreamTeamPoints,
552
561
  effectiveDisposals: adv?.effectiveDisposals ?? null,
553
562
  effectiveKicks: null,
@@ -631,6 +640,14 @@ var init_footywire_player_stats = __esm({
631
640
  });
632
641
 
633
642
  // src/transforms/match-results.ts
643
+ function toRoundCode(roundName) {
644
+ if (!roundName) return null;
645
+ const mapped = ROUND_CODE_MAP.get(roundName);
646
+ if (mapped) return mapped;
647
+ const m = ROUND_NUMBER_PATTERN.exec(roundName);
648
+ if (m?.[1]) return `R${m[1]}`;
649
+ return roundName;
650
+ }
634
651
  function inferRoundType(roundName) {
635
652
  return FINALS_PATTERN.test(roundName) ? "Finals" : "HomeAndAway";
636
653
  }
@@ -684,6 +701,7 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
684
701
  season,
685
702
  roundNumber: item.round?.roundNumber ?? 0,
686
703
  roundType: inferRoundType(item.round?.name ?? ""),
704
+ roundName: item.round?.name ?? null,
687
705
  date: new Date(item.match.utcStartTime),
688
706
  venue: item.venue?.name ? normaliseVenueName(item.venue.name) : "",
689
707
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
@@ -704,7 +722,10 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
704
722
  q3Away: findPeriod(awayScore?.periodScore, 3),
705
723
  q4Away: findPeriod(awayScore?.periodScore, 4),
706
724
  status: toMatchStatus(item.match.status),
707
- attendance: null,
725
+ attendance: item.attendance ?? null,
726
+ weatherTempCelsius: item.weather?.tempInCelsius ?? null,
727
+ weatherType: item.weather?.weatherType ?? null,
728
+ roundCode: toRoundCode(item.round?.name),
708
729
  venueState: item.venue?.state ?? null,
709
730
  venueTimezone: item.venue?.timeZone ?? null,
710
731
  homeRushedBehinds: homeScore?.rushedBehinds ?? null,
@@ -716,13 +737,21 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
716
737
  };
717
738
  });
718
739
  }
719
- var FINALS_PATTERN;
740
+ var FINALS_PATTERN, ROUND_CODE_MAP, ROUND_NUMBER_PATTERN;
720
741
  var init_match_results = __esm({
721
742
  "src/transforms/match-results.ts"() {
722
743
  "use strict";
723
744
  init_team_mapping();
724
745
  init_venue_mapping();
725
746
  FINALS_PATTERN = /final|elimination|qualifying|preliminary|semi|grand/i;
747
+ ROUND_CODE_MAP = /* @__PURE__ */ new Map([
748
+ ["Qualifying Final", "QF"],
749
+ ["Elimination Final", "EF"],
750
+ ["Semi Final", "SF"],
751
+ ["Preliminary Final", "PF"],
752
+ ["Grand Final", "GF"]
753
+ ]);
754
+ ROUND_NUMBER_PATTERN = /^Round\s+(\d+)$/i;
726
755
  }
727
756
  });
728
757
 
@@ -734,10 +763,12 @@ function parseMatchList(html, year) {
734
763
  let currentRound = 0;
735
764
  let lastHARound = 0;
736
765
  let currentRoundType = "HomeAndAway";
766
+ let currentRoundName = "";
737
767
  $("tr").each((_i, row) => {
738
768
  const roundHeader = $(row).find("td[colspan='7']");
739
769
  if (roundHeader.length > 0) {
740
770
  const text = roundHeader.text().trim();
771
+ currentRoundName = text;
741
772
  currentRoundType = inferRoundType(text);
742
773
  const roundMatch = /Round\s+(\d+)/i.exec(text);
743
774
  if (roundMatch?.[1]) {
@@ -780,6 +811,7 @@ function parseMatchList(html, year) {
780
811
  season: year,
781
812
  roundNumber: currentRound,
782
813
  roundType: currentRoundType,
814
+ roundName: currentRoundName || null,
783
815
  date,
784
816
  venue: normaliseVenueName(venue),
785
817
  homeTeam,
@@ -801,6 +833,9 @@ function parseMatchList(html, year) {
801
833
  q4Away: null,
802
834
  status: "Complete",
803
835
  attendance: attendance ? Number.parseInt(attendance, 10) || null : null,
836
+ weatherTempCelsius: null,
837
+ weatherType: null,
838
+ roundCode: toRoundCode(currentRoundName),
804
839
  venueState: null,
805
840
  venueTimezone: null,
806
841
  homeRushedBehinds: null,
@@ -1105,10 +1140,10 @@ var init_footywire = __esm({
1105
1140
  /**
1106
1141
  * Fetch match IDs from a season's match list page.
1107
1142
  *
1108
- * Extracts `mid=XXXX` values from score links.
1143
+ * Extracts `mid=XXXX` values from score links alongside round numbers.
1109
1144
  *
1110
1145
  * @param year - The season year.
1111
- * @returns Array of match ID strings.
1146
+ * @returns Array of match ID + round number pairs.
1112
1147
  */
1113
1148
  async fetchSeasonMatchIds(year) {
1114
1149
  const url = `${FOOTYWIRE_BASE}/ft_match_list?year=${year}`;
@@ -1116,15 +1151,33 @@ var init_footywire = __esm({
1116
1151
  if (!htmlResult.success) return htmlResult;
1117
1152
  try {
1118
1153
  const $ = cheerio2.load(htmlResult.data);
1119
- const ids = [];
1120
- $(".data:nth-child(5) a").each((_i, el) => {
1121
- const href = $(el).attr("href") ?? "";
1122
- const match = /mid=(\d+)/.exec(href);
1123
- if (match?.[1]) {
1124
- ids.push(match[1]);
1154
+ const entries = [];
1155
+ let currentRound = 0;
1156
+ let lastHARound = 0;
1157
+ $("tr").each((_i, row) => {
1158
+ const roundHeader = $(row).find("td[colspan='7']");
1159
+ if (roundHeader.length > 0) {
1160
+ const text = roundHeader.text().trim();
1161
+ const roundMatch = /Round\s+(\d+)/i.exec(text);
1162
+ if (roundMatch?.[1]) {
1163
+ currentRound = Number.parseInt(roundMatch[1], 10);
1164
+ if (inferRoundType(text) === "HomeAndAway") {
1165
+ lastHARound = currentRound;
1166
+ }
1167
+ } else if (inferRoundType(text) === "Finals") {
1168
+ currentRound = finalsRoundNumber(text, lastHARound);
1169
+ }
1170
+ return;
1171
+ }
1172
+ const scoreLink = $(row).find(".data:nth-child(5) a");
1173
+ if (scoreLink.length === 0) return;
1174
+ const href = scoreLink.attr("href") ?? "";
1175
+ const midMatch = /mid=(\d+)/.exec(href);
1176
+ if (midMatch?.[1]) {
1177
+ entries.push({ matchId: midMatch[1], roundNumber: currentRound });
1125
1178
  }
1126
1179
  });
1127
- return ok(ids);
1180
+ return ok(entries);
1128
1181
  } catch (cause) {
1129
1182
  return err(
1130
1183
  new ScrapeError(
@@ -1474,7 +1527,7 @@ var init_concurrency = __esm({
1474
1527
 
1475
1528
  // src/lib/validation.ts
1476
1529
  import { z } from "zod/v4";
1477
- var AflApiTokenSchema, CompetitionSchema, CompetitionListSchema, CompseasonSchema, CompseasonListSchema, RoundSchema, RoundListSchema, ScoreSchema, PeriodScoreSchema, TeamScoreSchema, CfsMatchTeamSchema, CfsMatchSchema, CfsScoreSchema, CfsVenueSchema, MatchItemSchema, MatchItemListSchema, CfsPlayerInnerSchema, statNum, PlayerGameStatsSchema, PlayerStatsItemSchema, PlayerStatsListSchema, RosterPlayerSchema, TeamPlayersSchema, MatchRosterSchema, TeamItemSchema, TeamListSchema, SquadPlayerInnerSchema, SquadPlayerItemSchema, SquadSchema, SquadListSchema, WinLossRecordSchema, LadderEntryRawSchema, LadderResponseSchema;
1530
+ var AflApiTokenSchema, CompetitionSchema, CompetitionListSchema, CompseasonSchema, CompseasonListSchema, RoundSchema, RoundListSchema, ScoreSchema, PeriodScoreSchema, TeamScoreSchema, CfsMatchTeamSchema, CfsMatchSchema, CfsScoreSchema, CfsVenueSchema, CfsWeatherSchema, MatchItemSchema, MatchItemListSchema, CfsPlayerInnerSchema, statNum, PlayerGameStatsSchema, PlayerStatsItemSchema, PlayerStatsListSchema, RosterPlayerSchema, TeamPlayersSchema, MatchRosterSchema, TeamItemSchema, TeamListSchema, SquadPlayerInnerSchema, SquadPlayerItemSchema, SquadSchema, SquadListSchema, WinLossRecordSchema, LadderEntryRawSchema, LadderResponseSchema;
1478
1531
  var init_validation = __esm({
1479
1532
  "src/lib/validation.ts"() {
1480
1533
  "use strict";
@@ -1558,6 +1611,10 @@ var init_validation = __esm({
1558
1611
  state: z.string().optional(),
1559
1612
  timeZone: z.string().optional()
1560
1613
  }).passthrough();
1614
+ CfsWeatherSchema = z.object({
1615
+ tempInCelsius: z.number().nullable().optional(),
1616
+ weatherType: z.string().nullable().optional()
1617
+ }).passthrough();
1561
1618
  MatchItemSchema = z.object({
1562
1619
  match: CfsMatchSchema,
1563
1620
  score: CfsScoreSchema.nullish(),
@@ -1566,7 +1623,9 @@ var init_validation = __esm({
1566
1623
  name: z.string(),
1567
1624
  roundId: z.string(),
1568
1625
  roundNumber: z.number()
1569
- }).passthrough().optional()
1626
+ }).passthrough().optional(),
1627
+ attendance: z.number().nullable().optional(),
1628
+ weather: CfsWeatherSchema.nullable().optional()
1570
1629
  }).passthrough();
1571
1630
  MatchItemListSchema = z.object({
1572
1631
  roundId: z.string().optional(),
@@ -1627,6 +1686,10 @@ var init_validation = __esm({
1627
1686
  metresGained: statNum,
1628
1687
  scoreInvolvements: statNum,
1629
1688
  ratingPoints: statNum,
1689
+ goalEfficiency: statNum,
1690
+ shotEfficiency: statNum,
1691
+ interchangeCounts: statNum,
1692
+ brownlowVotes: statNum,
1630
1693
  extendedStats: z.object({
1631
1694
  effectiveDisposals: statNum,
1632
1695
  effectiveKicks: statNum,
@@ -1671,8 +1734,8 @@ var init_validation = __esm({
1671
1734
  }).passthrough().nullable().optional()
1672
1735
  }).passthrough();
1673
1736
  PlayerStatsListSchema = z.object({
1674
- homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
1675
- awayTeamPlayerStats: z.array(PlayerStatsItemSchema)
1737
+ homeTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([]),
1738
+ awayTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([])
1676
1739
  }).passthrough();
1677
1740
  RosterPlayerSchema = z.object({
1678
1741
  player: z.object({
@@ -2293,6 +2356,7 @@ function transformSquiggleGamesToResults(games, season) {
2293
2356
  season,
2294
2357
  roundNumber: g.round,
2295
2358
  roundType: inferRoundType(g.roundname),
2359
+ roundName: g.roundname || null,
2296
2360
  date: new Date(g.unixtime * 1e3),
2297
2361
  venue: normaliseVenueName(g.venue),
2298
2362
  homeTeam: normaliseTeamName(g.hteam),
@@ -2314,6 +2378,9 @@ function transformSquiggleGamesToResults(games, season) {
2314
2378
  q4Away: null,
2315
2379
  status: "Complete",
2316
2380
  attendance: null,
2381
+ weatherTempCelsius: null,
2382
+ weatherType: null,
2383
+ roundCode: toRoundCode(g.roundname),
2317
2384
  venueState: null,
2318
2385
  venueTimezone: g.tz || null,
2319
2386
  homeRushedBehinds: null,
@@ -2481,6 +2548,9 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
2481
2548
  roundNumber,
2482
2549
  team: teamName,
2483
2550
  competition: "AFLM",
2551
+ date: null,
2552
+ homeTeam: null,
2553
+ awayTeam: null,
2484
2554
  playerId: `AT_${displayName.replace(/\s+/g, "_")}`,
2485
2555
  givenName,
2486
2556
  surname,
@@ -2520,6 +2590,12 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
2520
2590
  totalPossessions: null,
2521
2591
  timeOnGroundPercentage: safeInt(cells[24] ?? ""),
2522
2592
  ratingPoints: null,
2593
+ position: null,
2594
+ goalEfficiency: null,
2595
+ shotEfficiency: null,
2596
+ interchangeCounts: null,
2597
+ brownlowVotes: null,
2598
+ supercoachScore: null,
2523
2599
  dreamTeamPoints: null,
2524
2600
  effectiveDisposals: null,
2525
2601
  effectiveKicks: null,
@@ -2579,6 +2655,7 @@ function parseSeasonPage(html, year) {
2579
2655
  const results = [];
2580
2656
  let currentRound = 0;
2581
2657
  let currentRoundType = "HomeAndAway";
2658
+ let currentRoundName = "";
2582
2659
  let lastHARound = 0;
2583
2660
  let matchCounter = 0;
2584
2661
  $("table").each((_i, table) => {
@@ -2589,6 +2666,7 @@ function parseSeasonPage(html, year) {
2589
2666
  if (roundMatch?.[1] && border !== "1") {
2590
2667
  currentRound = Number.parseInt(roundMatch[1], 10);
2591
2668
  currentRoundType = inferRoundType(text);
2669
+ currentRoundName = text;
2592
2670
  if (currentRoundType === "HomeAndAway") {
2593
2671
  lastHARound = currentRound;
2594
2672
  }
@@ -2596,6 +2674,7 @@ function parseSeasonPage(html, year) {
2596
2674
  }
2597
2675
  if (border !== "1" && inferRoundType(text) === "Finals") {
2598
2676
  currentRoundType = "Finals";
2677
+ currentRoundName = text;
2599
2678
  currentRound = finalsRoundNumber(text, lastHARound);
2600
2679
  return;
2601
2680
  }
@@ -2626,6 +2705,7 @@ function parseSeasonPage(html, year) {
2626
2705
  season: year,
2627
2706
  roundNumber: currentRound,
2628
2707
  roundType: currentRoundType,
2708
+ roundName: currentRoundName || null,
2629
2709
  date,
2630
2710
  venue,
2631
2711
  homeTeam,
@@ -2647,6 +2727,9 @@ function parseSeasonPage(html, year) {
2647
2727
  q4Away: awayQuarters[3] ?? null,
2648
2728
  status: "Complete",
2649
2729
  attendance,
2730
+ weatherTempCelsius: null,
2731
+ weatherType: null,
2732
+ roundCode: toRoundCode(currentRoundName),
2650
2733
  venueState: null,
2651
2734
  venueTimezone: null,
2652
2735
  homeRushedBehinds: null,
@@ -3340,29 +3423,14 @@ async function resolveTeamId(client, teamName, competition) {
3340
3423
  }
3341
3424
  return ok(String(match.id));
3342
3425
  }
3343
- async function fetchFromAflApi(query) {
3344
- const client = new AflApiClient();
3345
- const competition = query.competition ?? "AFLM";
3346
- const season = query.season ?? resolveDefaultSeason(competition);
3347
- const [teamIdResult, seasonResult] = await Promise.all([
3348
- resolveTeamId(client, query.team, competition),
3349
- client.resolveCompSeason(competition, season)
3350
- ]);
3351
- if (!teamIdResult.success) return teamIdResult;
3352
- if (!seasonResult.success) return seasonResult;
3353
- const teamId = Number.parseInt(teamIdResult.data, 10);
3354
- if (Number.isNaN(teamId)) {
3355
- return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
3356
- }
3357
- const squadResult = await client.fetchSquad(teamId, seasonResult.data);
3358
- if (!squadResult.success) return squadResult;
3359
- const teamName = normaliseTeamName(squadResult.data.squad.team?.name ?? query.team);
3360
- const players = squadResult.data.squad.players.map((p) => ({
3426
+ function mapSquadToPlayerDetails(data, fallbackTeamName, competition) {
3427
+ const resolvedName = normaliseTeamName(data.squad.team?.name ?? fallbackTeamName);
3428
+ return data.squad.players.map((p) => ({
3361
3429
  playerId: p.player.providerId ?? String(p.player.id),
3362
3430
  givenName: p.player.firstName,
3363
3431
  surname: p.player.surname,
3364
3432
  displayName: `${p.player.firstName} ${p.player.surname}`,
3365
- team: teamName,
3433
+ team: resolvedName,
3366
3434
  jumperNumber: p.jumperNumber ?? null,
3367
3435
  position: p.position ?? null,
3368
3436
  dateOfBirth: p.player.dateOfBirth ?? null,
@@ -3378,35 +3446,83 @@ async function fetchFromAflApi(query) {
3378
3446
  source: "afl-api",
3379
3447
  competition
3380
3448
  }));
3381
- return ok(players);
3449
+ }
3450
+ async function fetchFromAflApi(query) {
3451
+ const client = new AflApiClient();
3452
+ const competition = query.competition ?? "AFLM";
3453
+ const season = query.season ?? resolveDefaultSeason(competition);
3454
+ const seasonResult = await client.resolveCompSeason(competition, season);
3455
+ if (!seasonResult.success) return seasonResult;
3456
+ if (query.team) {
3457
+ const teamIdResult = await resolveTeamId(client, query.team, competition);
3458
+ if (!teamIdResult.success) return teamIdResult;
3459
+ const teamId = Number.parseInt(teamIdResult.data, 10);
3460
+ if (Number.isNaN(teamId)) {
3461
+ return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
3462
+ }
3463
+ const squadResult = await client.fetchSquad(teamId, seasonResult.data);
3464
+ if (!squadResult.success) return squadResult;
3465
+ return ok(mapSquadToPlayerDetails(squadResult.data, query.team, competition));
3466
+ }
3467
+ const teamType = competition === "AFLW" ? "WOMEN" : "MEN";
3468
+ const teamsResult = await client.fetchTeams(teamType);
3469
+ if (!teamsResult.success) return teamsResult;
3470
+ const teamEntries = teamsResult.data.map((t) => ({
3471
+ id: Number.parseInt(String(t.id), 10),
3472
+ name: normaliseTeamName(t.name)
3473
+ }));
3474
+ const results = await batchedMap(
3475
+ teamEntries,
3476
+ (entry) => client.fetchSquad(entry.id, seasonResult.data)
3477
+ );
3478
+ const allPlayers = [];
3479
+ for (let i = 0; i < results.length; i++) {
3480
+ const result = results[i];
3481
+ const entry = teamEntries[i];
3482
+ if (result?.success && entry) {
3483
+ allPlayers.push(...mapSquadToPlayerDetails(result.data, entry.name, competition));
3484
+ }
3485
+ }
3486
+ return ok(allPlayers);
3487
+ }
3488
+ async function fetchAllTeamsFromScraper(fetchFn, source, competition) {
3489
+ const teamNames = [...AFL_SENIOR_TEAMS];
3490
+ const results = await batchedMap(teamNames, (name) => fetchFn(name));
3491
+ const allPlayers = [];
3492
+ for (const result of results) {
3493
+ if (result.success) {
3494
+ allPlayers.push(...result.data.map((p) => ({ ...p, source, competition })));
3495
+ }
3496
+ }
3497
+ return ok(allPlayers);
3382
3498
  }
3383
3499
  async function fetchFromFootyWire(query) {
3384
3500
  const competition = query.competition ?? "AFLM";
3385
3501
  if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3386
3502
  const client = new FootyWireClient();
3387
- const teamName = normaliseTeamName(query.team);
3388
- const result = await client.fetchPlayerList(teamName);
3389
- if (!result.success) return result;
3390
- const players = result.data.map((p) => ({
3391
- ...p,
3392
- source: "footywire",
3393
- competition
3394
- }));
3395
- return ok(players);
3503
+ if (query.team) {
3504
+ const teamName = normaliseTeamName(query.team);
3505
+ const result = await client.fetchPlayerList(teamName);
3506
+ if (!result.success) return result;
3507
+ return ok(result.data.map((p) => ({ ...p, source: "footywire", competition })));
3508
+ }
3509
+ return fetchAllTeamsFromScraper((name) => client.fetchPlayerList(name), "footywire", competition);
3396
3510
  }
3397
3511
  async function fetchFromAflTables(query) {
3398
3512
  const competition = query.competition ?? "AFLM";
3399
3513
  if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3400
3514
  const client = new AflTablesClient();
3401
- const teamName = normaliseTeamName(query.team);
3402
- const result = await client.fetchPlayerList(teamName);
3403
- if (!result.success) return result;
3404
- const players = result.data.map((p) => ({
3405
- ...p,
3406
- source: "afl-tables",
3515
+ if (query.team) {
3516
+ const teamName = normaliseTeamName(query.team);
3517
+ const result = await client.fetchPlayerList(teamName);
3518
+ if (!result.success) return result;
3519
+ return ok(result.data.map((p) => ({ ...p, source: "afl-tables", competition })));
3520
+ }
3521
+ return fetchAllTeamsFromScraper(
3522
+ (name) => client.fetchPlayerList(name),
3523
+ "afl-tables",
3407
3524
  competition
3408
- }));
3409
- return ok(players);
3525
+ );
3410
3526
  }
3411
3527
  async function fetchPlayerDetails(query) {
3412
3528
  switch (query.source) {
@@ -3428,6 +3544,7 @@ async function fetchPlayerDetails(query) {
3428
3544
  var init_player_details = __esm({
3429
3545
  "src/api/player-details.ts"() {
3430
3546
  "use strict";
3547
+ init_concurrency();
3431
3548
  init_date_utils();
3432
3549
  init_errors();
3433
3550
  init_result();
@@ -3442,18 +3559,21 @@ var init_player_details = __esm({
3442
3559
  function toNullable(value) {
3443
3560
  return value ?? null;
3444
3561
  }
3445
- function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
3562
+ function transformOne(item, ctx) {
3446
3563
  const inner = item.player.player.player;
3447
3564
  const stats = item.playerStats?.stats;
3448
3565
  const clearances = stats?.clearances;
3449
3566
  return {
3450
- matchId,
3451
- season,
3452
- roundNumber,
3567
+ matchId: ctx.matchId,
3568
+ season: ctx.season,
3569
+ roundNumber: ctx.roundNumber,
3453
3570
  team: normaliseTeamName(
3454
- teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3571
+ ctx.teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3455
3572
  ),
3456
- competition,
3573
+ competition: ctx.competition,
3574
+ date: ctx.date ?? null,
3575
+ homeTeam: ctx.homeTeam ?? null,
3576
+ awayTeam: ctx.awayTeam ?? null,
3457
3577
  playerId: inner.playerId,
3458
3578
  givenName: inner.playerName.givenName,
3459
3579
  surname: inner.playerName.surname,
@@ -3493,6 +3613,12 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
3493
3613
  totalPossessions: toNullable(stats?.totalPossessions),
3494
3614
  timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
3495
3615
  ratingPoints: toNullable(stats?.ratingPoints),
3616
+ position: item.player.player.position ?? null,
3617
+ goalEfficiency: toNullable(stats?.goalEfficiency),
3618
+ shotEfficiency: toNullable(stats?.shotEfficiency),
3619
+ interchangeCounts: toNullable(stats?.interchangeCounts),
3620
+ brownlowVotes: toNullable(stats?.brownlowVotes),
3621
+ supercoachScore: null,
3496
3622
  dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
3497
3623
  effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
3498
3624
  effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
@@ -3520,16 +3646,12 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
3520
3646
  kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
3521
3647
  ruckContests: toNullable(stats?.extendedStats?.ruckContests),
3522
3648
  scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
3523
- source
3649
+ source: ctx.source
3524
3650
  };
3525
3651
  }
3526
- function transformPlayerStats(data, matchId, season, roundNumber, competition, source = "afl-api", teamIdMap) {
3527
- const home = data.homeTeamPlayerStats.map(
3528
- (item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
3529
- );
3530
- const away = data.awayTeamPlayerStats.map(
3531
- (item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
3532
- );
3652
+ function transformPlayerStats(data, ctx) {
3653
+ const home = (data.homeTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
3654
+ const away = (data.awayTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
3533
3655
  return [...home, ...away];
3534
3656
  }
3535
3657
  var init_player_stats = __esm({
@@ -3558,24 +3680,19 @@ async function fetchPlayerStats(query) {
3558
3680
  teamIdMap2.set(match.awayTeamId, normaliseTeamName(match.awayTeam.name));
3559
3681
  }
3560
3682
  return ok(
3561
- transformPlayerStats(
3562
- statsResult.data,
3563
- query.matchId,
3564
- query.season,
3565
- query.round ?? 0,
3683
+ transformPlayerStats(statsResult.data, {
3684
+ matchId: query.matchId,
3685
+ season: query.season,
3686
+ roundNumber: query.round ?? 0,
3566
3687
  competition,
3567
- "afl-api",
3568
- teamIdMap2
3569
- )
3688
+ source: "afl-api",
3689
+ teamIdMap: teamIdMap2
3690
+ })
3570
3691
  );
3571
3692
  }
3572
3693
  const seasonResult = await client.resolveCompSeason(competition, query.season);
3573
3694
  if (!seasonResult.success) return seasonResult;
3574
- const roundNumber = query.round ?? 1;
3575
- const matchItemsResult = await client.fetchRoundMatchItemsByNumber(
3576
- seasonResult.data,
3577
- roundNumber
3578
- );
3695
+ const matchItemsResult = query.round != null ? await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round) : await client.fetchSeasonMatchItems(seasonResult.data);
3579
3696
  if (!matchItemsResult.success) return matchItemsResult;
3580
3697
  const teamIdMap = /* @__PURE__ */ new Map();
3581
3698
  for (const item of matchItemsResult.data) {
@@ -3594,15 +3711,17 @@ async function fetchPlayerStats(query) {
3594
3711
  const item = matchItemsResult.data[i];
3595
3712
  if (!item) continue;
3596
3713
  allStats.push(
3597
- ...transformPlayerStats(
3598
- statsResult.data,
3599
- item.match.matchId,
3600
- query.season,
3601
- roundNumber,
3714
+ ...transformPlayerStats(statsResult.data, {
3715
+ matchId: item.match.matchId,
3716
+ season: query.season,
3717
+ roundNumber: item.round?.roundNumber ?? query.round ?? 0,
3602
3718
  competition,
3603
- "afl-api",
3604
- teamIdMap
3605
- )
3719
+ source: "afl-api",
3720
+ teamIdMap,
3721
+ date: new Date(item.match.utcStartTime),
3722
+ homeTeam: normaliseTeamName(item.match.homeTeam.name),
3723
+ awayTeam: normaliseTeamName(item.match.awayTeam.name)
3724
+ })
3606
3725
  );
3607
3726
  }
3608
3727
  return ok(allStats);
@@ -3612,29 +3731,26 @@ async function fetchPlayerStats(query) {
3612
3731
  const fwClient = new FootyWireClient();
3613
3732
  const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
3614
3733
  if (!idsResult.success) return idsResult;
3615
- const matchIds = idsResult.data;
3616
- if (matchIds.length === 0) {
3734
+ const entries = query.round != null ? idsResult.data.filter((e) => e.roundNumber === query.round) : idsResult.data;
3735
+ if (entries.length === 0) {
3617
3736
  return ok([]);
3618
3737
  }
3619
3738
  const allStats = [];
3620
3739
  const batchSize = 5;
3621
- for (let i = 0; i < matchIds.length; i += batchSize) {
3622
- const batch = matchIds.slice(i, i + batchSize);
3740
+ for (let i = 0; i < entries.length; i += batchSize) {
3741
+ const batch = entries.slice(i, i + batchSize);
3623
3742
  const results = await Promise.all(
3624
- batch.map((mid) => fwClient.fetchMatchPlayerStats(mid, query.season, query.round ?? 0))
3743
+ batch.map((e) => fwClient.fetchMatchPlayerStats(e.matchId, query.season, e.roundNumber))
3625
3744
  );
3626
3745
  for (const result of results) {
3627
3746
  if (result.success) {
3628
3747
  allStats.push(...result.data);
3629
3748
  }
3630
3749
  }
3631
- if (i + batchSize < matchIds.length) {
3750
+ if (i + batchSize < entries.length) {
3632
3751
  await new Promise((resolve) => setTimeout(resolve, 500));
3633
3752
  }
3634
3753
  }
3635
- if (query.round != null) {
3636
- return ok(allStats.filter((s) => s.roundNumber === query.round));
3637
- }
3638
3754
  return ok(allStats);
3639
3755
  }
3640
3756
  case "afl-tables": {
@@ -5136,10 +5252,10 @@ var init_player_details2 = __esm({
5136
5252
  playerDetailsCommand = defineCommand9({
5137
5253
  meta: {
5138
5254
  name: "player-details",
5139
- description: "Fetch player biographical details for a team"
5255
+ description: "Fetch player biographical details (optionally filtered by team)"
5140
5256
  },
5141
5257
  args: {
5142
- ...REQUIRED_TEAM_FLAG,
5258
+ ...TEAM_FLAG,
5143
5259
  source: {
5144
5260
  type: "string",
5145
5261
  description: "Data source: afl-api, footywire, afl-tables",
@@ -5154,16 +5270,18 @@ var init_player_details2 = __esm({
5154
5270
  const competition = validateCompetition(args.competition);
5155
5271
  const format = validateFormat(args.format);
5156
5272
  const season = validateOptionalSeason(args.season) ?? resolveDefaultSeason(competition);
5157
- const team = await resolveTeamNameOrPrompt(args.team);
5273
+ const team = args.team ? await resolveTeamNameOrPrompt(args.team) : void 0;
5274
+ const label = team ? `Fetching player details for ${team}\u2026` : "Fetching player details for all teams\u2026";
5158
5275
  const result = await withSpinner(
5159
- "Fetching player details\u2026",
5276
+ label,
5160
5277
  () => fetchPlayerDetails({ source, team, season, competition })
5161
5278
  );
5162
5279
  if (!result.success) {
5163
5280
  throw result.error;
5164
5281
  }
5165
5282
  const data = result.data;
5166
- showSummary(`Loaded ${data.length} players for ${team} (${source})`);
5283
+ const summary = team ? `Loaded ${data.length} players for ${team} (${source})` : `Loaded ${data.length} players across all teams (${source})`;
5284
+ showSummary(summary);
5167
5285
  const formatOptions = {
5168
5286
  json: args.json,
5169
5287
  csv: args.csv,
@@ -5274,7 +5392,7 @@ resolveAliases();
5274
5392
  var main = defineCommand11({
5275
5393
  meta: {
5276
5394
  name: "fitzroy",
5277
- version: "1.4.2",
5395
+ version: "1.6.0",
5278
5396
  description: "TypeScript port of the fitzRoy R package \u2014 fetch AFL data from the command line"
5279
5397
  },
5280
5398
  subCommands: {