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