fitzroy 1.2.0 → 1.3.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/index.js CHANGED
@@ -20,6 +20,12 @@ var UnsupportedSourceError = class extends Error {
20
20
  }
21
21
  name = "UnsupportedSourceError";
22
22
  };
23
+ function aflwUnsupportedError(source) {
24
+ return new UnsupportedSourceError(
25
+ `AFLW data is not available from ${source}. Use --source afl-api for AFLW data.`,
26
+ source
27
+ );
28
+ }
23
29
  var ValidationError = class extends Error {
24
30
  constructor(message, issues) {
25
31
  super(message);
@@ -119,6 +125,10 @@ function toAestString(date) {
119
125
  });
120
126
  return formatter.format(date);
121
127
  }
128
+ function resolveDefaultSeason(competition = "AFLM") {
129
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
130
+ return competition === "AFLW" ? year - 1 : year;
131
+ }
122
132
  var MONTH_ABBREV_TO_INDEX = /* @__PURE__ */ new Map([
123
133
  ["jan", 0],
124
134
  ["feb", 1],
@@ -233,6 +243,26 @@ function normaliseTeamName(raw) {
233
243
  const trimmed = raw.trim();
234
244
  return ALIAS_MAP.get(trimmed.toLowerCase()) ?? trimmed;
235
245
  }
246
+ var AFL_API_TEAM_IDS = /* @__PURE__ */ new Map([
247
+ ["CD_T10", "Adelaide Crows"],
248
+ ["CD_T20", "Brisbane Lions"],
249
+ ["CD_T30", "Carlton"],
250
+ ["CD_T40", "Collingwood"],
251
+ ["CD_T50", "Essendon"],
252
+ ["CD_T60", "Fremantle"],
253
+ ["CD_T70", "Geelong Cats"],
254
+ ["CD_T1000", "Gold Coast Suns"],
255
+ ["CD_T1010", "GWS Giants"],
256
+ ["CD_T80", "Hawthorn"],
257
+ ["CD_T90", "Melbourne"],
258
+ ["CD_T100", "North Melbourne"],
259
+ ["CD_T110", "Port Adelaide"],
260
+ ["CD_T120", "Richmond"],
261
+ ["CD_T130", "St Kilda"],
262
+ ["CD_T160", "Sydney Swans"],
263
+ ["CD_T150", "West Coast Eagles"],
264
+ ["CD_T140", "Western Bulldogs"]
265
+ ]);
236
266
 
237
267
  // src/transforms/footywire-player-stats.ts
238
268
  import * as cheerio from "cheerio";
@@ -487,6 +517,14 @@ var FINALS_PATTERN = /final|elimination|qualifying|preliminary|semi|grand/i;
487
517
  function inferRoundType(roundName) {
488
518
  return FINALS_PATTERN.test(roundName) ? "Finals" : "HomeAndAway";
489
519
  }
520
+ function finalsRoundNumber(headerText, lastHARound) {
521
+ const lower = headerText.toLowerCase();
522
+ if (lower.includes("qualifying") || lower.includes("elimination")) return lastHARound + 1;
523
+ if (lower.includes("semi")) return lastHARound + 2;
524
+ if (lower.includes("preliminary")) return lastHARound + 3;
525
+ if (lower.includes("grand")) return lastHARound + 4;
526
+ return lastHARound + 1;
527
+ }
490
528
  function toMatchStatus(raw) {
491
529
  switch (raw) {
492
530
  case "CONCLUDED":
@@ -783,6 +821,7 @@ function parseMatchList(html, year) {
783
821
  const $ = cheerio2.load(html);
784
822
  const results = [];
785
823
  let currentRound = 0;
824
+ let lastHARound = 0;
786
825
  let currentRoundType = "HomeAndAway";
787
826
  $("tr").each((_i, row) => {
788
827
  const roundHeader = $(row).find("td[colspan='7']");
@@ -792,6 +831,11 @@ function parseMatchList(html, year) {
792
831
  const roundMatch = /Round\s+(\d+)/i.exec(text);
793
832
  if (roundMatch?.[1]) {
794
833
  currentRound = Number.parseInt(roundMatch[1], 10);
834
+ if (currentRoundType === "HomeAndAway") {
835
+ lastHARound = currentRound;
836
+ }
837
+ } else if (currentRoundType === "Finals") {
838
+ currentRound = finalsRoundNumber(text, lastHARound);
795
839
  }
796
840
  return;
797
841
  }
@@ -862,6 +906,7 @@ function parseFixtureList(html, year) {
862
906
  const $ = cheerio2.load(html);
863
907
  const fixtures = [];
864
908
  let currentRound = 0;
909
+ let lastHARound = 0;
865
910
  let currentRoundType = "HomeAndAway";
866
911
  let gameNumber = 0;
867
912
  $("tr").each((_i, row) => {
@@ -872,6 +917,11 @@ function parseFixtureList(html, year) {
872
917
  const roundMatch = /Round\s+(\d+)/i.exec(text);
873
918
  if (roundMatch?.[1]) {
874
919
  currentRound = Number.parseInt(roundMatch[1], 10);
920
+ if (currentRoundType === "HomeAndAway") {
921
+ lastHARound = currentRound;
922
+ }
923
+ } else if (currentRoundType === "Finals") {
924
+ currentRound = finalsRoundNumber(text, lastHARound);
875
925
  }
876
926
  return;
877
927
  }
@@ -1528,7 +1578,14 @@ var CfsPlayerInnerSchema = z.object({
1528
1578
  captain: z.boolean().optional(),
1529
1579
  playerJumperNumber: z.number().optional()
1530
1580
  }).passthrough();
1531
- var statNum = z.number().nullable().optional();
1581
+ var statNum = z.union([
1582
+ z.number(),
1583
+ z.string().transform((s) => {
1584
+ if (s === "" || s === "-") return null;
1585
+ const n = Number(s);
1586
+ return Number.isNaN(n) ? null : n;
1587
+ })
1588
+ ]).nullable().optional();
1532
1589
  var PlayerGameStatsSchema = z.object({
1533
1590
  goals: statNum,
1534
1591
  behinds: statNum,
@@ -1606,8 +1663,8 @@ var PlayerStatsItemSchema = z.object({
1606
1663
  teamId: z.string(),
1607
1664
  playerStats: z.object({
1608
1665
  stats: PlayerGameStatsSchema,
1609
- timeOnGroundPercentage: z.number().nullable().optional()
1610
- }).passthrough()
1666
+ timeOnGroundPercentage: statNum
1667
+ }).passthrough().nullable().optional()
1611
1668
  }).passthrough();
1612
1669
  var PlayerStatsListSchema = z.object({
1613
1670
  homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
@@ -2284,12 +2341,14 @@ function toFixture(item, season, fallbackRoundNumber, competition) {
2284
2341
  async function fetchFixture(query) {
2285
2342
  const competition = query.competition ?? "AFLM";
2286
2343
  if (query.source === "squiggle") {
2344
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
2287
2345
  const client2 = new SquiggleClient();
2288
2346
  const result = await client2.fetchGames(query.season, query.round ?? void 0);
2289
2347
  if (!result.success) return result;
2290
2348
  return ok(transformSquiggleGamesToFixture(result.data.games, query.season));
2291
2349
  }
2292
2350
  if (query.source === "footywire") {
2351
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
2293
2352
  const fwClient = new FootyWireClient();
2294
2353
  const result = await fwClient.fetchSeasonFixture(query.season);
2295
2354
  if (!result.success) return result;
@@ -2633,6 +2692,7 @@ function parseSeasonPage(html, year) {
2633
2692
  const results = [];
2634
2693
  let currentRound = 0;
2635
2694
  let currentRoundType = "HomeAndAway";
2695
+ let lastHARound = 0;
2636
2696
  let matchCounter = 0;
2637
2697
  $("table").each((_i, table) => {
2638
2698
  const $table = $(table);
@@ -2642,10 +2702,14 @@ function parseSeasonPage(html, year) {
2642
2702
  if (roundMatch?.[1] && border !== "1") {
2643
2703
  currentRound = Number.parseInt(roundMatch[1], 10);
2644
2704
  currentRoundType = inferRoundType(text);
2705
+ if (currentRoundType === "HomeAndAway") {
2706
+ lastHARound = currentRound;
2707
+ }
2645
2708
  return;
2646
2709
  }
2647
2710
  if (border !== "1" && inferRoundType(text) === "Finals") {
2648
2711
  currentRoundType = "Finals";
2712
+ currentRound = finalsRoundNumber(text, lastHARound);
2649
2713
  return;
2650
2714
  }
2651
2715
  if (border !== "1") return;
@@ -2960,6 +3024,7 @@ function transformLadderEntries(entries) {
2960
3024
  async function fetchLadder(query) {
2961
3025
  const competition = query.competition ?? "AFLM";
2962
3026
  if (query.source === "squiggle") {
3027
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
2963
3028
  const client2 = new SquiggleClient();
2964
3029
  const result = await client2.fetchStandings(query.season, query.round ?? void 0);
2965
3030
  if (!result.success) return result;
@@ -2971,6 +3036,7 @@ async function fetchLadder(query) {
2971
3036
  });
2972
3037
  }
2973
3038
  if (query.source === "afl-tables") {
3039
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
2974
3040
  const atClient = new AflTablesClient();
2975
3041
  const resultsResult = await atClient.fetchSeasonResults(query.season);
2976
3042
  if (!resultsResult.success) return resultsResult;
@@ -3105,6 +3171,7 @@ async function fetchMatchResults(query) {
3105
3171
  return ok(transformMatchItems(itemsResult.data, query.season, competition));
3106
3172
  }
3107
3173
  case "footywire": {
3174
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3108
3175
  const client = new FootyWireClient();
3109
3176
  const result = await client.fetchSeasonResults(query.season);
3110
3177
  if (!result.success) return result;
@@ -3114,6 +3181,7 @@ async function fetchMatchResults(query) {
3114
3181
  return result;
3115
3182
  }
3116
3183
  case "afl-tables": {
3184
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3117
3185
  const client = new AflTablesClient();
3118
3186
  const result = await client.fetchSeasonResults(query.season);
3119
3187
  if (!result.success) return result;
@@ -3123,6 +3191,7 @@ async function fetchMatchResults(query) {
3123
3191
  return result;
3124
3192
  }
3125
3193
  case "squiggle": {
3194
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
3126
3195
  const client = new SquiggleClient();
3127
3196
  const result = await client.fetchGames(query.season, query.round ?? void 0, 100);
3128
3197
  if (!result.success) return result;
@@ -3148,7 +3217,7 @@ async function resolveTeamId(client, teamName, competition) {
3148
3217
  async function fetchFromAflApi(query) {
3149
3218
  const client = new AflApiClient();
3150
3219
  const competition = query.competition ?? "AFLM";
3151
- const season = query.season ?? (/* @__PURE__ */ new Date()).getFullYear();
3220
+ const season = query.season ?? resolveDefaultSeason(competition);
3152
3221
  const [teamIdResult, seasonResult] = await Promise.all([
3153
3222
  resolveTeamId(client, query.team, competition),
3154
3223
  client.resolveCompSeason(competition, season)
@@ -3171,8 +3240,8 @@ async function fetchFromAflApi(query) {
3171
3240
  jumperNumber: p.jumperNumber ?? null,
3172
3241
  position: p.position ?? null,
3173
3242
  dateOfBirth: p.player.dateOfBirth ?? null,
3174
- heightCm: p.player.heightInCm ?? null,
3175
- weightKg: p.player.weightInKg ?? null,
3243
+ heightCm: p.player.heightInCm || null,
3244
+ weightKg: p.player.weightInKg || null,
3176
3245
  gamesPlayed: null,
3177
3246
  goals: null,
3178
3247
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
@@ -3186,8 +3255,9 @@ async function fetchFromAflApi(query) {
3186
3255
  return ok(players);
3187
3256
  }
3188
3257
  async function fetchFromFootyWire(query) {
3189
- const client = new FootyWireClient();
3190
3258
  const competition = query.competition ?? "AFLM";
3259
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3260
+ const client = new FootyWireClient();
3191
3261
  const teamName = normaliseTeamName(query.team);
3192
3262
  const result = await client.fetchPlayerList(teamName);
3193
3263
  if (!result.success) return result;
@@ -3199,8 +3269,9 @@ async function fetchFromFootyWire(query) {
3199
3269
  return ok(players);
3200
3270
  }
3201
3271
  async function fetchFromAflTables(query) {
3202
- const client = new AflTablesClient();
3203
3272
  const competition = query.competition ?? "AFLM";
3273
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3274
+ const client = new AflTablesClient();
3204
3275
  const teamName = normaliseTeamName(query.team);
3205
3276
  const result = await client.fetchPlayerList(teamName);
3206
3277
  if (!result.success) return result;
@@ -3235,80 +3306,82 @@ function toNullable(value) {
3235
3306
  }
3236
3307
  function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
3237
3308
  const inner = item.player.player.player;
3238
- const stats = item.playerStats.stats;
3239
- const clearances = stats.clearances;
3309
+ const stats = item.playerStats?.stats;
3310
+ const clearances = stats?.clearances;
3240
3311
  return {
3241
3312
  matchId,
3242
3313
  season,
3243
3314
  roundNumber,
3244
- team: normaliseTeamName(teamIdMap?.get(item.teamId) ?? item.teamId),
3315
+ team: normaliseTeamName(
3316
+ teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3317
+ ),
3245
3318
  competition,
3246
3319
  playerId: inner.playerId,
3247
3320
  givenName: inner.playerName.givenName,
3248
3321
  surname: inner.playerName.surname,
3249
3322
  displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
3250
3323
  jumperNumber: item.player.jumperNumber ?? null,
3251
- kicks: toNullable(stats.kicks),
3252
- handballs: toNullable(stats.handballs),
3253
- disposals: toNullable(stats.disposals),
3254
- marks: toNullable(stats.marks),
3255
- goals: toNullable(stats.goals),
3256
- behinds: toNullable(stats.behinds),
3257
- tackles: toNullable(stats.tackles),
3258
- hitouts: toNullable(stats.hitouts),
3259
- freesFor: toNullable(stats.freesFor),
3260
- freesAgainst: toNullable(stats.freesAgainst),
3261
- contestedPossessions: toNullable(stats.contestedPossessions),
3262
- uncontestedPossessions: toNullable(stats.uncontestedPossessions),
3263
- contestedMarks: toNullable(stats.contestedMarks),
3264
- intercepts: toNullable(stats.intercepts),
3324
+ kicks: toNullable(stats?.kicks),
3325
+ handballs: toNullable(stats?.handballs),
3326
+ disposals: toNullable(stats?.disposals),
3327
+ marks: toNullable(stats?.marks),
3328
+ goals: toNullable(stats?.goals),
3329
+ behinds: toNullable(stats?.behinds),
3330
+ tackles: toNullable(stats?.tackles),
3331
+ hitouts: toNullable(stats?.hitouts),
3332
+ freesFor: toNullable(stats?.freesFor),
3333
+ freesAgainst: toNullable(stats?.freesAgainst),
3334
+ contestedPossessions: toNullable(stats?.contestedPossessions),
3335
+ uncontestedPossessions: toNullable(stats?.uncontestedPossessions),
3336
+ contestedMarks: toNullable(stats?.contestedMarks),
3337
+ intercepts: toNullable(stats?.intercepts),
3265
3338
  centreClearances: toNullable(clearances?.centreClearances),
3266
3339
  stoppageClearances: toNullable(clearances?.stoppageClearances),
3267
3340
  totalClearances: toNullable(clearances?.totalClearances),
3268
- inside50s: toNullable(stats.inside50s),
3269
- rebound50s: toNullable(stats.rebound50s),
3270
- clangers: toNullable(stats.clangers),
3271
- turnovers: toNullable(stats.turnovers),
3272
- onePercenters: toNullable(stats.onePercenters),
3273
- bounces: toNullable(stats.bounces),
3274
- goalAssists: toNullable(stats.goalAssists),
3275
- disposalEfficiency: toNullable(stats.disposalEfficiency),
3276
- metresGained: toNullable(stats.metresGained),
3277
- goalAccuracy: toNullable(stats.goalAccuracy),
3278
- marksInside50: toNullable(stats.marksInside50),
3279
- tacklesInside50: toNullable(stats.tacklesInside50),
3280
- shotsAtGoal: toNullable(stats.shotsAtGoal),
3281
- scoreInvolvements: toNullable(stats.scoreInvolvements),
3282
- totalPossessions: toNullable(stats.totalPossessions),
3283
- timeOnGroundPercentage: toNullable(item.playerStats.timeOnGroundPercentage),
3284
- ratingPoints: toNullable(stats.ratingPoints),
3285
- dreamTeamPoints: toNullable(stats.dreamTeamPoints),
3286
- effectiveDisposals: toNullable(stats.extendedStats?.effectiveDisposals),
3287
- effectiveKicks: toNullable(stats.extendedStats?.effectiveKicks),
3288
- kickEfficiency: toNullable(stats.extendedStats?.kickEfficiency),
3289
- kickToHandballRatio: toNullable(stats.extendedStats?.kickToHandballRatio),
3290
- pressureActs: toNullable(stats.extendedStats?.pressureActs),
3291
- defHalfPressureActs: toNullable(stats.extendedStats?.defHalfPressureActs),
3292
- spoils: toNullable(stats.extendedStats?.spoils),
3293
- hitoutsToAdvantage: toNullable(stats.extendedStats?.hitoutsToAdvantage),
3294
- hitoutWinPercentage: toNullable(stats.extendedStats?.hitoutWinPercentage),
3295
- hitoutToAdvantageRate: toNullable(stats.extendedStats?.hitoutToAdvantageRate),
3296
- groundBallGets: toNullable(stats.extendedStats?.groundBallGets),
3297
- f50GroundBallGets: toNullable(stats.extendedStats?.f50GroundBallGets),
3298
- interceptMarks: toNullable(stats.extendedStats?.interceptMarks),
3299
- marksOnLead: toNullable(stats.extendedStats?.marksOnLead),
3300
- contestedPossessionRate: toNullable(stats.extendedStats?.contestedPossessionRate),
3301
- contestOffOneOnOnes: toNullable(stats.extendedStats?.contestOffOneOnOnes),
3302
- contestOffWins: toNullable(stats.extendedStats?.contestOffWins),
3303
- contestOffWinsPercentage: toNullable(stats.extendedStats?.contestOffWinsPercentage),
3304
- contestDefOneOnOnes: toNullable(stats.extendedStats?.contestDefOneOnOnes),
3305
- contestDefLosses: toNullable(stats.extendedStats?.contestDefLosses),
3306
- contestDefLossPercentage: toNullable(stats.extendedStats?.contestDefLossPercentage),
3307
- centreBounceAttendances: toNullable(stats.extendedStats?.centreBounceAttendances),
3308
- kickins: toNullable(stats.extendedStats?.kickins),
3309
- kickinsPlayon: toNullable(stats.extendedStats?.kickinsPlayon),
3310
- ruckContests: toNullable(stats.extendedStats?.ruckContests),
3311
- scoreLaunches: toNullable(stats.extendedStats?.scoreLaunches),
3341
+ inside50s: toNullable(stats?.inside50s),
3342
+ rebound50s: toNullable(stats?.rebound50s),
3343
+ clangers: toNullable(stats?.clangers),
3344
+ turnovers: toNullable(stats?.turnovers),
3345
+ onePercenters: toNullable(stats?.onePercenters),
3346
+ bounces: toNullable(stats?.bounces),
3347
+ goalAssists: toNullable(stats?.goalAssists),
3348
+ disposalEfficiency: toNullable(stats?.disposalEfficiency),
3349
+ metresGained: toNullable(stats?.metresGained),
3350
+ goalAccuracy: toNullable(stats?.goalAccuracy),
3351
+ marksInside50: toNullable(stats?.marksInside50),
3352
+ tacklesInside50: toNullable(stats?.tacklesInside50),
3353
+ shotsAtGoal: toNullable(stats?.shotsAtGoal),
3354
+ scoreInvolvements: toNullable(stats?.scoreInvolvements),
3355
+ totalPossessions: toNullable(stats?.totalPossessions),
3356
+ timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
3357
+ ratingPoints: toNullable(stats?.ratingPoints),
3358
+ dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
3359
+ effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
3360
+ effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
3361
+ kickEfficiency: toNullable(stats?.extendedStats?.kickEfficiency),
3362
+ kickToHandballRatio: toNullable(stats?.extendedStats?.kickToHandballRatio),
3363
+ pressureActs: toNullable(stats?.extendedStats?.pressureActs),
3364
+ defHalfPressureActs: toNullable(stats?.extendedStats?.defHalfPressureActs),
3365
+ spoils: toNullable(stats?.extendedStats?.spoils),
3366
+ hitoutsToAdvantage: toNullable(stats?.extendedStats?.hitoutsToAdvantage),
3367
+ hitoutWinPercentage: toNullable(stats?.extendedStats?.hitoutWinPercentage),
3368
+ hitoutToAdvantageRate: toNullable(stats?.extendedStats?.hitoutToAdvantageRate),
3369
+ groundBallGets: toNullable(stats?.extendedStats?.groundBallGets),
3370
+ f50GroundBallGets: toNullable(stats?.extendedStats?.f50GroundBallGets),
3371
+ interceptMarks: toNullable(stats?.extendedStats?.interceptMarks),
3372
+ marksOnLead: toNullable(stats?.extendedStats?.marksOnLead),
3373
+ contestedPossessionRate: toNullable(stats?.extendedStats?.contestedPossessionRate),
3374
+ contestOffOneOnOnes: toNullable(stats?.extendedStats?.contestOffOneOnOnes),
3375
+ contestOffWins: toNullable(stats?.extendedStats?.contestOffWins),
3376
+ contestOffWinsPercentage: toNullable(stats?.extendedStats?.contestOffWinsPercentage),
3377
+ contestDefOneOnOnes: toNullable(stats?.extendedStats?.contestDefOneOnOnes),
3378
+ contestDefLosses: toNullable(stats?.extendedStats?.contestDefLosses),
3379
+ contestDefLossPercentage: toNullable(stats?.extendedStats?.contestDefLossPercentage),
3380
+ centreBounceAttendances: toNullable(stats?.extendedStats?.centreBounceAttendances),
3381
+ kickins: toNullable(stats?.extendedStats?.kickins),
3382
+ kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
3383
+ ruckContests: toNullable(stats?.extendedStats?.ruckContests),
3384
+ scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
3312
3385
  source
3313
3386
  };
3314
3387
  }
@@ -3391,6 +3464,7 @@ async function fetchPlayerStats(query) {
3391
3464
  return ok(allStats);
3392
3465
  }
3393
3466
  case "footywire": {
3467
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3394
3468
  const fwClient = new FootyWireClient();
3395
3469
  const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
3396
3470
  if (!idsResult.success) return idsResult;
@@ -3420,6 +3494,7 @@ async function fetchPlayerStats(query) {
3420
3494
  return ok(allStats);
3421
3495
  }
3422
3496
  case "afl-tables": {
3497
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3423
3498
  const atClient = new AflTablesClient();
3424
3499
  const atResult = await atClient.fetchSeasonPlayerStats(query.season);
3425
3500
  if (!atResult.success) return atResult;
@@ -3443,7 +3518,37 @@ async function fetchTeamStats(query) {
3443
3518
  }
3444
3519
  case "afl-tables": {
3445
3520
  const client = new AflTablesClient();
3446
- return client.fetchTeamStats(query.season);
3521
+ const statsResult = await client.fetchTeamStats(query.season);
3522
+ if (!statsResult.success) return statsResult;
3523
+ const needsGp = statsResult.data.some((e) => e.gamesPlayed === 0);
3524
+ const gpMap = /* @__PURE__ */ new Map();
3525
+ if (needsGp) {
3526
+ const resultsResult = await client.fetchSeasonResults(query.season);
3527
+ if (resultsResult.success) {
3528
+ for (const m of resultsResult.data) {
3529
+ gpMap.set(m.homeTeam, (gpMap.get(m.homeTeam) ?? 0) + 1);
3530
+ gpMap.set(m.awayTeam, (gpMap.get(m.awayTeam) ?? 0) + 1);
3531
+ }
3532
+ }
3533
+ }
3534
+ const enriched = statsResult.data.map((entry) => ({
3535
+ ...entry,
3536
+ gamesPlayed: gpMap.get(entry.team) ?? entry.gamesPlayed
3537
+ }));
3538
+ if (summaryType === "averages") {
3539
+ return ok(
3540
+ enriched.map((entry) => ({
3541
+ ...entry,
3542
+ stats: Object.fromEntries(
3543
+ Object.entries(entry.stats).map(([k, v]) => [
3544
+ k,
3545
+ entry.gamesPlayed > 0 ? +(v / entry.gamesPlayed).toFixed(1) : 0
3546
+ ])
3547
+ )
3548
+ }))
3549
+ );
3550
+ }
3551
+ return ok(enriched);
3447
3552
  }
3448
3553
  case "afl-api":
3449
3554
  case "squiggle":
@@ -3462,19 +3567,30 @@ async function fetchTeamStats(query) {
3462
3567
  function teamTypeForComp(comp) {
3463
3568
  return comp === "AFLW" ? "WOMEN" : "MEN";
3464
3569
  }
3465
- async function fetchTeams(query) {
3466
- const client = new AflApiClient();
3467
- const teamType = query?.teamType ?? teamTypeForComp(query?.competition ?? "AFLM");
3468
- const result = await client.fetchTeams(teamType);
3469
- if (!result.success) return result;
3470
- const competition = query?.competition ?? "AFLM";
3471
- const teams = result.data.map((t) => ({
3570
+ function toTeams(data, competition) {
3571
+ return data.map((t) => ({
3472
3572
  teamId: String(t.id),
3473
3573
  name: normaliseTeamName(t.name),
3474
3574
  abbreviation: t.abbreviation ?? "",
3475
3575
  competition
3476
3576
  })).filter((t) => AFL_SENIOR_TEAMS.has(t.name));
3477
- return ok(teams);
3577
+ }
3578
+ async function fetchTeams(query) {
3579
+ const client = new AflApiClient();
3580
+ if (!query?.competition && !query?.teamType) {
3581
+ const [menResult, womenResult] = await Promise.all([
3582
+ client.fetchTeams("MEN"),
3583
+ client.fetchTeams("WOMEN")
3584
+ ]);
3585
+ if (!menResult.success) return menResult;
3586
+ if (!womenResult.success) return womenResult;
3587
+ return ok([...toTeams(menResult.data, "AFLM"), ...toTeams(womenResult.data, "AFLW")]);
3588
+ }
3589
+ const competition = query?.competition ?? "AFLM";
3590
+ const teamType = query?.teamType ?? teamTypeForComp(competition);
3591
+ const result = await client.fetchTeams(teamType);
3592
+ if (!result.success) return result;
3593
+ return ok(toTeams(result.data, competition));
3478
3594
  }
3479
3595
  async function fetchSquad(query) {
3480
3596
  const client = new AflApiClient();
@@ -3495,8 +3611,8 @@ async function fetchSquad(query) {
3495
3611
  jumperNumber: p.jumperNumber ?? null,
3496
3612
  position: p.position ?? null,
3497
3613
  dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
3498
- heightCm: p.player.heightInCm ?? null,
3499
- weightKg: p.player.weightInKg ?? null,
3614
+ heightCm: p.player.heightInCm || null,
3615
+ weightKg: p.player.weightInKg || null,
3500
3616
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
3501
3617
  draftPosition: p.player.draftPosition ? Number.parseInt(p.player.draftPosition, 10) || null : null,
3502
3618
  draftType: p.player.draftType ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitzroy",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "TypeScript library and CLI for AFL data — match results, player stats, fixtures, ladders, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",