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