fitzroy 1.2.0 → 1.3.1

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,15 @@ 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
+ z.boolean().transform((b) => b ? 1 : 0)
1589
+ ]).nullable().optional();
1532
1590
  var PlayerGameStatsSchema = z.object({
1533
1591
  goals: statNum,
1534
1592
  behinds: statNum,
@@ -1606,8 +1664,8 @@ var PlayerStatsItemSchema = z.object({
1606
1664
  teamId: z.string(),
1607
1665
  playerStats: z.object({
1608
1666
  stats: PlayerGameStatsSchema,
1609
- timeOnGroundPercentage: z.number().nullable().optional()
1610
- }).passthrough()
1667
+ timeOnGroundPercentage: statNum
1668
+ }).passthrough().nullable().optional()
1611
1669
  }).passthrough();
1612
1670
  var PlayerStatsListSchema = z.object({
1613
1671
  homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
@@ -2284,12 +2342,14 @@ function toFixture(item, season, fallbackRoundNumber, competition) {
2284
2342
  async function fetchFixture(query) {
2285
2343
  const competition = query.competition ?? "AFLM";
2286
2344
  if (query.source === "squiggle") {
2345
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
2287
2346
  const client2 = new SquiggleClient();
2288
2347
  const result = await client2.fetchGames(query.season, query.round ?? void 0);
2289
2348
  if (!result.success) return result;
2290
2349
  return ok(transformSquiggleGamesToFixture(result.data.games, query.season));
2291
2350
  }
2292
2351
  if (query.source === "footywire") {
2352
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
2293
2353
  const fwClient = new FootyWireClient();
2294
2354
  const result = await fwClient.fetchSeasonFixture(query.season);
2295
2355
  if (!result.success) return result;
@@ -2633,6 +2693,7 @@ function parseSeasonPage(html, year) {
2633
2693
  const results = [];
2634
2694
  let currentRound = 0;
2635
2695
  let currentRoundType = "HomeAndAway";
2696
+ let lastHARound = 0;
2636
2697
  let matchCounter = 0;
2637
2698
  $("table").each((_i, table) => {
2638
2699
  const $table = $(table);
@@ -2642,10 +2703,14 @@ function parseSeasonPage(html, year) {
2642
2703
  if (roundMatch?.[1] && border !== "1") {
2643
2704
  currentRound = Number.parseInt(roundMatch[1], 10);
2644
2705
  currentRoundType = inferRoundType(text);
2706
+ if (currentRoundType === "HomeAndAway") {
2707
+ lastHARound = currentRound;
2708
+ }
2645
2709
  return;
2646
2710
  }
2647
2711
  if (border !== "1" && inferRoundType(text) === "Finals") {
2648
2712
  currentRoundType = "Finals";
2713
+ currentRound = finalsRoundNumber(text, lastHARound);
2649
2714
  return;
2650
2715
  }
2651
2716
  if (border !== "1") return;
@@ -2960,6 +3025,7 @@ function transformLadderEntries(entries) {
2960
3025
  async function fetchLadder(query) {
2961
3026
  const competition = query.competition ?? "AFLM";
2962
3027
  if (query.source === "squiggle") {
3028
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
2963
3029
  const client2 = new SquiggleClient();
2964
3030
  const result = await client2.fetchStandings(query.season, query.round ?? void 0);
2965
3031
  if (!result.success) return result;
@@ -2971,6 +3037,7 @@ async function fetchLadder(query) {
2971
3037
  });
2972
3038
  }
2973
3039
  if (query.source === "afl-tables") {
3040
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
2974
3041
  const atClient = new AflTablesClient();
2975
3042
  const resultsResult = await atClient.fetchSeasonResults(query.season);
2976
3043
  if (!resultsResult.success) return resultsResult;
@@ -3105,6 +3172,7 @@ async function fetchMatchResults(query) {
3105
3172
  return ok(transformMatchItems(itemsResult.data, query.season, competition));
3106
3173
  }
3107
3174
  case "footywire": {
3175
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3108
3176
  const client = new FootyWireClient();
3109
3177
  const result = await client.fetchSeasonResults(query.season);
3110
3178
  if (!result.success) return result;
@@ -3114,6 +3182,7 @@ async function fetchMatchResults(query) {
3114
3182
  return result;
3115
3183
  }
3116
3184
  case "afl-tables": {
3185
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3117
3186
  const client = new AflTablesClient();
3118
3187
  const result = await client.fetchSeasonResults(query.season);
3119
3188
  if (!result.success) return result;
@@ -3123,6 +3192,7 @@ async function fetchMatchResults(query) {
3123
3192
  return result;
3124
3193
  }
3125
3194
  case "squiggle": {
3195
+ if (competition === "AFLW") return err(aflwUnsupportedError("squiggle"));
3126
3196
  const client = new SquiggleClient();
3127
3197
  const result = await client.fetchGames(query.season, query.round ?? void 0, 100);
3128
3198
  if (!result.success) return result;
@@ -3148,7 +3218,7 @@ async function resolveTeamId(client, teamName, competition) {
3148
3218
  async function fetchFromAflApi(query) {
3149
3219
  const client = new AflApiClient();
3150
3220
  const competition = query.competition ?? "AFLM";
3151
- const season = query.season ?? (/* @__PURE__ */ new Date()).getFullYear();
3221
+ const season = query.season ?? resolveDefaultSeason(competition);
3152
3222
  const [teamIdResult, seasonResult] = await Promise.all([
3153
3223
  resolveTeamId(client, query.team, competition),
3154
3224
  client.resolveCompSeason(competition, season)
@@ -3171,8 +3241,8 @@ async function fetchFromAflApi(query) {
3171
3241
  jumperNumber: p.jumperNumber ?? null,
3172
3242
  position: p.position ?? null,
3173
3243
  dateOfBirth: p.player.dateOfBirth ?? null,
3174
- heightCm: p.player.heightInCm ?? null,
3175
- weightKg: p.player.weightInKg ?? null,
3244
+ heightCm: p.player.heightInCm || null,
3245
+ weightKg: p.player.weightInKg || null,
3176
3246
  gamesPlayed: null,
3177
3247
  goals: null,
3178
3248
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
@@ -3186,8 +3256,9 @@ async function fetchFromAflApi(query) {
3186
3256
  return ok(players);
3187
3257
  }
3188
3258
  async function fetchFromFootyWire(query) {
3189
- const client = new FootyWireClient();
3190
3259
  const competition = query.competition ?? "AFLM";
3260
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3261
+ const client = new FootyWireClient();
3191
3262
  const teamName = normaliseTeamName(query.team);
3192
3263
  const result = await client.fetchPlayerList(teamName);
3193
3264
  if (!result.success) return result;
@@ -3199,8 +3270,9 @@ async function fetchFromFootyWire(query) {
3199
3270
  return ok(players);
3200
3271
  }
3201
3272
  async function fetchFromAflTables(query) {
3202
- const client = new AflTablesClient();
3203
3273
  const competition = query.competition ?? "AFLM";
3274
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3275
+ const client = new AflTablesClient();
3204
3276
  const teamName = normaliseTeamName(query.team);
3205
3277
  const result = await client.fetchPlayerList(teamName);
3206
3278
  if (!result.success) return result;
@@ -3235,80 +3307,82 @@ function toNullable(value) {
3235
3307
  }
3236
3308
  function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
3237
3309
  const inner = item.player.player.player;
3238
- const stats = item.playerStats.stats;
3239
- const clearances = stats.clearances;
3310
+ const stats = item.playerStats?.stats;
3311
+ const clearances = stats?.clearances;
3240
3312
  return {
3241
3313
  matchId,
3242
3314
  season,
3243
3315
  roundNumber,
3244
- team: normaliseTeamName(teamIdMap?.get(item.teamId) ?? item.teamId),
3316
+ team: normaliseTeamName(
3317
+ teamIdMap?.get(item.teamId) ?? AFL_API_TEAM_IDS.get(item.teamId) ?? item.teamId
3318
+ ),
3245
3319
  competition,
3246
3320
  playerId: inner.playerId,
3247
3321
  givenName: inner.playerName.givenName,
3248
3322
  surname: inner.playerName.surname,
3249
3323
  displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
3250
3324
  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),
3325
+ kicks: toNullable(stats?.kicks),
3326
+ handballs: toNullable(stats?.handballs),
3327
+ disposals: toNullable(stats?.disposals),
3328
+ marks: toNullable(stats?.marks),
3329
+ goals: toNullable(stats?.goals),
3330
+ behinds: toNullable(stats?.behinds),
3331
+ tackles: toNullable(stats?.tackles),
3332
+ hitouts: toNullable(stats?.hitouts),
3333
+ freesFor: toNullable(stats?.freesFor),
3334
+ freesAgainst: toNullable(stats?.freesAgainst),
3335
+ contestedPossessions: toNullable(stats?.contestedPossessions),
3336
+ uncontestedPossessions: toNullable(stats?.uncontestedPossessions),
3337
+ contestedMarks: toNullable(stats?.contestedMarks),
3338
+ intercepts: toNullable(stats?.intercepts),
3265
3339
  centreClearances: toNullable(clearances?.centreClearances),
3266
3340
  stoppageClearances: toNullable(clearances?.stoppageClearances),
3267
3341
  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),
3342
+ inside50s: toNullable(stats?.inside50s),
3343
+ rebound50s: toNullable(stats?.rebound50s),
3344
+ clangers: toNullable(stats?.clangers),
3345
+ turnovers: toNullable(stats?.turnovers),
3346
+ onePercenters: toNullable(stats?.onePercenters),
3347
+ bounces: toNullable(stats?.bounces),
3348
+ goalAssists: toNullable(stats?.goalAssists),
3349
+ disposalEfficiency: toNullable(stats?.disposalEfficiency),
3350
+ metresGained: toNullable(stats?.metresGained),
3351
+ goalAccuracy: toNullable(stats?.goalAccuracy),
3352
+ marksInside50: toNullable(stats?.marksInside50),
3353
+ tacklesInside50: toNullable(stats?.tacklesInside50),
3354
+ shotsAtGoal: toNullable(stats?.shotsAtGoal),
3355
+ scoreInvolvements: toNullable(stats?.scoreInvolvements),
3356
+ totalPossessions: toNullable(stats?.totalPossessions),
3357
+ timeOnGroundPercentage: toNullable(item.playerStats?.timeOnGroundPercentage),
3358
+ ratingPoints: toNullable(stats?.ratingPoints),
3359
+ dreamTeamPoints: toNullable(stats?.dreamTeamPoints),
3360
+ effectiveDisposals: toNullable(stats?.extendedStats?.effectiveDisposals),
3361
+ effectiveKicks: toNullable(stats?.extendedStats?.effectiveKicks),
3362
+ kickEfficiency: toNullable(stats?.extendedStats?.kickEfficiency),
3363
+ kickToHandballRatio: toNullable(stats?.extendedStats?.kickToHandballRatio),
3364
+ pressureActs: toNullable(stats?.extendedStats?.pressureActs),
3365
+ defHalfPressureActs: toNullable(stats?.extendedStats?.defHalfPressureActs),
3366
+ spoils: toNullable(stats?.extendedStats?.spoils),
3367
+ hitoutsToAdvantage: toNullable(stats?.extendedStats?.hitoutsToAdvantage),
3368
+ hitoutWinPercentage: toNullable(stats?.extendedStats?.hitoutWinPercentage),
3369
+ hitoutToAdvantageRate: toNullable(stats?.extendedStats?.hitoutToAdvantageRate),
3370
+ groundBallGets: toNullable(stats?.extendedStats?.groundBallGets),
3371
+ f50GroundBallGets: toNullable(stats?.extendedStats?.f50GroundBallGets),
3372
+ interceptMarks: toNullable(stats?.extendedStats?.interceptMarks),
3373
+ marksOnLead: toNullable(stats?.extendedStats?.marksOnLead),
3374
+ contestedPossessionRate: toNullable(stats?.extendedStats?.contestedPossessionRate),
3375
+ contestOffOneOnOnes: toNullable(stats?.extendedStats?.contestOffOneOnOnes),
3376
+ contestOffWins: toNullable(stats?.extendedStats?.contestOffWins),
3377
+ contestOffWinsPercentage: toNullable(stats?.extendedStats?.contestOffWinsPercentage),
3378
+ contestDefOneOnOnes: toNullable(stats?.extendedStats?.contestDefOneOnOnes),
3379
+ contestDefLosses: toNullable(stats?.extendedStats?.contestDefLosses),
3380
+ contestDefLossPercentage: toNullable(stats?.extendedStats?.contestDefLossPercentage),
3381
+ centreBounceAttendances: toNullable(stats?.extendedStats?.centreBounceAttendances),
3382
+ kickins: toNullable(stats?.extendedStats?.kickins),
3383
+ kickinsPlayon: toNullable(stats?.extendedStats?.kickinsPlayon),
3384
+ ruckContests: toNullable(stats?.extendedStats?.ruckContests),
3385
+ scoreLaunches: toNullable(stats?.extendedStats?.scoreLaunches),
3312
3386
  source
3313
3387
  };
3314
3388
  }
@@ -3334,11 +3408,11 @@ async function fetchPlayerStats(query) {
3334
3408
  client.fetchPlayerStats(query.matchId)
3335
3409
  ]);
3336
3410
  if (!statsResult.success) return statsResult;
3337
- const teamIdMap2 = /* @__PURE__ */ new Map();
3411
+ const teamIdMap2 = new Map(AFL_API_TEAM_IDS);
3338
3412
  if (rosterResult.success) {
3339
3413
  const match = rosterResult.data.match;
3340
- teamIdMap2.set(match.homeTeamId, match.homeTeam.name);
3341
- teamIdMap2.set(match.awayTeamId, match.awayTeam.name);
3414
+ teamIdMap2.set(match.homeTeamId, normaliseTeamName(match.homeTeam.name));
3415
+ teamIdMap2.set(match.awayTeamId, normaliseTeamName(match.awayTeam.name));
3342
3416
  }
3343
3417
  return ok(
3344
3418
  transformPlayerStats(
@@ -3348,7 +3422,7 @@ async function fetchPlayerStats(query) {
3348
3422
  query.round ?? 0,
3349
3423
  competition,
3350
3424
  "afl-api",
3351
- teamIdMap2.size > 0 ? teamIdMap2 : void 0
3425
+ teamIdMap2
3352
3426
  )
3353
3427
  );
3354
3428
  }
@@ -3391,6 +3465,7 @@ async function fetchPlayerStats(query) {
3391
3465
  return ok(allStats);
3392
3466
  }
3393
3467
  case "footywire": {
3468
+ if (competition === "AFLW") return err(aflwUnsupportedError("footywire"));
3394
3469
  const fwClient = new FootyWireClient();
3395
3470
  const idsResult = await fwClient.fetchSeasonMatchIds(query.season);
3396
3471
  if (!idsResult.success) return idsResult;
@@ -3420,6 +3495,7 @@ async function fetchPlayerStats(query) {
3420
3495
  return ok(allStats);
3421
3496
  }
3422
3497
  case "afl-tables": {
3498
+ if (competition === "AFLW") return err(aflwUnsupportedError("afl-tables"));
3423
3499
  const atClient = new AflTablesClient();
3424
3500
  const atResult = await atClient.fetchSeasonPlayerStats(query.season);
3425
3501
  if (!atResult.success) return atResult;
@@ -3443,7 +3519,39 @@ async function fetchTeamStats(query) {
3443
3519
  }
3444
3520
  case "afl-tables": {
3445
3521
  const client = new AflTablesClient();
3446
- return client.fetchTeamStats(query.season);
3522
+ const statsResult = await client.fetchTeamStats(query.season);
3523
+ if (!statsResult.success) return statsResult;
3524
+ const needsGp = statsResult.data.some((e) => e.gamesPlayed === 0);
3525
+ const gpMap = /* @__PURE__ */ new Map();
3526
+ if (needsGp) {
3527
+ const resultsResult = await client.fetchSeasonResults(query.season);
3528
+ if (resultsResult.success) {
3529
+ for (const m of resultsResult.data) {
3530
+ const home = normaliseTeamName(m.homeTeam);
3531
+ const away = normaliseTeamName(m.awayTeam);
3532
+ gpMap.set(home, (gpMap.get(home) ?? 0) + 1);
3533
+ gpMap.set(away, (gpMap.get(away) ?? 0) + 1);
3534
+ }
3535
+ }
3536
+ }
3537
+ const enriched = statsResult.data.map((entry) => ({
3538
+ ...entry,
3539
+ gamesPlayed: gpMap.get(normaliseTeamName(entry.team)) ?? entry.gamesPlayed
3540
+ }));
3541
+ if (summaryType === "averages") {
3542
+ return ok(
3543
+ enriched.map((entry) => ({
3544
+ ...entry,
3545
+ stats: Object.fromEntries(
3546
+ Object.entries(entry.stats).map(([k, v]) => [
3547
+ k,
3548
+ entry.gamesPlayed > 0 ? +(v / entry.gamesPlayed).toFixed(1) : 0
3549
+ ])
3550
+ )
3551
+ }))
3552
+ );
3553
+ }
3554
+ return ok(enriched);
3447
3555
  }
3448
3556
  case "afl-api":
3449
3557
  case "squiggle":
@@ -3462,19 +3570,30 @@ async function fetchTeamStats(query) {
3462
3570
  function teamTypeForComp(comp) {
3463
3571
  return comp === "AFLW" ? "WOMEN" : "MEN";
3464
3572
  }
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) => ({
3573
+ function toTeams(data, competition) {
3574
+ return data.map((t) => ({
3472
3575
  teamId: String(t.id),
3473
3576
  name: normaliseTeamName(t.name),
3474
3577
  abbreviation: t.abbreviation ?? "",
3475
3578
  competition
3476
3579
  })).filter((t) => AFL_SENIOR_TEAMS.has(t.name));
3477
- return ok(teams);
3580
+ }
3581
+ async function fetchTeams(query) {
3582
+ const client = new AflApiClient();
3583
+ if (!query?.competition && !query?.teamType) {
3584
+ const [menResult, womenResult] = await Promise.all([
3585
+ client.fetchTeams("MEN"),
3586
+ client.fetchTeams("WOMEN")
3587
+ ]);
3588
+ if (!menResult.success) return menResult;
3589
+ if (!womenResult.success) return womenResult;
3590
+ return ok([...toTeams(menResult.data, "AFLM"), ...toTeams(womenResult.data, "AFLW")]);
3591
+ }
3592
+ const competition = query?.competition ?? "AFLM";
3593
+ const teamType = query?.teamType ?? teamTypeForComp(competition);
3594
+ const result = await client.fetchTeams(teamType);
3595
+ if (!result.success) return result;
3596
+ return ok(toTeams(result.data, competition));
3478
3597
  }
3479
3598
  async function fetchSquad(query) {
3480
3599
  const client = new AflApiClient();
@@ -3495,8 +3614,8 @@ async function fetchSquad(query) {
3495
3614
  jumperNumber: p.jumperNumber ?? null,
3496
3615
  position: p.position ?? null,
3497
3616
  dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
3498
- heightCm: p.player.heightInCm ?? null,
3499
- weightKg: p.player.weightInKg ?? null,
3617
+ heightCm: p.player.heightInCm || null,
3618
+ weightKg: p.player.weightInKg || null,
3500
3619
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
3501
3620
  draftPosition: p.player.draftPosition ? Number.parseInt(p.player.draftPosition, 10) || null : null,
3502
3621
  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.1",
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",