fitzroy 1.1.0 → 1.1.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
@@ -47,35 +47,40 @@ function parseAflApiDate(iso) {
47
47
  }
48
48
  return date;
49
49
  }
50
- function parseFootyWireDate(dateStr) {
50
+ function parseFootyWireDate(dateStr, defaultYear) {
51
51
  const trimmed = dateStr.trim();
52
52
  if (trimmed === "") {
53
53
  return null;
54
54
  }
55
55
  const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
56
56
  const normalised = withoutDow.replace(/-/g, " ");
57
- const match = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
58
- if (!match) {
59
- return null;
60
- }
61
- const [, dayStr, monthStr, yearStr] = match;
62
- if (!dayStr || !monthStr || !yearStr) {
63
- return null;
64
- }
65
- const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
66
- if (monthIndex === void 0) {
67
- return null;
68
- }
69
- const year = Number.parseInt(yearStr, 10);
70
- const day = Number.parseInt(dayStr, 10);
71
- const date = new Date(Date.UTC(year, monthIndex, day));
72
- if (Number.isNaN(date.getTime())) {
73
- return null;
57
+ const fullMatch = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
58
+ if (fullMatch) {
59
+ const [, dayStr, monthStr, yearStr] = fullMatch;
60
+ if (dayStr && monthStr && yearStr) {
61
+ return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
62
+ }
74
63
  }
75
- if (date.getUTCFullYear() !== year || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== day) {
76
- return null;
64
+ const shortMatch = /^(\d{1,2})\s+([A-Za-z]+)(?:\s+(\d{1,2}):(\d{2})(am|pm))?$/i.exec(normalised);
65
+ if (shortMatch && defaultYear != null) {
66
+ const [, dayStr, monthStr, hourStr, minStr, ampm] = shortMatch;
67
+ if (!dayStr || !monthStr) return null;
68
+ const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
69
+ if (monthIndex === void 0) return null;
70
+ const day = Number.parseInt(dayStr, 10);
71
+ const hasTime = hourStr && minStr && ampm;
72
+ if (!hasTime) {
73
+ return buildUtcDate(defaultYear, monthStr, day);
74
+ }
75
+ let aestHours = Number.parseInt(hourStr, 10);
76
+ const minutes = Number.parseInt(minStr, 10);
77
+ if (ampm.toLowerCase() === "pm" && aestHours < 12) aestHours += 12;
78
+ if (ampm.toLowerCase() === "am" && aestHours === 12) aestHours = 0;
79
+ const date = new Date(Date.UTC(defaultYear, monthIndex, day, aestHours - 10, minutes));
80
+ if (Number.isNaN(date.getTime())) return null;
81
+ return date;
77
82
  }
78
- return date;
83
+ return null;
79
84
  }
80
85
  function parseAflTablesDate(dateStr) {
81
86
  const trimmed = dateStr.trim();
@@ -810,7 +815,7 @@ function parseMatchList(html, year) {
810
815
  const scoreLink = scoreCell.find("a").attr("href") ?? "";
811
816
  const midMatch = /mid=(\d+)/.exec(scoreLink);
812
817
  const matchId = midMatch?.[1] ? `FW_${midMatch[1]}` : `FW_${year}_R${currentRound}_${homeTeam}`;
813
- const date = parseFootyWireDate(dateText) ?? new Date(year, 0, 1);
818
+ const date = parseFootyWireDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
814
819
  const homeGoals = Math.floor(homePoints / 6);
815
820
  const homeBehinds = homePoints - homeGoals * 6;
816
821
  const awayGoals = Math.floor(awayPoints / 6);
@@ -880,7 +885,7 @@ function parseFixtureList(html, year) {
880
885
  if (teamLinks.length < 2) return;
881
886
  const homeTeam = normaliseTeamName($(teamLinks[0]).text().trim());
882
887
  const awayTeam = normaliseTeamName($(teamLinks[1]).text().trim());
883
- const date = parseFootyWireDate(dateText) ?? new Date(year, 0, 1);
888
+ const date = parseFootyWireDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
884
889
  gameNumber++;
885
890
  const scoreCell = cells.length >= 5 ? $(cells[4]) : null;
886
891
  const scoreText = scoreCell?.text().trim() ?? "";
@@ -988,6 +993,12 @@ var FOOTYWIRE_SLUG_MAP = /* @__PURE__ */ new Map([
988
993
  function teamNameToFootyWireSlug(teamName) {
989
994
  return FOOTYWIRE_SLUG_MAP.get(teamName);
990
995
  }
996
+ function normaliseDob(raw) {
997
+ if (!raw) return null;
998
+ const parsed = parseFootyWireDate(raw);
999
+ if (parsed) return parsed.toISOString().slice(0, 10);
1000
+ return raw;
1001
+ }
991
1002
  function parseFootyWirePlayerList(html, teamName) {
992
1003
  const $ = cheerio2.load(html);
993
1004
  const players = [];
@@ -1029,7 +1040,7 @@ function parseFootyWirePlayerList(html, teamName) {
1029
1040
  team: teamName,
1030
1041
  jumperNumber,
1031
1042
  position: position || null,
1032
- dateOfBirth: dobText || null,
1043
+ dateOfBirth: normaliseDob(dobText),
1033
1044
  heightCm,
1034
1045
  weightKg: null,
1035
1046
  gamesPlayed,
@@ -1501,71 +1512,72 @@ var CfsPlayerInnerSchema = z.object({
1501
1512
  captain: z.boolean().optional(),
1502
1513
  playerJumperNumber: z.number().optional()
1503
1514
  }).passthrough();
1515
+ var statNum = z.number().nullable().optional();
1504
1516
  var PlayerGameStatsSchema = z.object({
1505
- goals: z.number().optional(),
1506
- behinds: z.number().optional(),
1507
- kicks: z.number().optional(),
1508
- handballs: z.number().optional(),
1509
- disposals: z.number().optional(),
1510
- marks: z.number().optional(),
1511
- bounces: z.number().optional(),
1512
- tackles: z.number().optional(),
1513
- contestedPossessions: z.number().optional(),
1514
- uncontestedPossessions: z.number().optional(),
1515
- totalPossessions: z.number().optional(),
1516
- inside50s: z.number().optional(),
1517
- marksInside50: z.number().optional(),
1518
- contestedMarks: z.number().optional(),
1519
- hitouts: z.number().optional(),
1520
- onePercenters: z.number().optional(),
1521
- disposalEfficiency: z.number().optional(),
1522
- clangers: z.number().optional(),
1523
- freesFor: z.number().optional(),
1524
- freesAgainst: z.number().optional(),
1525
- dreamTeamPoints: z.number().optional(),
1517
+ goals: statNum,
1518
+ behinds: statNum,
1519
+ kicks: statNum,
1520
+ handballs: statNum,
1521
+ disposals: statNum,
1522
+ marks: statNum,
1523
+ bounces: statNum,
1524
+ tackles: statNum,
1525
+ contestedPossessions: statNum,
1526
+ uncontestedPossessions: statNum,
1527
+ totalPossessions: statNum,
1528
+ inside50s: statNum,
1529
+ marksInside50: statNum,
1530
+ contestedMarks: statNum,
1531
+ hitouts: statNum,
1532
+ onePercenters: statNum,
1533
+ disposalEfficiency: statNum,
1534
+ clangers: statNum,
1535
+ freesFor: statNum,
1536
+ freesAgainst: statNum,
1537
+ dreamTeamPoints: statNum,
1526
1538
  clearances: z.object({
1527
- centreClearances: z.number().optional(),
1528
- stoppageClearances: z.number().optional(),
1529
- totalClearances: z.number().optional()
1530
- }).passthrough().optional(),
1531
- rebound50s: z.number().optional(),
1532
- goalAssists: z.number().optional(),
1533
- goalAccuracy: z.number().optional(),
1534
- turnovers: z.number().optional(),
1535
- intercepts: z.number().optional(),
1536
- tacklesInside50: z.number().optional(),
1537
- shotsAtGoal: z.number().optional(),
1538
- metresGained: z.number().optional(),
1539
- scoreInvolvements: z.number().optional(),
1540
- ratingPoints: z.number().optional(),
1539
+ centreClearances: statNum,
1540
+ stoppageClearances: statNum,
1541
+ totalClearances: statNum
1542
+ }).passthrough().nullable().optional(),
1543
+ rebound50s: statNum,
1544
+ goalAssists: statNum,
1545
+ goalAccuracy: statNum,
1546
+ turnovers: statNum,
1547
+ intercepts: statNum,
1548
+ tacklesInside50: statNum,
1549
+ shotsAtGoal: statNum,
1550
+ metresGained: statNum,
1551
+ scoreInvolvements: statNum,
1552
+ ratingPoints: statNum,
1541
1553
  extendedStats: z.object({
1542
- effectiveDisposals: z.number().optional(),
1543
- effectiveKicks: z.number().optional(),
1544
- kickEfficiency: z.number().optional(),
1545
- kickToHandballRatio: z.number().optional(),
1546
- pressureActs: z.number().optional(),
1547
- defHalfPressureActs: z.number().optional(),
1548
- spoils: z.number().optional(),
1549
- hitoutsToAdvantage: z.number().optional(),
1550
- hitoutWinPercentage: z.number().optional(),
1551
- hitoutToAdvantageRate: z.number().optional(),
1552
- groundBallGets: z.number().optional(),
1553
- f50GroundBallGets: z.number().optional(),
1554
- interceptMarks: z.number().optional(),
1555
- marksOnLead: z.number().optional(),
1556
- contestedPossessionRate: z.number().optional(),
1557
- contestOffOneOnOnes: z.number().optional(),
1558
- contestOffWins: z.number().optional(),
1559
- contestOffWinsPercentage: z.number().optional(),
1560
- contestDefOneOnOnes: z.number().optional(),
1561
- contestDefLosses: z.number().optional(),
1562
- contestDefLossPercentage: z.number().optional(),
1563
- centreBounceAttendances: z.number().optional(),
1564
- kickins: z.number().optional(),
1565
- kickinsPlayon: z.number().optional(),
1566
- ruckContests: z.number().optional(),
1567
- scoreLaunches: z.number().optional()
1568
- }).passthrough().optional()
1554
+ effectiveDisposals: statNum,
1555
+ effectiveKicks: statNum,
1556
+ kickEfficiency: statNum,
1557
+ kickToHandballRatio: statNum,
1558
+ pressureActs: statNum,
1559
+ defHalfPressureActs: statNum,
1560
+ spoils: statNum,
1561
+ hitoutsToAdvantage: statNum,
1562
+ hitoutWinPercentage: statNum,
1563
+ hitoutToAdvantageRate: statNum,
1564
+ groundBallGets: statNum,
1565
+ f50GroundBallGets: statNum,
1566
+ interceptMarks: statNum,
1567
+ marksOnLead: statNum,
1568
+ contestedPossessionRate: statNum,
1569
+ contestOffOneOnOnes: statNum,
1570
+ contestOffWins: statNum,
1571
+ contestOffWinsPercentage: statNum,
1572
+ contestDefOneOnOnes: statNum,
1573
+ contestDefLosses: statNum,
1574
+ contestDefLossPercentage: statNum,
1575
+ centreBounceAttendances: statNum,
1576
+ kickins: statNum,
1577
+ kickinsPlayon: statNum,
1578
+ ruckContests: statNum,
1579
+ scoreLaunches: statNum
1580
+ }).passthrough().nullable().optional()
1569
1581
  }).passthrough();
1570
1582
  var PlayerStatsItemSchema = z.object({
1571
1583
  player: z.object({
@@ -1578,7 +1590,7 @@ var PlayerStatsItemSchema = z.object({
1578
1590
  teamId: z.string(),
1579
1591
  playerStats: z.object({
1580
1592
  stats: PlayerGameStatsSchema,
1581
- timeOnGroundPercentage: z.number().optional()
1593
+ timeOnGroundPercentage: z.number().nullable().optional()
1582
1594
  }).passthrough()
1583
1595
  }).passthrough();
1584
1596
  var PlayerStatsListSchema = z.object({
@@ -2696,6 +2708,7 @@ function parseAttendanceFromInfo(text) {
2696
2708
  if (!match?.[1]) return null;
2697
2709
  return Number.parseInt(match[1].replace(/,/g, ""), 10) || null;
2698
2710
  }
2711
+ var GP_HEADERS = /* @__PURE__ */ new Set(["gm", "gp", "p", "mp", "games"]);
2699
2712
  function parseAflTablesTeamStats(html, year) {
2700
2713
  const $ = cheerio6.load(html);
2701
2714
  const teamMap = /* @__PURE__ */ new Map();
@@ -2709,6 +2722,7 @@ function parseAflTablesTeamStats(html, year) {
2709
2722
  $(rows[0]).find("td, th").each((_ci, cell) => {
2710
2723
  headers.push($(cell).text().trim());
2711
2724
  });
2725
+ const gpColIdx = headers.findIndex((h, i) => i > 0 && GP_HEADERS.has(h.toLowerCase()));
2712
2726
  for (let ri = 1; ri < rows.length; ri++) {
2713
2727
  const cells = $(rows[ri]).find("td");
2714
2728
  if (cells.length < 3) continue;
@@ -2721,7 +2735,12 @@ function parseAflTablesTeamStats(html, year) {
2721
2735
  }
2722
2736
  const entry = teamMap.get(teamName);
2723
2737
  if (!entry) continue;
2738
+ if (gpColIdx >= 0 && suffix === "_for") {
2739
+ const gpVal = Number.parseFloat($(cells[gpColIdx]).text().trim().replace(/,/g, "")) || 0;
2740
+ entry.gamesPlayed = gpVal;
2741
+ }
2724
2742
  for (let ci = 1; ci < cells.length; ci++) {
2743
+ if (ci === gpColIdx) continue;
2725
2744
  const header = headers[ci];
2726
2745
  if (!header) continue;
2727
2746
  const value = Number.parseFloat($(cells[ci]).text().trim().replace(/,/g, "")) || 0;
@@ -3279,15 +3298,26 @@ async function fetchPlayerStats(query) {
3279
3298
  case "afl-api": {
3280
3299
  const client = new AflApiClient();
3281
3300
  if (query.matchId) {
3282
- const result = await client.fetchPlayerStats(query.matchId);
3283
- if (!result.success) return result;
3301
+ const [rosterResult, statsResult] = await Promise.all([
3302
+ client.fetchMatchRoster(query.matchId),
3303
+ client.fetchPlayerStats(query.matchId)
3304
+ ]);
3305
+ if (!statsResult.success) return statsResult;
3306
+ const teamIdMap2 = /* @__PURE__ */ new Map();
3307
+ if (rosterResult.success) {
3308
+ const match = rosterResult.data.match;
3309
+ teamIdMap2.set(match.homeTeamId, match.homeTeam.name);
3310
+ teamIdMap2.set(match.awayTeamId, match.awayTeam.name);
3311
+ }
3284
3312
  return ok(
3285
3313
  transformPlayerStats(
3286
- result.data,
3314
+ statsResult.data,
3287
3315
  query.matchId,
3288
3316
  query.season,
3289
3317
  query.round ?? 0,
3290
- competition
3318
+ competition,
3319
+ "afl-api",
3320
+ teamIdMap2.size > 0 ? teamIdMap2 : void 0
3291
3321
  )
3292
3322
  );
3293
3323
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitzroy",
3
- "version": "1.1.0",
3
+ "version": "1.1.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",