fitzroy 1.4.2 → 1.5.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,11 @@ 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
+ supercoachScore: basic.supercoachPoints,
551
559
  dreamTeamPoints: basic.dreamTeamPoints,
552
560
  effectiveDisposals: adv?.effectiveDisposals ?? null,
553
561
  effectiveKicks: null,
@@ -684,6 +692,7 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
684
692
  season,
685
693
  roundNumber: item.round?.roundNumber ?? 0,
686
694
  roundType: inferRoundType(item.round?.name ?? ""),
695
+ roundName: item.round?.name ?? null,
687
696
  date: new Date(item.match.utcStartTime),
688
697
  venue: item.venue?.name ? normaliseVenueName(item.venue.name) : "",
689
698
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
@@ -734,10 +743,12 @@ function parseMatchList(html, year) {
734
743
  let currentRound = 0;
735
744
  let lastHARound = 0;
736
745
  let currentRoundType = "HomeAndAway";
746
+ let currentRoundName = "";
737
747
  $("tr").each((_i, row) => {
738
748
  const roundHeader = $(row).find("td[colspan='7']");
739
749
  if (roundHeader.length > 0) {
740
750
  const text = roundHeader.text().trim();
751
+ currentRoundName = text;
741
752
  currentRoundType = inferRoundType(text);
742
753
  const roundMatch = /Round\s+(\d+)/i.exec(text);
743
754
  if (roundMatch?.[1]) {
@@ -780,6 +791,7 @@ function parseMatchList(html, year) {
780
791
  season: year,
781
792
  roundNumber: currentRound,
782
793
  roundType: currentRoundType,
794
+ roundName: currentRoundName || null,
783
795
  date,
784
796
  venue: normaliseVenueName(venue),
785
797
  homeTeam,
@@ -1105,10 +1117,10 @@ var init_footywire = __esm({
1105
1117
  /**
1106
1118
  * Fetch match IDs from a season's match list page.
1107
1119
  *
1108
- * Extracts `mid=XXXX` values from score links.
1120
+ * Extracts `mid=XXXX` values from score links alongside round numbers.
1109
1121
  *
1110
1122
  * @param year - The season year.
1111
- * @returns Array of match ID strings.
1123
+ * @returns Array of match ID + round number pairs.
1112
1124
  */
1113
1125
  async fetchSeasonMatchIds(year) {
1114
1126
  const url = `${FOOTYWIRE_BASE}/ft_match_list?year=${year}`;
@@ -1116,15 +1128,33 @@ var init_footywire = __esm({
1116
1128
  if (!htmlResult.success) return htmlResult;
1117
1129
  try {
1118
1130
  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]);
1131
+ const entries = [];
1132
+ let currentRound = 0;
1133
+ let lastHARound = 0;
1134
+ $("tr").each((_i, row) => {
1135
+ const roundHeader = $(row).find("td[colspan='7']");
1136
+ if (roundHeader.length > 0) {
1137
+ const text = roundHeader.text().trim();
1138
+ const roundMatch = /Round\s+(\d+)/i.exec(text);
1139
+ if (roundMatch?.[1]) {
1140
+ currentRound = Number.parseInt(roundMatch[1], 10);
1141
+ if (inferRoundType(text) === "HomeAndAway") {
1142
+ lastHARound = currentRound;
1143
+ }
1144
+ } else if (inferRoundType(text) === "Finals") {
1145
+ currentRound = finalsRoundNumber(text, lastHARound);
1146
+ }
1147
+ return;
1148
+ }
1149
+ const scoreLink = $(row).find(".data:nth-child(5) a");
1150
+ if (scoreLink.length === 0) return;
1151
+ const href = scoreLink.attr("href") ?? "";
1152
+ const midMatch = /mid=(\d+)/.exec(href);
1153
+ if (midMatch?.[1]) {
1154
+ entries.push({ matchId: midMatch[1], roundNumber: currentRound });
1125
1155
  }
1126
1156
  });
1127
- return ok(ids);
1157
+ return ok(entries);
1128
1158
  } catch (cause) {
1129
1159
  return err(
1130
1160
  new ScrapeError(
@@ -1627,6 +1657,9 @@ var init_validation = __esm({
1627
1657
  metresGained: statNum,
1628
1658
  scoreInvolvements: statNum,
1629
1659
  ratingPoints: statNum,
1660
+ goalEfficiency: statNum,
1661
+ shotEfficiency: statNum,
1662
+ interchangeCounts: statNum,
1630
1663
  extendedStats: z.object({
1631
1664
  effectiveDisposals: statNum,
1632
1665
  effectiveKicks: statNum,
@@ -1671,8 +1704,8 @@ var init_validation = __esm({
1671
1704
  }).passthrough().nullable().optional()
1672
1705
  }).passthrough();
1673
1706
  PlayerStatsListSchema = z.object({
1674
- homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
1675
- awayTeamPlayerStats: z.array(PlayerStatsItemSchema)
1707
+ homeTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([]),
1708
+ awayTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([])
1676
1709
  }).passthrough();
1677
1710
  RosterPlayerSchema = z.object({
1678
1711
  player: z.object({
@@ -2293,6 +2326,7 @@ function transformSquiggleGamesToResults(games, season) {
2293
2326
  season,
2294
2327
  roundNumber: g.round,
2295
2328
  roundType: inferRoundType(g.roundname),
2329
+ roundName: g.roundname || null,
2296
2330
  date: new Date(g.unixtime * 1e3),
2297
2331
  venue: normaliseVenueName(g.venue),
2298
2332
  homeTeam: normaliseTeamName(g.hteam),
@@ -2481,6 +2515,9 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
2481
2515
  roundNumber,
2482
2516
  team: teamName,
2483
2517
  competition: "AFLM",
2518
+ date: null,
2519
+ homeTeam: null,
2520
+ awayTeam: null,
2484
2521
  playerId: `AT_${displayName.replace(/\s+/g, "_")}`,
2485
2522
  givenName,
2486
2523
  surname,
@@ -2520,6 +2557,11 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
2520
2557
  totalPossessions: null,
2521
2558
  timeOnGroundPercentage: safeInt(cells[24] ?? ""),
2522
2559
  ratingPoints: null,
2560
+ position: null,
2561
+ goalEfficiency: null,
2562
+ shotEfficiency: null,
2563
+ interchangeCounts: null,
2564
+ supercoachScore: null,
2523
2565
  dreamTeamPoints: null,
2524
2566
  effectiveDisposals: null,
2525
2567
  effectiveKicks: null,
@@ -2579,6 +2621,7 @@ function parseSeasonPage(html, year) {
2579
2621
  const results = [];
2580
2622
  let currentRound = 0;
2581
2623
  let currentRoundType = "HomeAndAway";
2624
+ let currentRoundName = "";
2582
2625
  let lastHARound = 0;
2583
2626
  let matchCounter = 0;
2584
2627
  $("table").each((_i, table) => {
@@ -2589,6 +2632,7 @@ function parseSeasonPage(html, year) {
2589
2632
  if (roundMatch?.[1] && border !== "1") {
2590
2633
  currentRound = Number.parseInt(roundMatch[1], 10);
2591
2634
  currentRoundType = inferRoundType(text);
2635
+ currentRoundName = text;
2592
2636
  if (currentRoundType === "HomeAndAway") {
2593
2637
  lastHARound = currentRound;
2594
2638
  }
@@ -2596,6 +2640,7 @@ function parseSeasonPage(html, year) {
2596
2640
  }
2597
2641
  if (border !== "1" && inferRoundType(text) === "Finals") {
2598
2642
  currentRoundType = "Finals";
2643
+ currentRoundName = text;
2599
2644
  currentRound = finalsRoundNumber(text, lastHARound);
2600
2645
  return;
2601
2646
  }
@@ -2626,6 +2671,7 @@ function parseSeasonPage(html, year) {
2626
2671
  season: year,
2627
2672
  roundNumber: currentRound,
2628
2673
  roundType: currentRoundType,
2674
+ roundName: currentRoundName || null,
2629
2675
  date,
2630
2676
  venue,
2631
2677
  homeTeam,
@@ -3340,29 +3386,14 @@ async function resolveTeamId(client, teamName, competition) {
3340
3386
  }
3341
3387
  return ok(String(match.id));
3342
3388
  }
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) => ({
3389
+ function mapSquadToPlayerDetails(data, fallbackTeamName, competition) {
3390
+ const resolvedName = normaliseTeamName(data.squad.team?.name ?? fallbackTeamName);
3391
+ return data.squad.players.map((p) => ({
3361
3392
  playerId: p.player.providerId ?? String(p.player.id),
3362
3393
  givenName: p.player.firstName,
3363
3394
  surname: p.player.surname,
3364
3395
  displayName: `${p.player.firstName} ${p.player.surname}`,
3365
- team: teamName,
3396
+ team: resolvedName,
3366
3397
  jumperNumber: p.jumperNumber ?? null,
3367
3398
  position: p.position ?? null,
3368
3399
  dateOfBirth: p.player.dateOfBirth ?? null,
@@ -3378,35 +3409,83 @@ async function fetchFromAflApi(query) {
3378
3409
  source: "afl-api",
3379
3410
  competition
3380
3411
  }));
3381
- return ok(players);
3412
+ }
3413
+ async function fetchFromAflApi(query) {
3414
+ const client = new AflApiClient();
3415
+ const competition = query.competition ?? "AFLM";
3416
+ const season = query.season ?? resolveDefaultSeason(competition);
3417
+ const seasonResult = await client.resolveCompSeason(competition, season);
3418
+ if (!seasonResult.success) return seasonResult;
3419
+ if (query.team) {
3420
+ const teamIdResult = await resolveTeamId(client, query.team, competition);
3421
+ if (!teamIdResult.success) return teamIdResult;
3422
+ const teamId = Number.parseInt(teamIdResult.data, 10);
3423
+ if (Number.isNaN(teamId)) {
3424
+ return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
3425
+ }
3426
+ const squadResult = await client.fetchSquad(teamId, seasonResult.data);
3427
+ if (!squadResult.success) return squadResult;
3428
+ return ok(mapSquadToPlayerDetails(squadResult.data, query.team, competition));
3429
+ }
3430
+ const teamType = competition === "AFLW" ? "WOMEN" : "MEN";
3431
+ const teamsResult = await client.fetchTeams(teamType);
3432
+ if (!teamsResult.success) return teamsResult;
3433
+ const teamEntries = teamsResult.data.map((t) => ({
3434
+ id: Number.parseInt(String(t.id), 10),
3435
+ name: normaliseTeamName(t.name)
3436
+ }));
3437
+ const results = await batchedMap(
3438
+ teamEntries,
3439
+ (entry) => client.fetchSquad(entry.id, seasonResult.data)
3440
+ );
3441
+ const allPlayers = [];
3442
+ for (let i = 0; i < results.length; i++) {
3443
+ const result = results[i];
3444
+ const entry = teamEntries[i];
3445
+ if (result?.success && entry) {
3446
+ allPlayers.push(...mapSquadToPlayerDetails(result.data, entry.name, competition));
3447
+ }
3448
+ }
3449
+ return ok(allPlayers);
3450
+ }
3451
+ async function fetchAllTeamsFromScraper(fetchFn, source, competition) {
3452
+ const teamNames = [...AFL_SENIOR_TEAMS];
3453
+ const results = await batchedMap(teamNames, (name) => fetchFn(name));
3454
+ const allPlayers = [];
3455
+ for (const result of results) {
3456
+ if (result.success) {
3457
+ allPlayers.push(...result.data.map((p) => ({ ...p, source, competition })));
3458
+ }
3459
+ }
3460
+ return ok(allPlayers);
3382
3461
  }
3383
3462
  async function fetchFromFootyWire(query) {
3384
3463
  const competition = query.competition ?? "AFLM";
3385
3464
  if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3386
3465
  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);
3466
+ if (query.team) {
3467
+ const teamName = normaliseTeamName(query.team);
3468
+ const result = await client.fetchPlayerList(teamName);
3469
+ if (!result.success) return result;
3470
+ return ok(result.data.map((p) => ({ ...p, source: "footywire", competition })));
3471
+ }
3472
+ return fetchAllTeamsFromScraper((name) => client.fetchPlayerList(name), "footywire", competition);
3396
3473
  }
3397
3474
  async function fetchFromAflTables(query) {
3398
3475
  const competition = query.competition ?? "AFLM";
3399
3476
  if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3400
3477
  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",
3478
+ if (query.team) {
3479
+ const teamName = normaliseTeamName(query.team);
3480
+ const result = await client.fetchPlayerList(teamName);
3481
+ if (!result.success) return result;
3482
+ return ok(result.data.map((p) => ({ ...p, source: "afl-tables", competition })));
3483
+ }
3484
+ return fetchAllTeamsFromScraper(
3485
+ (name) => client.fetchPlayerList(name),
3486
+ "afl-tables",
3407
3487
  competition
3408
- }));
3409
- return ok(players);
3488
+ );
3410
3489
  }
3411
3490
  async function fetchPlayerDetails(query) {
3412
3491
  switch (query.source) {
@@ -3428,6 +3507,7 @@ async function fetchPlayerDetails(query) {
3428
3507
  var init_player_details = __esm({
3429
3508
  "src/api/player-details.ts"() {
3430
3509
  "use strict";
3510
+ init_concurrency();
3431
3511
  init_date_utils();
3432
3512
  init_errors();
3433
3513
  init_result();
@@ -3442,18 +3522,21 @@ var init_player_details = __esm({
3442
3522
  function toNullable(value) {
3443
3523
  return value ?? null;
3444
3524
  }
3445
- function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
3525
+ function transformOne(item, ctx) {
3446
3526
  const inner = item.player.player.player;
3447
3527
  const stats = item.playerStats?.stats;
3448
3528
  const clearances = stats?.clearances;
3449
3529
  return {
3450
- matchId,
3451
- season,
3452
- roundNumber,
3530
+ matchId: ctx.matchId,
3531
+ season: ctx.season,
3532
+ roundNumber: ctx.roundNumber,
3453
3533
  team: normaliseTeamName(
3454
- teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3534
+ ctx.teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3455
3535
  ),
3456
- competition,
3536
+ competition: ctx.competition,
3537
+ date: ctx.date ?? null,
3538
+ homeTeam: ctx.homeTeam ?? null,
3539
+ awayTeam: ctx.awayTeam ?? null,
3457
3540
  playerId: inner.playerId,
3458
3541
  givenName: inner.playerName.givenName,
3459
3542
  surname: inner.playerName.surname,
@@ -3493,6 +3576,11 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
3493
3576
  totalPossessions: toNullable(stats?.totalPossessions),
3494
3577
  timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
3495
3578
  ratingPoints: toNullable(stats?.ratingPoints),
3579
+ position: item.player.player.position ?? null,
3580
+ goalEfficiency: toNullable(stats?.goalEfficiency),
3581
+ shotEfficiency: toNullable(stats?.shotEfficiency),
3582
+ interchangeCounts: toNullable(stats?.interchangeCounts),
3583
+ supercoachScore: null,
3496
3584
  dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
3497
3585
  effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
3498
3586
  effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
@@ -3520,16 +3608,12 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
3520
3608
  kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
3521
3609
  ruckContests: toNullable(stats?.extendedStats?.ruckContests),
3522
3610
  scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
3523
- source
3611
+ source: ctx.source
3524
3612
  };
3525
3613
  }
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
- );
3614
+ function transformPlayerStats(data, ctx) {
3615
+ const home = (data.homeTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
3616
+ const away = (data.awayTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
3533
3617
  return [...home, ...away];
3534
3618
  }
3535
3619
  var init_player_stats = __esm({
@@ -3558,24 +3642,19 @@ async function fetchPlayerStats(query) {
3558
3642
  teamIdMap2.set(match.awayTeamId, normaliseTeamName(match.awayTeam.name));
3559
3643
  }
3560
3644
  return ok(
3561
- transformPlayerStats(
3562
- statsResult.data,
3563
- query.matchId,
3564
- query.season,
3565
- query.round ?? 0,
3645
+ transformPlayerStats(statsResult.data, {
3646
+ matchId: query.matchId,
3647
+ season: query.season,
3648
+ roundNumber: query.round ?? 0,
3566
3649
  competition,
3567
- "afl-api",
3568
- teamIdMap2
3569
- )
3650
+ source: "afl-api",
3651
+ teamIdMap: teamIdMap2
3652
+ })
3570
3653
  );
3571
3654
  }
3572
3655
  const seasonResult = await client.resolveCompSeason(competition, query.season);
3573
3656
  if (!seasonResult.success) return seasonResult;
3574
- const roundNumber = query.round ?? 1;
3575
- const matchItemsResult = await client.fetchRoundMatchItemsByNumber(
3576
- seasonResult.data,
3577
- roundNumber
3578
- );
3657
+ const matchItemsResult = query.round != null ? await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round) : await client.fetchSeasonMatchItems(seasonResult.data);
3579
3658
  if (!matchItemsResult.success) return matchItemsResult;
3580
3659
  const teamIdMap = /* @__PURE__ */ new Map();
3581
3660
  for (const item of matchItemsResult.data) {
@@ -3594,15 +3673,17 @@ async function fetchPlayerStats(query) {
3594
3673
  const item = matchItemsResult.data[i];
3595
3674
  if (!item) continue;
3596
3675
  allStats.push(
3597
- ...transformPlayerStats(
3598
- statsResult.data,
3599
- item.match.matchId,
3600
- query.season,
3601
- roundNumber,
3676
+ ...transformPlayerStats(statsResult.data, {
3677
+ matchId: item.match.matchId,
3678
+ season: query.season,
3679
+ roundNumber: item.round?.roundNumber ?? query.round ?? 0,
3602
3680
  competition,
3603
- "afl-api",
3604
- teamIdMap
3605
- )
3681
+ source: "afl-api",
3682
+ teamIdMap,
3683
+ date: new Date(item.match.utcStartTime),
3684
+ homeTeam: normaliseTeamName(item.match.homeTeam.name),
3685
+ awayTeam: normaliseTeamName(item.match.awayTeam.name)
3686
+ })
3606
3687
  );
3607
3688
  }
3608
3689
  return ok(allStats);
@@ -3612,29 +3693,26 @@ async function fetchPlayerStats(query) {
3612
3693
  const fwClient = new FootyWireClient();
3613
3694
  const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
3614
3695
  if (!idsResult.success) return idsResult;
3615
- const matchIds = idsResult.data;
3616
- if (matchIds.length === 0) {
3696
+ const entries = query.round != null ? idsResult.data.filter((e) => e.roundNumber === query.round) : idsResult.data;
3697
+ if (entries.length === 0) {
3617
3698
  return ok([]);
3618
3699
  }
3619
3700
  const allStats = [];
3620
3701
  const batchSize = 5;
3621
- for (let i = 0; i < matchIds.length; i += batchSize) {
3622
- const batch = matchIds.slice(i, i + batchSize);
3702
+ for (let i = 0; i < entries.length; i += batchSize) {
3703
+ const batch = entries.slice(i, i + batchSize);
3623
3704
  const results = await Promise.all(
3624
- batch.map((mid) => fwClient.fetchMatchPlayerStats(mid, query.season, query.round ?? 0))
3705
+ batch.map((e) => fwClient.fetchMatchPlayerStats(e.matchId, query.season, e.roundNumber))
3625
3706
  );
3626
3707
  for (const result of results) {
3627
3708
  if (result.success) {
3628
3709
  allStats.push(...result.data);
3629
3710
  }
3630
3711
  }
3631
- if (i + batchSize < matchIds.length) {
3712
+ if (i + batchSize < entries.length) {
3632
3713
  await new Promise((resolve) => setTimeout(resolve, 500));
3633
3714
  }
3634
3715
  }
3635
- if (query.round != null) {
3636
- return ok(allStats.filter((s) => s.roundNumber === query.round));
3637
- }
3638
3716
  return ok(allStats);
3639
3717
  }
3640
3718
  case "afl-tables": {
@@ -5136,10 +5214,10 @@ var init_player_details2 = __esm({
5136
5214
  playerDetailsCommand = defineCommand9({
5137
5215
  meta: {
5138
5216
  name: "player-details",
5139
- description: "Fetch player biographical details for a team"
5217
+ description: "Fetch player biographical details (optionally filtered by team)"
5140
5218
  },
5141
5219
  args: {
5142
- ...REQUIRED_TEAM_FLAG,
5220
+ ...TEAM_FLAG,
5143
5221
  source: {
5144
5222
  type: "string",
5145
5223
  description: "Data source: afl-api, footywire, afl-tables",
@@ -5154,16 +5232,18 @@ var init_player_details2 = __esm({
5154
5232
  const competition = validateCompetition(args.competition);
5155
5233
  const format = validateFormat(args.format);
5156
5234
  const season = validateOptionalSeason(args.season) ?? resolveDefaultSeason(competition);
5157
- const team = await resolveTeamNameOrPrompt(args.team);
5235
+ const team = args.team ? await resolveTeamNameOrPrompt(args.team) : void 0;
5236
+ const label = team ? `Fetching player details for ${team}\u2026` : "Fetching player details for all teams\u2026";
5158
5237
  const result = await withSpinner(
5159
- "Fetching player details\u2026",
5238
+ label,
5160
5239
  () => fetchPlayerDetails({ source, team, season, competition })
5161
5240
  );
5162
5241
  if (!result.success) {
5163
5242
  throw result.error;
5164
5243
  }
5165
5244
  const data = result.data;
5166
- showSummary(`Loaded ${data.length} players for ${team} (${source})`);
5245
+ const summary = team ? `Loaded ${data.length} players for ${team} (${source})` : `Loaded ${data.length} players across all teams (${source})`;
5246
+ showSummary(summary);
5167
5247
  const formatOptions = {
5168
5248
  json: args.json,
5169
5249
  csv: args.csv,
@@ -5274,7 +5354,7 @@ resolveAliases();
5274
5354
  var main = defineCommand11({
5275
5355
  meta: {
5276
5356
  name: "fitzroy",
5277
- version: "1.4.2",
5357
+ version: "1.5.0",
5278
5358
  description: "TypeScript port of the fitzRoy R package \u2014 fetch AFL data from the command line"
5279
5359
  },
5280
5360
  subCommands: {
package/dist/index.d.ts CHANGED
@@ -56,6 +56,8 @@ interface MatchResult {
56
56
  readonly season: number;
57
57
  readonly roundNumber: number;
58
58
  readonly roundType: RoundType;
59
+ /** Human-readable round name (e.g. "Round 1", "Qualifying Final"). Null for scraped sources. */
60
+ readonly roundName: string | null;
59
61
  readonly date: Date;
60
62
  readonly venue: string;
61
63
  readonly homeTeam: string;
@@ -104,6 +106,10 @@ interface PlayerStats {
104
106
  readonly roundNumber: number;
105
107
  readonly team: string;
106
108
  readonly competition: CompetitionCode;
109
+ /** Match context for cross-source joins (null when unavailable). */
110
+ readonly date: Date | null;
111
+ readonly homeTeam: string | null;
112
+ readonly awayTeam: string | null;
107
113
  /** Player identification. */
108
114
  readonly playerId: string;
109
115
  readonly givenName: string;
@@ -149,7 +155,14 @@ interface PlayerStats {
149
155
  readonly totalPossessions: number | null;
150
156
  readonly timeOnGroundPercentage: number | null;
151
157
  readonly ratingPoints: number | null;
158
+ /** Position played in this match (e.g. "INT", "MIDFIELD"). Null when unavailable. */
159
+ readonly position: string | null;
160
+ /** Efficiency stats. */
161
+ readonly goalEfficiency: number | null;
162
+ readonly shotEfficiency: number | null;
163
+ readonly interchangeCounts: number | null;
152
164
  /** Fantasy. */
165
+ readonly supercoachScore: number | null;
153
166
  readonly dreamTeamPoints: number | null;
154
167
  /** Extended stats. */
155
168
  readonly effectiveDisposals: number | null;
@@ -297,7 +310,8 @@ interface PlayerDetails {
297
310
  /** Query parameters for fetching player details. */
298
311
  interface PlayerDetailsQuery {
299
312
  readonly source: DataSource;
300
- readonly team: string;
313
+ /** Team name. When omitted, returns details for all teams. */
314
+ readonly team?: string | undefined;
301
315
  readonly season?: number | undefined;
302
316
  readonly current?: boolean | undefined;
303
317
  readonly competition?: CompetitionCode | undefined;
@@ -505,8 +519,9 @@ declare function fetchMatchResults(query: SeasonRoundQuery): Promise<Result<Matc
505
519
  * Fetch player biographical details (DOB, height, draft info, etc.).
506
520
  *
507
521
  * Dispatches to the appropriate data source based on `query.source`.
522
+ * When `query.team` is omitted, returns details for all teams.
508
523
  *
509
- * @param query - Team name, source, and optional season/competition filters.
524
+ * @param query - Source, optional team name, and optional season/competition filters.
510
525
  * @returns Array of player details.
511
526
  *
512
527
  * @example
@@ -1209,6 +1224,9 @@ declare const PlayerGameStatsSchema: z2.ZodObject<{
1209
1224
  metresGained: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1210
1225
  scoreInvolvements: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1211
1226
  ratingPoints: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1227
+ goalEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1228
+ shotEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1229
+ interchangeCounts: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1212
1230
  extendedStats: z2.ZodOptional<z2.ZodNullable<z2.ZodObject<{
1213
1231
  effectiveDisposals: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1214
1232
  effectiveKicks: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
@@ -1294,6 +1312,9 @@ declare const PlayerStatsItemSchema: z2.ZodObject<{
1294
1312
  metresGained: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1295
1313
  scoreInvolvements: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1296
1314
  ratingPoints: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1315
+ goalEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1316
+ shotEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1317
+ interchangeCounts: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1297
1318
  extendedStats: z2.ZodOptional<z2.ZodNullable<z2.ZodObject<{
1298
1319
  effectiveDisposals: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1299
1320
  effectiveKicks: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
@@ -1328,7 +1349,7 @@ declare const PlayerStatsItemSchema: z2.ZodObject<{
1328
1349
  }, z2.core.$loose>;
1329
1350
  /** Schema for the player stats response. */
1330
1351
  declare const PlayerStatsListSchema: z2.ZodObject<{
1331
- homeTeamPlayerStats: z2.ZodArray<z2.ZodObject<{
1352
+ homeTeamPlayerStats: z2.ZodDefault<z2.ZodNullable<z2.ZodArray<z2.ZodObject<{
1332
1353
  player: z2.ZodObject<{
1333
1354
  player: z2.ZodObject<{
1334
1355
  position: z2.ZodOptional<z2.ZodString>;
@@ -1383,6 +1404,9 @@ declare const PlayerStatsListSchema: z2.ZodObject<{
1383
1404
  metresGained: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1384
1405
  scoreInvolvements: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1385
1406
  ratingPoints: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1407
+ goalEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1408
+ shotEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1409
+ interchangeCounts: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1386
1410
  extendedStats: z2.ZodOptional<z2.ZodNullable<z2.ZodObject<{
1387
1411
  effectiveDisposals: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1388
1412
  effectiveKicks: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
@@ -1414,8 +1438,8 @@ declare const PlayerStatsListSchema: z2.ZodObject<{
1414
1438
  }, z2.core.$loose>;
1415
1439
  timeOnGroundPercentage: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1416
1440
  }, z2.core.$loose>>>;
1417
- }, z2.core.$loose>>;
1418
- awayTeamPlayerStats: z2.ZodArray<z2.ZodObject<{
1441
+ }, z2.core.$loose>>>>;
1442
+ awayTeamPlayerStats: z2.ZodDefault<z2.ZodNullable<z2.ZodArray<z2.ZodObject<{
1419
1443
  player: z2.ZodObject<{
1420
1444
  player: z2.ZodObject<{
1421
1445
  position: z2.ZodOptional<z2.ZodString>;
@@ -1470,6 +1494,9 @@ declare const PlayerStatsListSchema: z2.ZodObject<{
1470
1494
  metresGained: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1471
1495
  scoreInvolvements: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1472
1496
  ratingPoints: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1497
+ goalEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1498
+ shotEfficiency: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1499
+ interchangeCounts: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1473
1500
  extendedStats: z2.ZodOptional<z2.ZodNullable<z2.ZodObject<{
1474
1501
  effectiveDisposals: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1475
1502
  effectiveKicks: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
@@ -1501,7 +1528,7 @@ declare const PlayerStatsListSchema: z2.ZodObject<{
1501
1528
  }, z2.core.$loose>;
1502
1529
  timeOnGroundPercentage: z2.ZodOptional<z2.ZodNullable<z2.ZodUnion<readonly [z2.ZodNumber, z2.ZodPipe<z2.ZodString, z2.ZodTransform<number | null, string>>, z2.ZodPipe<z2.ZodBoolean, z2.ZodTransform<1 | 0, boolean>>]>>>;
1503
1530
  }, z2.core.$loose>>>;
1504
- }, z2.core.$loose>>;
1531
+ }, z2.core.$loose>>>>;
1505
1532
  }, z2.core.$loose>;
1506
1533
  /** Inferred type for a single player stats item. */
1507
1534
  type PlayerStatsItem = z2.infer<typeof PlayerStatsItemSchema>;
@@ -2065,12 +2092,15 @@ declare class FootyWireClient {
2065
2092
  /**
2066
2093
  * Fetch match IDs from a season's match list page.
2067
2094
  *
2068
- * Extracts `mid=XXXX` values from score links.
2095
+ * Extracts `mid=XXXX` values from score links alongside round numbers.
2069
2096
  *
2070
2097
  * @param year - The season year.
2071
- * @returns Array of match ID strings.
2098
+ * @returns Array of match ID + round number pairs.
2072
2099
  */
2073
- fetchSeasonMatchIds(year: number): Promise<Result<string[], ScrapeError>>;
2100
+ fetchSeasonMatchIds(year: number): Promise<Result<Array<{
2101
+ matchId: string;
2102
+ roundNumber: number;
2103
+ }>, ScrapeError>>;
2074
2104
  /**
2075
2105
  * Fetch player list (team history) from FootyWire.
2076
2106
  *
@@ -2177,17 +2207,26 @@ declare function inferRoundType(roundName: string): RoundType;
2177
2207
  * @returns Flattened, normalised MatchResult array.
2178
2208
  */
2179
2209
  declare function transformMatchItems(items: readonly MatchItem[], season: number, competition: CompetitionCode, source?: DataSource): MatchResult[];
2210
+ /** Context for a single match transform. */
2211
+ interface TransformContext {
2212
+ readonly matchId: string;
2213
+ readonly season: number;
2214
+ readonly roundNumber: number;
2215
+ readonly competition: CompetitionCode;
2216
+ readonly source: DataSource;
2217
+ readonly teamIdMap?: ReadonlyMap<string, string>;
2218
+ readonly date?: Date | null;
2219
+ readonly homeTeam?: string | null;
2220
+ readonly awayTeam?: string | null;
2221
+ }
2180
2222
  /**
2181
2223
  * Transform raw AFL API player stats list into typed PlayerStats objects.
2182
2224
  *
2183
2225
  * @param data - Raw player stats response with home/away arrays.
2184
- * @param matchId - The match provider ID.
2185
- * @param season - The season year.
2186
- * @param roundNumber - The round number.
2187
- * @param competition - The competition code.
2226
+ * @param ctx - Match context (IDs, season, round, source, team mappings, match metadata).
2188
2227
  * @returns Flattened PlayerStats array (home players first, then away).
2189
2228
  */
2190
- declare function transformPlayerStats(data: PlayerStatsList, matchId: string, season: number, roundNumber: number, competition: CompetitionCode, source?: DataSource, teamIdMap?: ReadonlyMap<string, string>): PlayerStats[];
2229
+ declare function transformPlayerStats(data: PlayerStatsList, ctx: TransformContext): PlayerStats[];
2191
2230
  /**
2192
2231
  * Transform Squiggle games into MatchResult objects.
2193
2232
  *
@@ -2204,4 +2243,4 @@ declare function transformSquiggleGamesToFixture(games: readonly SquiggleGame[],
2204
2243
  * Transform Squiggle standings into LadderEntry objects.
2205
2244
  */
2206
2245
  declare function transformSquiggleStandings(standings: readonly SquiggleStanding[]): LadderEntry[];
2207
- export { transformSquiggleStandings, transformSquiggleGamesToResults, transformSquiggleGamesToFixture, transformPlayerStats, transformMatchRoster, transformMatchItems, transformLadderEntries, toAestString, parseFootyWireDate, parseAflTablesDate, parseAflApiDate, ok, normaliseVenueName, normaliseTeamName, inferRoundType, fetchTeams2 as fetchTeams, fetchTeamStats2 as fetchTeamStats, fetchSquad2 as fetchSquad, fetchPlayerStats2 as fetchPlayerStats, fetchPlayerDetails, fetchMatchResults, fetchLineup, fetchLadder2 as fetchLadder, fetchFryziggStats, fetchFixture, fetchCoachesVotes, fetchAwards, err, computeLadder, ValidationError, UnsupportedSourceError, TeamStatsSummaryType, TeamStatsQuery, TeamStatsEntry, TeamScoreSchema, TeamScore, TeamQuery, TeamPlayersSchema, TeamPlayers, TeamListSchema, TeamList, TeamItemSchema, TeamItem, Team, SquiggleStandingsResponseSchema, SquiggleStandingsResponse, SquiggleStandingSchema, SquiggleStanding, SquiggleGamesResponseSchema, SquiggleGamesResponse, SquiggleGameSchema, SquiggleGame, SquiggleClientOptions, SquiggleClient, SquadSchema, SquadQuery, SquadPlayerItemSchema, SquadPlayerItem, SquadPlayerInnerSchema, SquadPlayer, SquadListSchema, SquadList, Squad, SeasonRoundQuery, ScrapeError, ScoreSchema, Score, RoundType, RoundSchema, RoundListSchema, RoundList, Round, RosterPlayerSchema, RosterPlayer, RisingStarNomination, Result, QuarterScore, PlayerStatsQuery, PlayerStatsListSchema, PlayerStatsList, PlayerStatsItemSchema, PlayerStatsItem, PlayerStats, PlayerGameStatsSchema, PlayerGameStats, PlayerDetailsQuery, PlayerDetails, PeriodScoreSchema, PeriodScore, Ok, MatchStatus, MatchRosterSchema, MatchRoster, MatchResult, MatchQuery, MatchItemSchema, MatchItemListSchema, MatchItemList, MatchItem, LineupQuery, LineupPlayer, Lineup, LadderResponseSchema, LadderResponse, LadderQuery, LadderEntryRawSchema, LadderEntryRaw, LadderEntry, Ladder, FootyWireClientOptions, FootyWireClient, Fixture, Err, DataSource, CompseasonSchema, CompseasonListSchema, CompseasonList, Compseason, CompetitionSchema, CompetitionListSchema, CompetitionList, CompetitionCode, Competition, CoachesVoteQuery, CoachesVote, CfsVenueSchema, CfsVenue, CfsScoreSchema, CfsScore, CfsMatchTeamSchema, CfsMatchTeam, CfsMatchSchema, CfsMatch, BrownlowVote, AwardType, AwardQuery, Award, AllAustralianSelection, AflTablesClientOptions, AflTablesClient, AflCoachesClientOptions, AflCoachesClient, AflApiTokenSchema, AflApiToken, AflApiError, AflApiClientOptions, AflApiClient };
2246
+ export { transformSquiggleStandings, transformSquiggleGamesToResults, transformSquiggleGamesToFixture, transformPlayerStats, transformMatchRoster, transformMatchItems, transformLadderEntries, toAestString, parseFootyWireDate, parseAflTablesDate, parseAflApiDate, ok, normaliseVenueName, normaliseTeamName, inferRoundType, fetchTeams2 as fetchTeams, fetchTeamStats2 as fetchTeamStats, fetchSquad2 as fetchSquad, fetchPlayerStats2 as fetchPlayerStats, fetchPlayerDetails, fetchMatchResults, fetchLineup, fetchLadder2 as fetchLadder, fetchFryziggStats, fetchFixture, fetchCoachesVotes, fetchAwards, err, computeLadder, ValidationError, UnsupportedSourceError, TransformContext, TeamStatsSummaryType, TeamStatsQuery, TeamStatsEntry, TeamScoreSchema, TeamScore, TeamQuery, TeamPlayersSchema, TeamPlayers, TeamListSchema, TeamList, TeamItemSchema, TeamItem, Team, SquiggleStandingsResponseSchema, SquiggleStandingsResponse, SquiggleStandingSchema, SquiggleStanding, SquiggleGamesResponseSchema, SquiggleGamesResponse, SquiggleGameSchema, SquiggleGame, SquiggleClientOptions, SquiggleClient, SquadSchema, SquadQuery, SquadPlayerItemSchema, SquadPlayerItem, SquadPlayerInnerSchema, SquadPlayer, SquadListSchema, SquadList, Squad, SeasonRoundQuery, ScrapeError, ScoreSchema, Score, RoundType, RoundSchema, RoundListSchema, RoundList, Round, RosterPlayerSchema, RosterPlayer, RisingStarNomination, Result, QuarterScore, PlayerStatsQuery, PlayerStatsListSchema, PlayerStatsList, PlayerStatsItemSchema, PlayerStatsItem, PlayerStats, PlayerGameStatsSchema, PlayerGameStats, PlayerDetailsQuery, PlayerDetails, PeriodScoreSchema, PeriodScore, Ok, MatchStatus, MatchRosterSchema, MatchRoster, MatchResult, MatchQuery, MatchItemSchema, MatchItemListSchema, MatchItemList, MatchItem, LineupQuery, LineupPlayer, Lineup, LadderResponseSchema, LadderResponse, LadderQuery, LadderEntryRawSchema, LadderEntryRaw, LadderEntry, Ladder, FootyWireClientOptions, FootyWireClient, Fixture, Err, DataSource, CompseasonSchema, CompseasonListSchema, CompseasonList, Compseason, CompetitionSchema, CompetitionListSchema, CompetitionList, CompetitionCode, Competition, CoachesVoteQuery, CoachesVote, CfsVenueSchema, CfsVenue, CfsScoreSchema, CfsScore, CfsMatchTeamSchema, CfsMatchTeam, CfsMatchSchema, CfsMatch, BrownlowVote, AwardType, AwardQuery, Award, AllAustralianSelection, AflTablesClientOptions, AflTablesClient, AflCoachesClientOptions, AflCoachesClient, AflApiTokenSchema, AflApiToken, AflApiError, AflApiClientOptions, AflApiClient };
package/dist/index.js CHANGED
@@ -489,6 +489,9 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
489
489
  roundNumber,
490
490
  team: teamName,
491
491
  competition: "AFLM",
492
+ date: null,
493
+ homeTeam: null,
494
+ awayTeam: null,
492
495
  playerId: `FW_${basic.player.replace(/\s+/g, "_")}`,
493
496
  givenName: firstName,
494
497
  surname,
@@ -528,6 +531,11 @@ function mergeFootyWireStats(basicTeams, advancedTeams, matchId, season, roundNu
528
531
  totalPossessions: null,
529
532
  timeOnGroundPercentage: adv?.timeOnGroundPercentage ?? null,
530
533
  ratingPoints: null,
534
+ position: null,
535
+ goalEfficiency: null,
536
+ shotEfficiency: null,
537
+ interchangeCounts: null,
538
+ supercoachScore: basic.supercoachPoints,
531
539
  dreamTeamPoints: basic.dreamTeamPoints,
532
540
  effectiveDisposals: adv?.effectiveDisposals ?? null,
533
541
  effectiveKicks: null,
@@ -617,6 +625,7 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
617
625
  season,
618
626
  roundNumber: item.round?.roundNumber ?? 0,
619
627
  roundType: inferRoundType(item.round?.name ?? ""),
628
+ roundName: item.round?.name ?? null,
620
629
  date: new Date(item.match.utcStartTime),
621
630
  venue: item.venue?.name ? normaliseVenueName(item.venue.name) : "",
622
631
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
@@ -750,10 +759,10 @@ var FootyWireClient = class {
750
759
  /**
751
760
  * Fetch match IDs from a season's match list page.
752
761
  *
753
- * Extracts `mid=XXXX` values from score links.
762
+ * Extracts `mid=XXXX` values from score links alongside round numbers.
754
763
  *
755
764
  * @param year - The season year.
756
- * @returns Array of match ID strings.
765
+ * @returns Array of match ID + round number pairs.
757
766
  */
758
767
  async fetchSeasonMatchIds(year) {
759
768
  const url = `${FOOTYWIRE_BASE}/ft_match_list?year=${year}`;
@@ -761,15 +770,33 @@ var FootyWireClient = class {
761
770
  if (!htmlResult.success) return htmlResult;
762
771
  try {
763
772
  const $ = cheerio2.load(htmlResult.data);
764
- const ids = [];
765
- $(".data:nth-child(5) a").each((_i, el) => {
766
- const href = $(el).attr("href") ?? "";
767
- const match = /mid=(\d+)/.exec(href);
768
- if (match?.[1]) {
769
- ids.push(match[1]);
773
+ const entries = [];
774
+ let currentRound = 0;
775
+ let lastHARound = 0;
776
+ $("tr").each((_i, row) => {
777
+ const roundHeader = $(row).find("td[colspan='7']");
778
+ if (roundHeader.length > 0) {
779
+ const text = roundHeader.text().trim();
780
+ const roundMatch = /Round\s+(\d+)/i.exec(text);
781
+ if (roundMatch?.[1]) {
782
+ currentRound = Number.parseInt(roundMatch[1], 10);
783
+ if (inferRoundType(text) === "HomeAndAway") {
784
+ lastHARound = currentRound;
785
+ }
786
+ } else if (inferRoundType(text) === "Finals") {
787
+ currentRound = finalsRoundNumber(text, lastHARound);
788
+ }
789
+ return;
790
+ }
791
+ const scoreLink = $(row).find(".data:nth-child(5) a");
792
+ if (scoreLink.length === 0) return;
793
+ const href = scoreLink.attr("href") ?? "";
794
+ const midMatch = /mid=(\d+)/.exec(href);
795
+ if (midMatch?.[1]) {
796
+ entries.push({ matchId: midMatch[1], roundNumber: currentRound });
770
797
  }
771
798
  });
772
- return ok(ids);
799
+ return ok(entries);
773
800
  } catch (cause) {
774
801
  return err(
775
802
  new ScrapeError(
@@ -873,10 +900,12 @@ function parseMatchList(html, year) {
873
900
  let currentRound = 0;
874
901
  let lastHARound = 0;
875
902
  let currentRoundType = "HomeAndAway";
903
+ let currentRoundName = "";
876
904
  $("tr").each((_i, row) => {
877
905
  const roundHeader = $(row).find("td[colspan='7']");
878
906
  if (roundHeader.length > 0) {
879
907
  const text = roundHeader.text().trim();
908
+ currentRoundName = text;
880
909
  currentRoundType = inferRoundType(text);
881
910
  const roundMatch = /Round\s+(\d+)/i.exec(text);
882
911
  if (roundMatch?.[1]) {
@@ -919,6 +948,7 @@ function parseMatchList(html, year) {
919
948
  season: year,
920
949
  roundNumber: currentRound,
921
950
  roundType: currentRoundType,
951
+ roundName: currentRoundName || null,
922
952
  date,
923
953
  venue: normaliseVenueName(venue),
924
954
  homeTeam,
@@ -1674,6 +1704,9 @@ var PlayerGameStatsSchema = z.object({
1674
1704
  metresGained: statNum,
1675
1705
  scoreInvolvements: statNum,
1676
1706
  ratingPoints: statNum,
1707
+ goalEfficiency: statNum,
1708
+ shotEfficiency: statNum,
1709
+ interchangeCounts: statNum,
1677
1710
  extendedStats: z.object({
1678
1711
  effectiveDisposals: statNum,
1679
1712
  effectiveKicks: statNum,
@@ -1718,8 +1751,8 @@ var PlayerStatsItemSchema = z.object({
1718
1751
  }).passthrough().nullable().optional()
1719
1752
  }).passthrough();
1720
1753
  var PlayerStatsListSchema = z.object({
1721
- homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
1722
- awayTeamPlayerStats: z.array(PlayerStatsItemSchema)
1754
+ homeTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([]),
1755
+ awayTeamPlayerStats: z.array(PlayerStatsItemSchema).nullable().default([])
1723
1756
  }).passthrough();
1724
1757
  var RosterPlayerSchema = z.object({
1725
1758
  player: z.object({
@@ -2313,6 +2346,7 @@ function transformSquiggleGamesToResults(games, season) {
2313
2346
  season,
2314
2347
  roundNumber: g.round,
2315
2348
  roundType: inferRoundType(g.roundname),
2349
+ roundName: g.roundname || null,
2316
2350
  date: new Date(g.unixtime * 1e3),
2317
2351
  venue: normaliseVenueName(g.venue),
2318
2352
  homeTeam: normaliseTeamName(g.hteam),
@@ -2481,6 +2515,9 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
2481
2515
  roundNumber,
2482
2516
  team: teamName,
2483
2517
  competition: "AFLM",
2518
+ date: null,
2519
+ homeTeam: null,
2520
+ awayTeam: null,
2484
2521
  playerId: `AT_${displayName.replace(/\s+/g, "_")}`,
2485
2522
  givenName,
2486
2523
  surname,
@@ -2520,6 +2557,11 @@ function parseAflTablesGameStats(html, matchId, season, roundNumber) {
2520
2557
  totalPossessions: null,
2521
2558
  timeOnGroundPercentage: safeInt(cells[24] ?? ""),
2522
2559
  ratingPoints: null,
2560
+ position: null,
2561
+ goalEfficiency: null,
2562
+ shotEfficiency: null,
2563
+ interchangeCounts: null,
2564
+ supercoachScore: null,
2523
2565
  dreamTeamPoints: null,
2524
2566
  effectiveDisposals: null,
2525
2567
  effectiveKicks: null,
@@ -2743,6 +2785,7 @@ function parseSeasonPage(html, year) {
2743
2785
  const results = [];
2744
2786
  let currentRound = 0;
2745
2787
  let currentRoundType = "HomeAndAway";
2788
+ let currentRoundName = "";
2746
2789
  let lastHARound = 0;
2747
2790
  let matchCounter = 0;
2748
2791
  $("table").each((_i, table) => {
@@ -2753,6 +2796,7 @@ function parseSeasonPage(html, year) {
2753
2796
  if (roundMatch?.[1] && border !== "1") {
2754
2797
  currentRound = Number.parseInt(roundMatch[1], 10);
2755
2798
  currentRoundType = inferRoundType(text);
2799
+ currentRoundName = text;
2756
2800
  if (currentRoundType === "HomeAndAway") {
2757
2801
  lastHARound = currentRound;
2758
2802
  }
@@ -2760,6 +2804,7 @@ function parseSeasonPage(html, year) {
2760
2804
  }
2761
2805
  if (border !== "1" && inferRoundType(text) === "Finals") {
2762
2806
  currentRoundType = "Finals";
2807
+ currentRoundName = text;
2763
2808
  currentRound = finalsRoundNumber(text, lastHARound);
2764
2809
  return;
2765
2810
  }
@@ -2790,6 +2835,7 @@ function parseSeasonPage(html, year) {
2790
2835
  season: year,
2791
2836
  roundNumber: currentRound,
2792
2837
  roundType: currentRoundType,
2838
+ roundName: currentRoundName || null,
2793
2839
  date,
2794
2840
  venue,
2795
2841
  homeTeam,
@@ -3265,29 +3311,14 @@ async function resolveTeamId(client, teamName, competition) {
3265
3311
  }
3266
3312
  return ok(String(match.id));
3267
3313
  }
3268
- async function fetchFromAflApi(query) {
3269
- const client = new AflApiClient();
3270
- const competition = query.competition ?? "AFLM";
3271
- const season = query.season ?? resolveDefaultSeason(competition);
3272
- const [teamIdResult, seasonResult] = await Promise.all([
3273
- resolveTeamId(client, query.team, competition),
3274
- client.resolveCompSeason(competition, season)
3275
- ]);
3276
- if (!teamIdResult.success) return teamIdResult;
3277
- if (!seasonResult.success) return seasonResult;
3278
- const teamId = Number.parseInt(teamIdResult.data, 10);
3279
- if (Number.isNaN(teamId)) {
3280
- return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
3281
- }
3282
- const squadResult = await client.fetchSquad(teamId, seasonResult.data);
3283
- if (!squadResult.success) return squadResult;
3284
- const teamName = normaliseTeamName(squadResult.data.squad.team?.name ?? query.team);
3285
- const players = squadResult.data.squad.players.map((p) => ({
3314
+ function mapSquadToPlayerDetails(data, fallbackTeamName, competition) {
3315
+ const resolvedName = normaliseTeamName(data.squad.team?.name ?? fallbackTeamName);
3316
+ return data.squad.players.map((p) => ({
3286
3317
  playerId: p.player.providerId ?? String(p.player.id),
3287
3318
  givenName: p.player.firstName,
3288
3319
  surname: p.player.surname,
3289
3320
  displayName: `${p.player.firstName} ${p.player.surname}`,
3290
- team: teamName,
3321
+ team: resolvedName,
3291
3322
  jumperNumber: p.jumperNumber ?? null,
3292
3323
  position: p.position ?? null,
3293
3324
  dateOfBirth: p.player.dateOfBirth ?? null,
@@ -3303,35 +3334,83 @@ async function fetchFromAflApi(query) {
3303
3334
  source: "afl-api",
3304
3335
  competition
3305
3336
  }));
3306
- return ok(players);
3337
+ }
3338
+ async function fetchFromAflApi(query) {
3339
+ const client = new AflApiClient();
3340
+ const competition = query.competition ?? "AFLM";
3341
+ const season = query.season ?? resolveDefaultSeason(competition);
3342
+ const seasonResult = await client.resolveCompSeason(competition, season);
3343
+ if (!seasonResult.success) return seasonResult;
3344
+ if (query.team) {
3345
+ const teamIdResult = await resolveTeamId(client, query.team, competition);
3346
+ if (!teamIdResult.success) return teamIdResult;
3347
+ const teamId = Number.parseInt(teamIdResult.data, 10);
3348
+ if (Number.isNaN(teamId)) {
3349
+ return err(new ValidationError(`Invalid team ID: ${teamIdResult.data}`));
3350
+ }
3351
+ const squadResult = await client.fetchSquad(teamId, seasonResult.data);
3352
+ if (!squadResult.success) return squadResult;
3353
+ return ok(mapSquadToPlayerDetails(squadResult.data, query.team, competition));
3354
+ }
3355
+ const teamType = competition === "AFLW" ? "WOMEN" : "MEN";
3356
+ const teamsResult = await client.fetchTeams(teamType);
3357
+ if (!teamsResult.success) return teamsResult;
3358
+ const teamEntries = teamsResult.data.map((t) => ({
3359
+ id: Number.parseInt(String(t.id), 10),
3360
+ name: normaliseTeamName(t.name)
3361
+ }));
3362
+ const results = await batchedMap(
3363
+ teamEntries,
3364
+ (entry) => client.fetchSquad(entry.id, seasonResult.data)
3365
+ );
3366
+ const allPlayers = [];
3367
+ for (let i = 0; i < results.length; i++) {
3368
+ const result = results[i];
3369
+ const entry = teamEntries[i];
3370
+ if (result?.success && entry) {
3371
+ allPlayers.push(...mapSquadToPlayerDetails(result.data, entry.name, competition));
3372
+ }
3373
+ }
3374
+ return ok(allPlayers);
3375
+ }
3376
+ async function fetchAllTeamsFromScraper(fetchFn, source, competition) {
3377
+ const teamNames = [...AFL_SENIOR_TEAMS];
3378
+ const results = await batchedMap(teamNames, (name) => fetchFn(name));
3379
+ const allPlayers = [];
3380
+ for (const result of results) {
3381
+ if (result.success) {
3382
+ allPlayers.push(...result.data.map((p) => ({ ...p, source, competition })));
3383
+ }
3384
+ }
3385
+ return ok(allPlayers);
3307
3386
  }
3308
3387
  async function fetchFromFootyWire(query) {
3309
3388
  const competition = query.competition ?? "AFLM";
3310
3389
  if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3311
3390
  const client = new FootyWireClient();
3312
- const teamName = normaliseTeamName(query.team);
3313
- const result = await client.fetchPlayerList(teamName);
3314
- if (!result.success) return result;
3315
- const players = result.data.map((p) => ({
3316
- ...p,
3317
- source: "footywire",
3318
- competition
3319
- }));
3320
- return ok(players);
3391
+ if (query.team) {
3392
+ const teamName = normaliseTeamName(query.team);
3393
+ const result = await client.fetchPlayerList(teamName);
3394
+ if (!result.success) return result;
3395
+ return ok(result.data.map((p) => ({ ...p, source: "footywire", competition })));
3396
+ }
3397
+ return fetchAllTeamsFromScraper((name) => client.fetchPlayerList(name), "footywire", competition);
3321
3398
  }
3322
3399
  async function fetchFromAflTables(query) {
3323
3400
  const competition = query.competition ?? "AFLM";
3324
3401
  if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3325
3402
  const client = new AflTablesClient();
3326
- const teamName = normaliseTeamName(query.team);
3327
- const result = await client.fetchPlayerList(teamName);
3328
- if (!result.success) return result;
3329
- const players = result.data.map((p) => ({
3330
- ...p,
3331
- source: "afl-tables",
3403
+ if (query.team) {
3404
+ const teamName = normaliseTeamName(query.team);
3405
+ const result = await client.fetchPlayerList(teamName);
3406
+ if (!result.success) return result;
3407
+ return ok(result.data.map((p) => ({ ...p, source: "afl-tables", competition })));
3408
+ }
3409
+ return fetchAllTeamsFromScraper(
3410
+ (name) => client.fetchPlayerList(name),
3411
+ "afl-tables",
3332
3412
  competition
3333
- }));
3334
- return ok(players);
3413
+ );
3335
3414
  }
3336
3415
  async function fetchPlayerDetails(query) {
3337
3416
  switch (query.source) {
@@ -3355,18 +3434,21 @@ async function fetchPlayerDetails(query) {
3355
3434
  function toNullable(value) {
3356
3435
  return value ?? null;
3357
3436
  }
3358
- function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
3437
+ function transformOne(item, ctx) {
3359
3438
  const inner = item.player.player.player;
3360
3439
  const stats = item.playerStats?.stats;
3361
3440
  const clearances = stats?.clearances;
3362
3441
  return {
3363
- matchId,
3364
- season,
3365
- roundNumber,
3442
+ matchId: ctx.matchId,
3443
+ season: ctx.season,
3444
+ roundNumber: ctx.roundNumber,
3366
3445
  team: normaliseTeamName(
3367
- teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3446
+ ctx.teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3368
3447
  ),
3369
- competition,
3448
+ competition: ctx.competition,
3449
+ date: ctx.date ?? null,
3450
+ homeTeam: ctx.homeTeam ?? null,
3451
+ awayTeam: ctx.awayTeam ?? null,
3370
3452
  playerId: inner.playerId,
3371
3453
  givenName: inner.playerName.givenName,
3372
3454
  surname: inner.playerName.surname,
@@ -3406,6 +3488,11 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
3406
3488
  totalPossessions: toNullable(stats?.totalPossessions),
3407
3489
  timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
3408
3490
  ratingPoints: toNullable(stats?.ratingPoints),
3491
+ position: item.player.player.position ?? null,
3492
+ goalEfficiency: toNullable(stats?.goalEfficiency),
3493
+ shotEfficiency: toNullable(stats?.shotEfficiency),
3494
+ interchangeCounts: toNullable(stats?.interchangeCounts),
3495
+ supercoachScore: null,
3409
3496
  dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
3410
3497
  effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
3411
3498
  effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
@@ -3433,16 +3520,12 @@ function transformOne(item, matchId, season, roundNumber, competition, source, t
3433
3520
  kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
3434
3521
  ruckContests: toNullable(stats?.extendedStats?.ruckContests),
3435
3522
  scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
3436
- source
3523
+ source: ctx.source
3437
3524
  };
3438
3525
  }
3439
- function transformPlayerStats(data, matchId, season, roundNumber, competition, source = "afl-api", teamIdMap) {
3440
- const home = data.homeTeamPlayerStats.map(
3441
- (item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
3442
- );
3443
- const away = data.awayTeamPlayerStats.map(
3444
- (item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
3445
- );
3526
+ function transformPlayerStats(data, ctx) {
3527
+ const home = (data.homeTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
3528
+ const away = (data.awayTeamPlayerStats ?? []).map((item) => transformOne(item, ctx));
3446
3529
  return [...home, ...away];
3447
3530
  }
3448
3531
 
@@ -3465,24 +3548,19 @@ async function fetchPlayerStats(query) {
3465
3548
  teamIdMap2.set(match.awayTeamId, normaliseTeamName(match.awayTeam.name));
3466
3549
  }
3467
3550
  return ok(
3468
- transformPlayerStats(
3469
- statsResult.data,
3470
- query.matchId,
3471
- query.season,
3472
- query.round ?? 0,
3551
+ transformPlayerStats(statsResult.data, {
3552
+ matchId: query.matchId,
3553
+ season: query.season,
3554
+ roundNumber: query.round ?? 0,
3473
3555
  competition,
3474
- "afl-api",
3475
- teamIdMap2
3476
- )
3556
+ source: "afl-api",
3557
+ teamIdMap: teamIdMap2
3558
+ })
3477
3559
  );
3478
3560
  }
3479
3561
  const seasonResult = await client.resolveCompSeason(competition, query.season);
3480
3562
  if (!seasonResult.success) return seasonResult;
3481
- const roundNumber = query.round ?? 1;
3482
- const matchItemsResult = await client.fetchRoundMatchItemsByNumber(
3483
- seasonResult.data,
3484
- roundNumber
3485
- );
3563
+ const matchItemsResult = query.round != null ? await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round) : await client.fetchSeasonMatchItems(seasonResult.data);
3486
3564
  if (!matchItemsResult.success) return matchItemsResult;
3487
3565
  const teamIdMap = /* @__PURE__ */ new Map();
3488
3566
  for (const item of matchItemsResult.data) {
@@ -3501,15 +3579,17 @@ async function fetchPlayerStats(query) {
3501
3579
  const item = matchItemsResult.data[i];
3502
3580
  if (!item) continue;
3503
3581
  allStats.push(
3504
- ...transformPlayerStats(
3505
- statsResult.data,
3506
- item.match.matchId,
3507
- query.season,
3508
- roundNumber,
3582
+ ...transformPlayerStats(statsResult.data, {
3583
+ matchId: item.match.matchId,
3584
+ season: query.season,
3585
+ roundNumber: item.round?.roundNumber ?? query.round ?? 0,
3509
3586
  competition,
3510
- "afl-api",
3511
- teamIdMap
3512
- )
3587
+ source: "afl-api",
3588
+ teamIdMap,
3589
+ date: new Date(item.match.utcStartTime),
3590
+ homeTeam: normaliseTeamName(item.match.homeTeam.name),
3591
+ awayTeam: normaliseTeamName(item.match.awayTeam.name)
3592
+ })
3513
3593
  );
3514
3594
  }
3515
3595
  return ok(allStats);
@@ -3519,29 +3599,26 @@ async function fetchPlayerStats(query) {
3519
3599
  const fwClient = new FootyWireClient();
3520
3600
  const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
3521
3601
  if (!idsResult.success) return idsResult;
3522
- const matchIds = idsResult.data;
3523
- if (matchIds.length === 0) {
3602
+ const entries = query.round != null ? idsResult.data.filter((e) => e.roundNumber === query.round) : idsResult.data;
3603
+ if (entries.length === 0) {
3524
3604
  return ok([]);
3525
3605
  }
3526
3606
  const allStats = [];
3527
3607
  const batchSize = 5;
3528
- for (let i = 0; i < matchIds.length; i += batchSize) {
3529
- const batch = matchIds.slice(i, i + batchSize);
3608
+ for (let i = 0; i < entries.length; i += batchSize) {
3609
+ const batch = entries.slice(i, i + batchSize);
3530
3610
  const results = await Promise.all(
3531
- batch.map((mid) => fwClient.fetchMatchPlayerStats(mid, query.season, query.round ?? 0))
3611
+ batch.map((e) => fwClient.fetchMatchPlayerStats(e.matchId, query.season, e.roundNumber))
3532
3612
  );
3533
3613
  for (const result of results) {
3534
3614
  if (result.success) {
3535
3615
  allStats.push(...result.data);
3536
3616
  }
3537
3617
  }
3538
- if (i + batchSize < matchIds.length) {
3618
+ if (i + batchSize < entries.length) {
3539
3619
  await new Promise((resolve) => setTimeout(resolve, 500));
3540
3620
  }
3541
3621
  }
3542
- if (query.round != null) {
3543
- return ok(allStats.filter((s) => s.roundNumber === query.round));
3544
- }
3545
3622
  return ok(allStats);
3546
3623
  }
3547
3624
  case "afl-tables": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitzroy",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "TypeScript port of the fitzRoy R package — programmatic access to AFL data including match results, player stats, fixtures, ladders, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",