fitzroy 1.6.1 → 1.7.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 +401 -7
- package/dist/index.d.ts +41 -6
- package/dist/index.js +374 -14
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1827,7 +1827,7 @@ var init_validation = __esm({
|
|
|
1827
1827
|
});
|
|
1828
1828
|
|
|
1829
1829
|
// src/sources/afl-api.ts
|
|
1830
|
-
var TOKEN_URL, API_BASE, CFS_BASE, AflApiClient;
|
|
1830
|
+
var USER_AGENT, TOKEN_URL, API_BASE, CFS_BASE, AflApiClient;
|
|
1831
1831
|
var init_afl_api = __esm({
|
|
1832
1832
|
"src/sources/afl-api.ts"() {
|
|
1833
1833
|
"use strict";
|
|
@@ -1835,6 +1835,7 @@ var init_afl_api = __esm({
|
|
|
1835
1835
|
init_errors();
|
|
1836
1836
|
init_result();
|
|
1837
1837
|
init_validation();
|
|
1838
|
+
USER_AGENT = "fitzroy/2 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
1838
1839
|
TOKEN_URL = "https://api.afl.com.au/cfs/afl/WMCTok";
|
|
1839
1840
|
API_BASE = "https://aflapi.afl.com.au/afl/v2";
|
|
1840
1841
|
CFS_BASE = "https://api.afl.com.au/cfs/afl";
|
|
@@ -1844,7 +1845,14 @@ var init_afl_api = __esm({
|
|
|
1844
1845
|
cachedToken = null;
|
|
1845
1846
|
pendingAuth = null;
|
|
1846
1847
|
constructor(options) {
|
|
1847
|
-
|
|
1848
|
+
const baseFetch = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
1849
|
+
this.fetchFn = (input, init) => {
|
|
1850
|
+
const headers = new Headers(init?.headers);
|
|
1851
|
+
if (!headers.has("User-Agent")) {
|
|
1852
|
+
headers.set("User-Agent", USER_AGENT);
|
|
1853
|
+
}
|
|
1854
|
+
return baseFetch(input, { ...init, headers });
|
|
1855
|
+
};
|
|
1848
1856
|
this.tokenUrl = options?.tokenUrl ?? TOKEN_URL;
|
|
1849
1857
|
}
|
|
1850
1858
|
/**
|
|
@@ -2261,7 +2269,7 @@ var init_squiggle_validation = __esm({
|
|
|
2261
2269
|
});
|
|
2262
2270
|
|
|
2263
2271
|
// src/sources/squiggle.ts
|
|
2264
|
-
var SQUIGGLE_BASE,
|
|
2272
|
+
var SQUIGGLE_BASE, USER_AGENT2, SquiggleClient;
|
|
2265
2273
|
var init_squiggle = __esm({
|
|
2266
2274
|
"src/sources/squiggle.ts"() {
|
|
2267
2275
|
"use strict";
|
|
@@ -2269,7 +2277,7 @@ var init_squiggle = __esm({
|
|
|
2269
2277
|
init_result();
|
|
2270
2278
|
init_squiggle_validation();
|
|
2271
2279
|
SQUIGGLE_BASE = "https://api.squiggle.com.au/";
|
|
2272
|
-
|
|
2280
|
+
USER_AGENT2 = "fitzRoy-ts/1.0 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
2273
2281
|
SquiggleClient = class {
|
|
2274
2282
|
fetchFn;
|
|
2275
2283
|
constructor(options) {
|
|
@@ -2282,7 +2290,7 @@ var init_squiggle = __esm({
|
|
|
2282
2290
|
const url = `${SQUIGGLE_BASE}?${params.toString()}`;
|
|
2283
2291
|
try {
|
|
2284
2292
|
const response = await this.fetchFn(url, {
|
|
2285
|
-
headers: { "User-Agent":
|
|
2293
|
+
headers: { "User-Agent": USER_AGENT2 }
|
|
2286
2294
|
});
|
|
2287
2295
|
if (!response.ok) {
|
|
2288
2296
|
return err(
|
|
@@ -3555,6 +3563,374 @@ var init_player_details = __esm({
|
|
|
3555
3563
|
}
|
|
3556
3564
|
});
|
|
3557
3565
|
|
|
3566
|
+
// src/sources/fryzigg.ts
|
|
3567
|
+
import { isDataFrame, parseRds, RdsError } from "@jackemcpherson/rds-js";
|
|
3568
|
+
var FRYZIGG_URLS, USER_AGENT3, FryziggClient;
|
|
3569
|
+
var init_fryzigg = __esm({
|
|
3570
|
+
"src/sources/fryzigg.ts"() {
|
|
3571
|
+
"use strict";
|
|
3572
|
+
init_errors();
|
|
3573
|
+
init_result();
|
|
3574
|
+
FRYZIGG_URLS = {
|
|
3575
|
+
AFLM: "http://www.fryziggafl.net/static/fryziggafl.rds",
|
|
3576
|
+
AFLW: "http://www.fryziggafl.net/static/aflw_player_stats.rds"
|
|
3577
|
+
};
|
|
3578
|
+
USER_AGENT3 = "fitzRoy-ts/1.0 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
3579
|
+
FryziggClient = class {
|
|
3580
|
+
fetchFn;
|
|
3581
|
+
constructor(options) {
|
|
3582
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
3583
|
+
}
|
|
3584
|
+
/**
|
|
3585
|
+
* Fetch the full player statistics dataset for a competition.
|
|
3586
|
+
*
|
|
3587
|
+
* Returns column-major DataFrame from rds-js. The caller is responsible
|
|
3588
|
+
* for filtering rows and mapping to domain types.
|
|
3589
|
+
*
|
|
3590
|
+
* @param competition - AFLM or AFLW.
|
|
3591
|
+
* @returns Column-major DataFrame with all rows, or an error.
|
|
3592
|
+
*/
|
|
3593
|
+
async fetchPlayerStats(competition) {
|
|
3594
|
+
const url = FRYZIGG_URLS[competition];
|
|
3595
|
+
try {
|
|
3596
|
+
const response = await this.fetchFn(url, {
|
|
3597
|
+
headers: { "User-Agent": USER_AGENT3 }
|
|
3598
|
+
});
|
|
3599
|
+
if (!response.ok) {
|
|
3600
|
+
return err(
|
|
3601
|
+
new ScrapeError(`Fryzigg request failed: ${response.status} (${url})`, "fryzigg")
|
|
3602
|
+
);
|
|
3603
|
+
}
|
|
3604
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
3605
|
+
const result = await parseRds(buffer);
|
|
3606
|
+
if (!isDataFrame(result)) {
|
|
3607
|
+
return err(new ScrapeError("Fryzigg RDS file did not contain a data frame", "fryzigg"));
|
|
3608
|
+
}
|
|
3609
|
+
return ok(result);
|
|
3610
|
+
} catch (cause) {
|
|
3611
|
+
if (cause instanceof RdsError) {
|
|
3612
|
+
return err(new ScrapeError(`Fryzigg RDS parse error: ${cause.message}`, "fryzigg"));
|
|
3613
|
+
}
|
|
3614
|
+
return err(
|
|
3615
|
+
new ScrapeError(
|
|
3616
|
+
`Fryzigg request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
3617
|
+
"fryzigg"
|
|
3618
|
+
)
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
});
|
|
3625
|
+
|
|
3626
|
+
// src/transforms/fryzigg-player-stats.ts
|
|
3627
|
+
function transformFryziggPlayerStats(frame, options) {
|
|
3628
|
+
const colIndex = /* @__PURE__ */ new Map();
|
|
3629
|
+
for (let i = 0; i < frame.names.length; i++) {
|
|
3630
|
+
const name = frame.names[i];
|
|
3631
|
+
if (name !== void 0) {
|
|
3632
|
+
colIndex.set(name, i);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
for (const group of REQUIRED_COLUMN_GROUPS) {
|
|
3636
|
+
if (!group.some((name) => colIndex.has(name))) {
|
|
3637
|
+
return err(
|
|
3638
|
+
new ScrapeError(
|
|
3639
|
+
`Fryzigg data frame missing required column: "${group.join('" or "')}"`,
|
|
3640
|
+
"fryzigg"
|
|
3641
|
+
)
|
|
3642
|
+
);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
const getCol = (name) => {
|
|
3646
|
+
if (name === void 0) return void 0;
|
|
3647
|
+
const idx = colIndex.get(name);
|
|
3648
|
+
if (idx === void 0) return void 0;
|
|
3649
|
+
return frame.columns[idx];
|
|
3650
|
+
};
|
|
3651
|
+
const isAflw = options.competition === "AFLW";
|
|
3652
|
+
const mapping = isAflw ? AFLW_COLUMNS : AFLM_COLUMNS;
|
|
3653
|
+
const cols = {
|
|
3654
|
+
matchId: getCol("match_id"),
|
|
3655
|
+
date: getCol(mapping.date),
|
|
3656
|
+
homeTeam: getCol(mapping.homeTeam),
|
|
3657
|
+
awayTeam: getCol(mapping.awayTeam),
|
|
3658
|
+
team: getCol(mapping.team),
|
|
3659
|
+
round: getCol(mapping.round),
|
|
3660
|
+
jumperNumber: getCol(mapping.jumperNumber),
|
|
3661
|
+
playerId: getCol("player_id"),
|
|
3662
|
+
firstName: getCol(mapping.firstName),
|
|
3663
|
+
lastName: getCol(mapping.lastName),
|
|
3664
|
+
playerName: getCol(mapping.playerName),
|
|
3665
|
+
kicks: getCol("kicks"),
|
|
3666
|
+
handballs: getCol("handballs"),
|
|
3667
|
+
disposals: getCol("disposals"),
|
|
3668
|
+
marks: getCol("marks"),
|
|
3669
|
+
goals: getCol("goals"),
|
|
3670
|
+
behinds: getCol("behinds"),
|
|
3671
|
+
tackles: getCol("tackles"),
|
|
3672
|
+
hitouts: getCol("hitouts"),
|
|
3673
|
+
freesFor: getCol(mapping.freesFor),
|
|
3674
|
+
freesAgainst: getCol(mapping.freesAgainst),
|
|
3675
|
+
contestedPossessions: getCol("contested_possessions"),
|
|
3676
|
+
uncontestedPossessions: getCol("uncontested_possessions"),
|
|
3677
|
+
contestedMarks: getCol("contested_marks"),
|
|
3678
|
+
intercepts: getCol("intercepts"),
|
|
3679
|
+
centreClearances: getCol("centre_clearances"),
|
|
3680
|
+
stoppageClearances: getCol("stoppage_clearances"),
|
|
3681
|
+
totalClearances: getCol(mapping.totalClearances),
|
|
3682
|
+
inside50s: getCol(mapping.inside50s),
|
|
3683
|
+
rebound50s: getCol(mapping.rebound50s),
|
|
3684
|
+
clangers: getCol("clangers"),
|
|
3685
|
+
turnovers: getCol("turnovers"),
|
|
3686
|
+
onePercenters: getCol("one_percenters"),
|
|
3687
|
+
bounces: getCol("bounces"),
|
|
3688
|
+
goalAssists: getCol("goal_assists"),
|
|
3689
|
+
disposalEfficiency: getCol(mapping.disposalEfficiency),
|
|
3690
|
+
metresGained: getCol("metres_gained"),
|
|
3691
|
+
marksInside50: getCol(mapping.marksInside50),
|
|
3692
|
+
tacklesInside50: getCol(mapping.tacklesInside50),
|
|
3693
|
+
shotsAtGoal: getCol("shots_at_goal"),
|
|
3694
|
+
scoreInvolvements: getCol("score_involvements"),
|
|
3695
|
+
totalPossessions: getCol(mapping.totalPossessions),
|
|
3696
|
+
timeOnGround: getCol(mapping.timeOnGround),
|
|
3697
|
+
ratingPoints: getCol("rating_points"),
|
|
3698
|
+
position: getCol(mapping.position),
|
|
3699
|
+
brownlowVotes: getCol("brownlow_votes"),
|
|
3700
|
+
supercoachScore: getCol("supercoach_score"),
|
|
3701
|
+
dreamTeamPoints: getCol(mapping.dreamTeamPoints),
|
|
3702
|
+
effectiveDisposals: getCol("effective_disposals"),
|
|
3703
|
+
effectiveKicks: getCol("effective_kicks"),
|
|
3704
|
+
pressureActs: getCol("pressure_acts"),
|
|
3705
|
+
defHalfPressureActs: getCol("def_half_pressure_acts"),
|
|
3706
|
+
spoils: getCol("spoils"),
|
|
3707
|
+
hitoutsToAdvantage: getCol("hitouts_to_advantage"),
|
|
3708
|
+
hitoutWinPercentage: getCol("hitout_win_percentage"),
|
|
3709
|
+
groundBallGets: getCol("ground_ball_gets"),
|
|
3710
|
+
f50GroundBallGets: getCol("f50_ground_ball_gets"),
|
|
3711
|
+
interceptMarks: getCol("intercept_marks"),
|
|
3712
|
+
marksOnLead: getCol("marks_on_lead"),
|
|
3713
|
+
contestOffOneOnOnes: getCol("contest_off_one_on_ones"),
|
|
3714
|
+
contestOffWins: getCol("contest_off_wins"),
|
|
3715
|
+
contestDefOneOnOnes: getCol("contest_def_one_on_ones"),
|
|
3716
|
+
contestDefLosses: getCol("contest_def_losses"),
|
|
3717
|
+
ruckContests: getCol("ruck_contests"),
|
|
3718
|
+
scoreLaunches: getCol("score_launches")
|
|
3719
|
+
};
|
|
3720
|
+
const dateCol = cols.date;
|
|
3721
|
+
const roundCol = cols.round;
|
|
3722
|
+
const nRows = dateCol ? dateCol.length : 0;
|
|
3723
|
+
const hasFilters = options.season !== void 0 || options.round !== void 0;
|
|
3724
|
+
let rowIndices = null;
|
|
3725
|
+
let rowCount = nRows;
|
|
3726
|
+
if (hasFilters) {
|
|
3727
|
+
const matching = [];
|
|
3728
|
+
for (let i = 0; i < nRows; i++) {
|
|
3729
|
+
if (options.season !== void 0) {
|
|
3730
|
+
const dateStr = dateCol?.[i];
|
|
3731
|
+
if (typeof dateStr !== "string") continue;
|
|
3732
|
+
const year = Number(dateStr.slice(0, 4));
|
|
3733
|
+
if (year !== options.season) continue;
|
|
3734
|
+
}
|
|
3735
|
+
if (options.round !== void 0 && roundCol) {
|
|
3736
|
+
const roundVal = roundCol[i];
|
|
3737
|
+
const roundNum = typeof roundVal === "string" ? Number(roundVal) : roundVal;
|
|
3738
|
+
if (roundNum !== options.round) continue;
|
|
3739
|
+
}
|
|
3740
|
+
matching.push(i);
|
|
3741
|
+
}
|
|
3742
|
+
rowIndices = matching;
|
|
3743
|
+
rowCount = matching.length;
|
|
3744
|
+
}
|
|
3745
|
+
const stats = new Array(rowCount);
|
|
3746
|
+
for (let j = 0; j < rowCount; j++) {
|
|
3747
|
+
const i = rowIndices ? rowIndices[j] : j;
|
|
3748
|
+
stats[j] = mapRow(i, cols, isAflw, options.competition);
|
|
3749
|
+
}
|
|
3750
|
+
return ok(stats);
|
|
3751
|
+
}
|
|
3752
|
+
function numAt(column, i) {
|
|
3753
|
+
if (!column) return null;
|
|
3754
|
+
const v = column[i];
|
|
3755
|
+
return typeof v === "number" ? v : null;
|
|
3756
|
+
}
|
|
3757
|
+
function strAt(column, i) {
|
|
3758
|
+
if (!column) return null;
|
|
3759
|
+
const v = column[i];
|
|
3760
|
+
return typeof v === "string" ? v : null;
|
|
3761
|
+
}
|
|
3762
|
+
function roundAt(column, i) {
|
|
3763
|
+
if (!column) return 0;
|
|
3764
|
+
const v = column[i];
|
|
3765
|
+
if (typeof v === "number") return v;
|
|
3766
|
+
if (typeof v === "string") {
|
|
3767
|
+
const n = Number(v);
|
|
3768
|
+
return Number.isNaN(n) ? 0 : n;
|
|
3769
|
+
}
|
|
3770
|
+
return 0;
|
|
3771
|
+
}
|
|
3772
|
+
function mapRow(i, c, isAflw, competition) {
|
|
3773
|
+
const dateStr = strAt(c.date, i);
|
|
3774
|
+
let firstName;
|
|
3775
|
+
let lastName;
|
|
3776
|
+
if (isAflw) {
|
|
3777
|
+
const playerName = strAt(c.playerName, i) ?? "";
|
|
3778
|
+
const commaIdx = playerName.indexOf(", ");
|
|
3779
|
+
firstName = commaIdx >= 0 ? playerName.slice(0, commaIdx) : playerName;
|
|
3780
|
+
lastName = commaIdx >= 0 ? playerName.slice(commaIdx + 2) : "";
|
|
3781
|
+
} else {
|
|
3782
|
+
firstName = strAt(c.firstName, i) ?? "";
|
|
3783
|
+
lastName = strAt(c.lastName, i) ?? "";
|
|
3784
|
+
}
|
|
3785
|
+
const team = strAt(c.team, i) ?? "";
|
|
3786
|
+
const homeTeam = strAt(c.homeTeam, i);
|
|
3787
|
+
const awayTeam = strAt(c.awayTeam, i);
|
|
3788
|
+
return {
|
|
3789
|
+
matchId: String(c.matchId?.[i] ?? ""),
|
|
3790
|
+
season: dateStr ? Number(dateStr.slice(0, 4)) : 0,
|
|
3791
|
+
roundNumber: roundAt(c.round, i),
|
|
3792
|
+
team: normaliseTeamName(team),
|
|
3793
|
+
competition,
|
|
3794
|
+
date: dateStr ? new Date(dateStr) : null,
|
|
3795
|
+
homeTeam: homeTeam ? normaliseTeamName(homeTeam) : null,
|
|
3796
|
+
awayTeam: awayTeam ? normaliseTeamName(awayTeam) : null,
|
|
3797
|
+
playerId: String(c.playerId?.[i] ?? ""),
|
|
3798
|
+
givenName: firstName,
|
|
3799
|
+
surname: lastName,
|
|
3800
|
+
displayName: `${firstName} ${lastName}`.trim(),
|
|
3801
|
+
jumperNumber: numAt(c.jumperNumber, i),
|
|
3802
|
+
kicks: numAt(c.kicks, i),
|
|
3803
|
+
handballs: numAt(c.handballs, i),
|
|
3804
|
+
disposals: numAt(c.disposals, i),
|
|
3805
|
+
marks: numAt(c.marks, i),
|
|
3806
|
+
goals: numAt(c.goals, i),
|
|
3807
|
+
behinds: numAt(c.behinds, i),
|
|
3808
|
+
tackles: numAt(c.tackles, i),
|
|
3809
|
+
hitouts: numAt(c.hitouts, i),
|
|
3810
|
+
freesFor: numAt(c.freesFor, i),
|
|
3811
|
+
freesAgainst: numAt(c.freesAgainst, i),
|
|
3812
|
+
contestedPossessions: numAt(c.contestedPossessions, i),
|
|
3813
|
+
uncontestedPossessions: numAt(c.uncontestedPossessions, i),
|
|
3814
|
+
contestedMarks: numAt(c.contestedMarks, i),
|
|
3815
|
+
intercepts: numAt(c.intercepts, i),
|
|
3816
|
+
centreClearances: numAt(c.centreClearances, i),
|
|
3817
|
+
stoppageClearances: numAt(c.stoppageClearances, i),
|
|
3818
|
+
totalClearances: numAt(c.totalClearances, i),
|
|
3819
|
+
inside50s: numAt(c.inside50s, i),
|
|
3820
|
+
rebound50s: numAt(c.rebound50s, i),
|
|
3821
|
+
clangers: numAt(c.clangers, i),
|
|
3822
|
+
turnovers: numAt(c.turnovers, i),
|
|
3823
|
+
onePercenters: numAt(c.onePercenters, i),
|
|
3824
|
+
bounces: numAt(c.bounces, i),
|
|
3825
|
+
goalAssists: numAt(c.goalAssists, i),
|
|
3826
|
+
disposalEfficiency: numAt(c.disposalEfficiency, i),
|
|
3827
|
+
metresGained: numAt(c.metresGained, i),
|
|
3828
|
+
goalAccuracy: null,
|
|
3829
|
+
marksInside50: numAt(c.marksInside50, i),
|
|
3830
|
+
tacklesInside50: numAt(c.tacklesInside50, i),
|
|
3831
|
+
shotsAtGoal: numAt(c.shotsAtGoal, i),
|
|
3832
|
+
scoreInvolvements: numAt(c.scoreInvolvements, i),
|
|
3833
|
+
totalPossessions: numAt(c.totalPossessions, i),
|
|
3834
|
+
timeOnGroundPercentage: numAt(c.timeOnGround, i),
|
|
3835
|
+
ratingPoints: numAt(c.ratingPoints, i),
|
|
3836
|
+
position: strAt(c.position, i),
|
|
3837
|
+
goalEfficiency: null,
|
|
3838
|
+
shotEfficiency: null,
|
|
3839
|
+
interchangeCounts: null,
|
|
3840
|
+
brownlowVotes: numAt(c.brownlowVotes, i),
|
|
3841
|
+
supercoachScore: numAt(c.supercoachScore, i),
|
|
3842
|
+
dreamTeamPoints: numAt(c.dreamTeamPoints, i),
|
|
3843
|
+
effectiveDisposals: numAt(c.effectiveDisposals, i),
|
|
3844
|
+
effectiveKicks: numAt(c.effectiveKicks, i),
|
|
3845
|
+
kickEfficiency: null,
|
|
3846
|
+
kickToHandballRatio: null,
|
|
3847
|
+
pressureActs: numAt(c.pressureActs, i),
|
|
3848
|
+
defHalfPressureActs: numAt(c.defHalfPressureActs, i),
|
|
3849
|
+
spoils: numAt(c.spoils, i),
|
|
3850
|
+
hitoutsToAdvantage: numAt(c.hitoutsToAdvantage, i),
|
|
3851
|
+
hitoutWinPercentage: numAt(c.hitoutWinPercentage, i),
|
|
3852
|
+
hitoutToAdvantageRate: null,
|
|
3853
|
+
groundBallGets: numAt(c.groundBallGets, i),
|
|
3854
|
+
f50GroundBallGets: numAt(c.f50GroundBallGets, i),
|
|
3855
|
+
interceptMarks: numAt(c.interceptMarks, i),
|
|
3856
|
+
marksOnLead: numAt(c.marksOnLead, i),
|
|
3857
|
+
contestedPossessionRate: null,
|
|
3858
|
+
contestOffOneOnOnes: numAt(c.contestOffOneOnOnes, i),
|
|
3859
|
+
contestOffWins: numAt(c.contestOffWins, i),
|
|
3860
|
+
contestOffWinsPercentage: null,
|
|
3861
|
+
contestDefOneOnOnes: numAt(c.contestDefOneOnOnes, i),
|
|
3862
|
+
contestDefLosses: numAt(c.contestDefLosses, i),
|
|
3863
|
+
contestDefLossPercentage: null,
|
|
3864
|
+
centreBounceAttendances: null,
|
|
3865
|
+
kickins: null,
|
|
3866
|
+
kickinsPlayon: null,
|
|
3867
|
+
ruckContests: numAt(c.ruckContests, i),
|
|
3868
|
+
scoreLaunches: numAt(c.scoreLaunches, i),
|
|
3869
|
+
source: "fryzigg"
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
var REQUIRED_COLUMN_GROUPS, AFLM_COLUMNS, AFLW_COLUMNS;
|
|
3873
|
+
var init_fryzigg_player_stats = __esm({
|
|
3874
|
+
"src/transforms/fryzigg-player-stats.ts"() {
|
|
3875
|
+
"use strict";
|
|
3876
|
+
init_errors();
|
|
3877
|
+
init_result();
|
|
3878
|
+
init_team_mapping();
|
|
3879
|
+
REQUIRED_COLUMN_GROUPS = [
|
|
3880
|
+
["match_id"],
|
|
3881
|
+
["match_date", "date"],
|
|
3882
|
+
["player_id"],
|
|
3883
|
+
["player_team", "team"]
|
|
3884
|
+
];
|
|
3885
|
+
AFLM_COLUMNS = {
|
|
3886
|
+
date: "match_date",
|
|
3887
|
+
homeTeam: "match_home_team",
|
|
3888
|
+
awayTeam: "match_away_team",
|
|
3889
|
+
team: "player_team",
|
|
3890
|
+
round: "match_round",
|
|
3891
|
+
jumperNumber: "guernsey_number",
|
|
3892
|
+
firstName: "player_first_name",
|
|
3893
|
+
lastName: "player_last_name",
|
|
3894
|
+
playerName: void 0,
|
|
3895
|
+
freesFor: "free_kicks_for",
|
|
3896
|
+
freesAgainst: "free_kicks_against",
|
|
3897
|
+
totalClearances: "clearances",
|
|
3898
|
+
inside50s: "inside_fifties",
|
|
3899
|
+
rebound50s: "rebounds",
|
|
3900
|
+
disposalEfficiency: "disposal_efficiency_percentage",
|
|
3901
|
+
marksInside50: "marks_inside_fifty",
|
|
3902
|
+
tacklesInside50: "tackles_inside_fifty",
|
|
3903
|
+
timeOnGround: "time_on_ground_percentage",
|
|
3904
|
+
position: "player_position",
|
|
3905
|
+
dreamTeamPoints: "afl_fantasy_score",
|
|
3906
|
+
totalPossessions: void 0
|
|
3907
|
+
};
|
|
3908
|
+
AFLW_COLUMNS = {
|
|
3909
|
+
date: "date",
|
|
3910
|
+
homeTeam: "home_team",
|
|
3911
|
+
awayTeam: "away_team",
|
|
3912
|
+
team: "team",
|
|
3913
|
+
round: "fixture_round",
|
|
3914
|
+
jumperNumber: "number",
|
|
3915
|
+
firstName: void 0,
|
|
3916
|
+
lastName: void 0,
|
|
3917
|
+
playerName: "player_name",
|
|
3918
|
+
freesFor: "frees_for",
|
|
3919
|
+
freesAgainst: "frees_against",
|
|
3920
|
+
totalClearances: "total_clearances",
|
|
3921
|
+
inside50s: "inside50s",
|
|
3922
|
+
rebound50s: "rebound50s",
|
|
3923
|
+
disposalEfficiency: "disposal_efficiency",
|
|
3924
|
+
marksInside50: "marks_inside50",
|
|
3925
|
+
tacklesInside50: "tackles_inside50",
|
|
3926
|
+
timeOnGround: "time_on_ground",
|
|
3927
|
+
position: "position",
|
|
3928
|
+
dreamTeamPoints: "fantasy_score",
|
|
3929
|
+
totalPossessions: "total_possessions"
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3932
|
+
});
|
|
3933
|
+
|
|
3558
3934
|
// src/transforms/player-stats.ts
|
|
3559
3935
|
function toNullable(value) {
|
|
3560
3936
|
return value ?? null;
|
|
@@ -3763,6 +4139,16 @@ async function fetchPlayerStats(query) {
|
|
|
3763
4139
|
}
|
|
3764
4140
|
return atResult;
|
|
3765
4141
|
}
|
|
4142
|
+
case "fryzigg": {
|
|
4143
|
+
const fzClient = new FryziggClient();
|
|
4144
|
+
const fzResult = await fzClient.fetchPlayerStats(competition);
|
|
4145
|
+
if (!fzResult.success) return fzResult;
|
|
4146
|
+
return transformFryziggPlayerStats(fzResult.data, {
|
|
4147
|
+
competition,
|
|
4148
|
+
season: query.season,
|
|
4149
|
+
round: query.round
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
3766
4152
|
default:
|
|
3767
4153
|
return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
|
|
3768
4154
|
}
|
|
@@ -3777,6 +4163,8 @@ var init_player_stats2 = __esm({
|
|
|
3777
4163
|
init_afl_api();
|
|
3778
4164
|
init_afl_tables();
|
|
3779
4165
|
init_footywire();
|
|
4166
|
+
init_fryzigg();
|
|
4167
|
+
init_fryzigg_player_stats();
|
|
3780
4168
|
init_player_stats();
|
|
3781
4169
|
}
|
|
3782
4170
|
});
|
|
@@ -4371,7 +4759,13 @@ var init_validation2 = __esm({
|
|
|
4371
4759
|
"use strict";
|
|
4372
4760
|
init_team_mapping();
|
|
4373
4761
|
init_date_utils();
|
|
4374
|
-
VALID_SOURCES = [
|
|
4762
|
+
VALID_SOURCES = [
|
|
4763
|
+
"afl-api",
|
|
4764
|
+
"footywire",
|
|
4765
|
+
"afl-tables",
|
|
4766
|
+
"squiggle",
|
|
4767
|
+
"fryzigg"
|
|
4768
|
+
];
|
|
4375
4769
|
VALID_COMPETITIONS = ["AFLM", "AFLW"];
|
|
4376
4770
|
VALID_FORMATS = ["table", "json", "csv"];
|
|
4377
4771
|
VALID_SUMMARIES = ["totals", "averages"];
|
|
@@ -5392,7 +5786,7 @@ resolveAliases();
|
|
|
5392
5786
|
var main = defineCommand11({
|
|
5393
5787
|
meta: {
|
|
5394
5788
|
name: "fitzroy",
|
|
5395
|
-
version: "1.
|
|
5789
|
+
version: "1.7.0",
|
|
5396
5790
|
description: "TypeScript port of the fitzRoy R package \u2014 fetch AFL data from the command line"
|
|
5397
5791
|
},
|
|
5398
5792
|
subCommands: {
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ type CompetitionCode = "AFLM" | "AFLW";
|
|
|
35
35
|
/** Round classification. */
|
|
36
36
|
type RoundType = "HomeAndAway" | "Finals";
|
|
37
37
|
/** Supported data sources mirroring the R package's `source` parameter. */
|
|
38
|
-
type DataSource = "afl-api" | "footywire" | "afl-tables" | "squiggle";
|
|
38
|
+
type DataSource = "afl-api" | "footywire" | "afl-tables" | "squiggle" | "fryzigg";
|
|
39
39
|
/** Match status as reported by the AFL API. */
|
|
40
40
|
type MatchStatus = "Upcoming" | "Live" | "Complete" | "Postponed" | "Cancelled";
|
|
41
41
|
/** Goals-behinds-points breakdown for a single quarter. */
|
|
@@ -2152,13 +2152,33 @@ declare class FootyWireClient {
|
|
|
2152
2152
|
*/
|
|
2153
2153
|
fetchTeamStats(year: number, summaryType?: "totals" | "averages"): Promise<Result<TeamStatsEntry[], ScrapeError>>;
|
|
2154
2154
|
}
|
|
2155
|
+
import { DataFrame } from "@jackemcpherson/rds-js";
|
|
2156
|
+
/** Options for constructing a Fryzigg client. */
|
|
2157
|
+
interface FryziggClientOptions {
|
|
2158
|
+
readonly fetchFn?: typeof fetch | undefined;
|
|
2159
|
+
}
|
|
2155
2160
|
/**
|
|
2156
|
-
*
|
|
2161
|
+
* Fryzigg RDS client.
|
|
2157
2162
|
*
|
|
2158
|
-
*
|
|
2159
|
-
*
|
|
2163
|
+
* Downloads and parses static RDS files from fryziggafl.net. The full
|
|
2164
|
+
* dataset is always fetched — there is no server-side filtering. Callers
|
|
2165
|
+
* should filter the returned DataFrame by season/round before constructing
|
|
2166
|
+
* row objects to minimise memory usage.
|
|
2160
2167
|
*/
|
|
2161
|
-
declare
|
|
2168
|
+
declare class FryziggClient {
|
|
2169
|
+
private readonly fetchFn;
|
|
2170
|
+
constructor(options?: FryziggClientOptions);
|
|
2171
|
+
/**
|
|
2172
|
+
* Fetch the full player statistics dataset for a competition.
|
|
2173
|
+
*
|
|
2174
|
+
* Returns column-major DataFrame from rds-js. The caller is responsible
|
|
2175
|
+
* for filtering rows and mapping to domain types.
|
|
2176
|
+
*
|
|
2177
|
+
* @param competition - AFLM or AFLW.
|
|
2178
|
+
* @returns Column-major DataFrame with all rows, or an error.
|
|
2179
|
+
*/
|
|
2180
|
+
fetchPlayerStats(competition: CompetitionCode): Promise<Result<DataFrame, ScrapeError>>;
|
|
2181
|
+
}
|
|
2162
2182
|
/** Options for constructing a Squiggle client. */
|
|
2163
2183
|
interface SquiggleClientOptions {
|
|
2164
2184
|
readonly fetchFn?: typeof fetch | undefined;
|
|
@@ -2200,6 +2220,21 @@ declare class SquiggleClient {
|
|
|
2200
2220
|
* @returns Sorted ladder entries.
|
|
2201
2221
|
*/
|
|
2202
2222
|
declare function computeLadder(results: readonly MatchResult[], upToRound?: number): LadderEntry[];
|
|
2223
|
+
import { DataFrame as DataFrame2 } from "@jackemcpherson/rds-js";
|
|
2224
|
+
/** Parameters for filtering and mapping fryzigg data. */
|
|
2225
|
+
interface FryziggTransformOptions {
|
|
2226
|
+
readonly competition: CompetitionCode;
|
|
2227
|
+
readonly season?: number | undefined;
|
|
2228
|
+
readonly round?: number | undefined;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Transform a fryzigg DataFrame into filtered PlayerStats[].
|
|
2232
|
+
*
|
|
2233
|
+
* @param frame - Column-major data from FryziggClient.
|
|
2234
|
+
* @param options - Competition, season, and round filters.
|
|
2235
|
+
* @returns Filtered array of PlayerStats, or error if columns are missing.
|
|
2236
|
+
*/
|
|
2237
|
+
declare function transformFryziggPlayerStats(frame: DataFrame2, options: FryziggTransformOptions): Result<PlayerStats[], ScrapeError>;
|
|
2203
2238
|
/**
|
|
2204
2239
|
* Transform raw AFL API ladder entries into typed LadderEntry objects.
|
|
2205
2240
|
*
|
|
@@ -2264,4 +2299,4 @@ declare function transformSquiggleGamesToFixture(games: readonly SquiggleGame[],
|
|
|
2264
2299
|
* Transform Squiggle standings into LadderEntry objects.
|
|
2265
2300
|
*/
|
|
2266
2301
|
declare function transformSquiggleStandings(standings: readonly SquiggleStanding[]): LadderEntry[];
|
|
2267
|
-
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,
|
|
2302
|
+
export { transformSquiggleStandings, transformSquiggleGamesToResults, transformSquiggleGamesToFixture, transformPlayerStats, transformMatchRoster, transformMatchItems, transformLadderEntries, transformFryziggPlayerStats, 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, 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, FryziggTransformOptions, FryziggClientOptions, FryziggClient, 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
|
@@ -1872,6 +1872,7 @@ var LadderResponseSchema = z.object({
|
|
|
1872
1872
|
}).passthrough();
|
|
1873
1873
|
|
|
1874
1874
|
// src/sources/afl-api.ts
|
|
1875
|
+
var USER_AGENT = "fitzroy/2 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
1875
1876
|
var TOKEN_URL = "https://api.afl.com.au/cfs/afl/WMCTok";
|
|
1876
1877
|
var API_BASE = "https://aflapi.afl.com.au/afl/v2";
|
|
1877
1878
|
var CFS_BASE = "https://api.afl.com.au/cfs/afl";
|
|
@@ -1881,7 +1882,14 @@ var AflApiClient = class {
|
|
|
1881
1882
|
cachedToken = null;
|
|
1882
1883
|
pendingAuth = null;
|
|
1883
1884
|
constructor(options) {
|
|
1884
|
-
|
|
1885
|
+
const baseFetch = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
1886
|
+
this.fetchFn = (input, init) => {
|
|
1887
|
+
const headers = new Headers(init?.headers);
|
|
1888
|
+
if (!headers.has("User-Agent")) {
|
|
1889
|
+
headers.set("User-Agent", USER_AGENT);
|
|
1890
|
+
}
|
|
1891
|
+
return baseFetch(input, { ...init, headers });
|
|
1892
|
+
};
|
|
1885
1893
|
this.tokenUrl = options?.tokenUrl ?? TOKEN_URL;
|
|
1886
1894
|
}
|
|
1887
1895
|
/**
|
|
@@ -2291,7 +2299,7 @@ var SquiggleStandingsResponseSchema = z2.object({
|
|
|
2291
2299
|
|
|
2292
2300
|
// src/sources/squiggle.ts
|
|
2293
2301
|
var SQUIGGLE_BASE = "https://api.squiggle.com.au/";
|
|
2294
|
-
var
|
|
2302
|
+
var USER_AGENT2 = "fitzRoy-ts/1.0 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
2295
2303
|
var SquiggleClient = class {
|
|
2296
2304
|
fetchFn;
|
|
2297
2305
|
constructor(options) {
|
|
@@ -2304,7 +2312,7 @@ var SquiggleClient = class {
|
|
|
2304
2312
|
const url = `${SQUIGGLE_BASE}?${params.toString()}`;
|
|
2305
2313
|
try {
|
|
2306
2314
|
const response = await this.fetchFn(url, {
|
|
2307
|
-
headers: { "User-Agent":
|
|
2315
|
+
headers: { "User-Agent": USER_AGENT2 }
|
|
2308
2316
|
});
|
|
2309
2317
|
if (!response.ok) {
|
|
2310
2318
|
return err(
|
|
@@ -3467,6 +3475,357 @@ async function fetchPlayerDetails(query) {
|
|
|
3467
3475
|
}
|
|
3468
3476
|
}
|
|
3469
3477
|
|
|
3478
|
+
// src/sources/fryzigg.ts
|
|
3479
|
+
import { isDataFrame, parseRds, RdsError } from "@jackemcpherson/rds-js";
|
|
3480
|
+
var FRYZIGG_URLS = {
|
|
3481
|
+
AFLM: "http://www.fryziggafl.net/static/fryziggafl.rds",
|
|
3482
|
+
AFLW: "http://www.fryziggafl.net/static/aflw_player_stats.rds"
|
|
3483
|
+
};
|
|
3484
|
+
var USER_AGENT3 = "fitzRoy-ts/1.0 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
3485
|
+
var FryziggClient = class {
|
|
3486
|
+
fetchFn;
|
|
3487
|
+
constructor(options) {
|
|
3488
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
3489
|
+
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Fetch the full player statistics dataset for a competition.
|
|
3492
|
+
*
|
|
3493
|
+
* Returns column-major DataFrame from rds-js. The caller is responsible
|
|
3494
|
+
* for filtering rows and mapping to domain types.
|
|
3495
|
+
*
|
|
3496
|
+
* @param competition - AFLM or AFLW.
|
|
3497
|
+
* @returns Column-major DataFrame with all rows, or an error.
|
|
3498
|
+
*/
|
|
3499
|
+
async fetchPlayerStats(competition) {
|
|
3500
|
+
const url = FRYZIGG_URLS[competition];
|
|
3501
|
+
try {
|
|
3502
|
+
const response = await this.fetchFn(url, {
|
|
3503
|
+
headers: { "User-Agent": USER_AGENT3 }
|
|
3504
|
+
});
|
|
3505
|
+
if (!response.ok) {
|
|
3506
|
+
return err(
|
|
3507
|
+
new ScrapeError(`Fryzigg request failed: ${response.status} (${url})`, "fryzigg")
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
3511
|
+
const result = await parseRds(buffer);
|
|
3512
|
+
if (!isDataFrame(result)) {
|
|
3513
|
+
return err(new ScrapeError("Fryzigg RDS file did not contain a data frame", "fryzigg"));
|
|
3514
|
+
}
|
|
3515
|
+
return ok(result);
|
|
3516
|
+
} catch (cause) {
|
|
3517
|
+
if (cause instanceof RdsError) {
|
|
3518
|
+
return err(new ScrapeError(`Fryzigg RDS parse error: ${cause.message}`, "fryzigg"));
|
|
3519
|
+
}
|
|
3520
|
+
return err(
|
|
3521
|
+
new ScrapeError(
|
|
3522
|
+
`Fryzigg request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
3523
|
+
"fryzigg"
|
|
3524
|
+
)
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
// src/transforms/fryzigg-player-stats.ts
|
|
3531
|
+
var REQUIRED_COLUMN_GROUPS = [
|
|
3532
|
+
["match_id"],
|
|
3533
|
+
["match_date", "date"],
|
|
3534
|
+
["player_id"],
|
|
3535
|
+
["player_team", "team"]
|
|
3536
|
+
];
|
|
3537
|
+
var AFLM_COLUMNS = {
|
|
3538
|
+
date: "match_date",
|
|
3539
|
+
homeTeam: "match_home_team",
|
|
3540
|
+
awayTeam: "match_away_team",
|
|
3541
|
+
team: "player_team",
|
|
3542
|
+
round: "match_round",
|
|
3543
|
+
jumperNumber: "guernsey_number",
|
|
3544
|
+
firstName: "player_first_name",
|
|
3545
|
+
lastName: "player_last_name",
|
|
3546
|
+
playerName: void 0,
|
|
3547
|
+
freesFor: "free_kicks_for",
|
|
3548
|
+
freesAgainst: "free_kicks_against",
|
|
3549
|
+
totalClearances: "clearances",
|
|
3550
|
+
inside50s: "inside_fifties",
|
|
3551
|
+
rebound50s: "rebounds",
|
|
3552
|
+
disposalEfficiency: "disposal_efficiency_percentage",
|
|
3553
|
+
marksInside50: "marks_inside_fifty",
|
|
3554
|
+
tacklesInside50: "tackles_inside_fifty",
|
|
3555
|
+
timeOnGround: "time_on_ground_percentage",
|
|
3556
|
+
position: "player_position",
|
|
3557
|
+
dreamTeamPoints: "afl_fantasy_score",
|
|
3558
|
+
totalPossessions: void 0
|
|
3559
|
+
};
|
|
3560
|
+
var AFLW_COLUMNS = {
|
|
3561
|
+
date: "date",
|
|
3562
|
+
homeTeam: "home_team",
|
|
3563
|
+
awayTeam: "away_team",
|
|
3564
|
+
team: "team",
|
|
3565
|
+
round: "fixture_round",
|
|
3566
|
+
jumperNumber: "number",
|
|
3567
|
+
firstName: void 0,
|
|
3568
|
+
lastName: void 0,
|
|
3569
|
+
playerName: "player_name",
|
|
3570
|
+
freesFor: "frees_for",
|
|
3571
|
+
freesAgainst: "frees_against",
|
|
3572
|
+
totalClearances: "total_clearances",
|
|
3573
|
+
inside50s: "inside50s",
|
|
3574
|
+
rebound50s: "rebound50s",
|
|
3575
|
+
disposalEfficiency: "disposal_efficiency",
|
|
3576
|
+
marksInside50: "marks_inside50",
|
|
3577
|
+
tacklesInside50: "tackles_inside50",
|
|
3578
|
+
timeOnGround: "time_on_ground",
|
|
3579
|
+
position: "position",
|
|
3580
|
+
dreamTeamPoints: "fantasy_score",
|
|
3581
|
+
totalPossessions: "total_possessions"
|
|
3582
|
+
};
|
|
3583
|
+
function transformFryziggPlayerStats(frame, options) {
|
|
3584
|
+
const colIndex = /* @__PURE__ */ new Map();
|
|
3585
|
+
for (let i = 0; i < frame.names.length; i++) {
|
|
3586
|
+
const name = frame.names[i];
|
|
3587
|
+
if (name !== void 0) {
|
|
3588
|
+
colIndex.set(name, i);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
for (const group of REQUIRED_COLUMN_GROUPS) {
|
|
3592
|
+
if (!group.some((name) => colIndex.has(name))) {
|
|
3593
|
+
return err(
|
|
3594
|
+
new ScrapeError(
|
|
3595
|
+
`Fryzigg data frame missing required column: "${group.join('" or "')}"`,
|
|
3596
|
+
"fryzigg"
|
|
3597
|
+
)
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
const getCol = (name) => {
|
|
3602
|
+
if (name === void 0) return void 0;
|
|
3603
|
+
const idx = colIndex.get(name);
|
|
3604
|
+
if (idx === void 0) return void 0;
|
|
3605
|
+
return frame.columns[idx];
|
|
3606
|
+
};
|
|
3607
|
+
const isAflw = options.competition === "AFLW";
|
|
3608
|
+
const mapping = isAflw ? AFLW_COLUMNS : AFLM_COLUMNS;
|
|
3609
|
+
const cols = {
|
|
3610
|
+
matchId: getCol("match_id"),
|
|
3611
|
+
date: getCol(mapping.date),
|
|
3612
|
+
homeTeam: getCol(mapping.homeTeam),
|
|
3613
|
+
awayTeam: getCol(mapping.awayTeam),
|
|
3614
|
+
team: getCol(mapping.team),
|
|
3615
|
+
round: getCol(mapping.round),
|
|
3616
|
+
jumperNumber: getCol(mapping.jumperNumber),
|
|
3617
|
+
playerId: getCol("player_id"),
|
|
3618
|
+
firstName: getCol(mapping.firstName),
|
|
3619
|
+
lastName: getCol(mapping.lastName),
|
|
3620
|
+
playerName: getCol(mapping.playerName),
|
|
3621
|
+
kicks: getCol("kicks"),
|
|
3622
|
+
handballs: getCol("handballs"),
|
|
3623
|
+
disposals: getCol("disposals"),
|
|
3624
|
+
marks: getCol("marks"),
|
|
3625
|
+
goals: getCol("goals"),
|
|
3626
|
+
behinds: getCol("behinds"),
|
|
3627
|
+
tackles: getCol("tackles"),
|
|
3628
|
+
hitouts: getCol("hitouts"),
|
|
3629
|
+
freesFor: getCol(mapping.freesFor),
|
|
3630
|
+
freesAgainst: getCol(mapping.freesAgainst),
|
|
3631
|
+
contestedPossessions: getCol("contested_possessions"),
|
|
3632
|
+
uncontestedPossessions: getCol("uncontested_possessions"),
|
|
3633
|
+
contestedMarks: getCol("contested_marks"),
|
|
3634
|
+
intercepts: getCol("intercepts"),
|
|
3635
|
+
centreClearances: getCol("centre_clearances"),
|
|
3636
|
+
stoppageClearances: getCol("stoppage_clearances"),
|
|
3637
|
+
totalClearances: getCol(mapping.totalClearances),
|
|
3638
|
+
inside50s: getCol(mapping.inside50s),
|
|
3639
|
+
rebound50s: getCol(mapping.rebound50s),
|
|
3640
|
+
clangers: getCol("clangers"),
|
|
3641
|
+
turnovers: getCol("turnovers"),
|
|
3642
|
+
onePercenters: getCol("one_percenters"),
|
|
3643
|
+
bounces: getCol("bounces"),
|
|
3644
|
+
goalAssists: getCol("goal_assists"),
|
|
3645
|
+
disposalEfficiency: getCol(mapping.disposalEfficiency),
|
|
3646
|
+
metresGained: getCol("metres_gained"),
|
|
3647
|
+
marksInside50: getCol(mapping.marksInside50),
|
|
3648
|
+
tacklesInside50: getCol(mapping.tacklesInside50),
|
|
3649
|
+
shotsAtGoal: getCol("shots_at_goal"),
|
|
3650
|
+
scoreInvolvements: getCol("score_involvements"),
|
|
3651
|
+
totalPossessions: getCol(mapping.totalPossessions),
|
|
3652
|
+
timeOnGround: getCol(mapping.timeOnGround),
|
|
3653
|
+
ratingPoints: getCol("rating_points"),
|
|
3654
|
+
position: getCol(mapping.position),
|
|
3655
|
+
brownlowVotes: getCol("brownlow_votes"),
|
|
3656
|
+
supercoachScore: getCol("supercoach_score"),
|
|
3657
|
+
dreamTeamPoints: getCol(mapping.dreamTeamPoints),
|
|
3658
|
+
effectiveDisposals: getCol("effective_disposals"),
|
|
3659
|
+
effectiveKicks: getCol("effective_kicks"),
|
|
3660
|
+
pressureActs: getCol("pressure_acts"),
|
|
3661
|
+
defHalfPressureActs: getCol("def_half_pressure_acts"),
|
|
3662
|
+
spoils: getCol("spoils"),
|
|
3663
|
+
hitoutsToAdvantage: getCol("hitouts_to_advantage"),
|
|
3664
|
+
hitoutWinPercentage: getCol("hitout_win_percentage"),
|
|
3665
|
+
groundBallGets: getCol("ground_ball_gets"),
|
|
3666
|
+
f50GroundBallGets: getCol("f50_ground_ball_gets"),
|
|
3667
|
+
interceptMarks: getCol("intercept_marks"),
|
|
3668
|
+
marksOnLead: getCol("marks_on_lead"),
|
|
3669
|
+
contestOffOneOnOnes: getCol("contest_off_one_on_ones"),
|
|
3670
|
+
contestOffWins: getCol("contest_off_wins"),
|
|
3671
|
+
contestDefOneOnOnes: getCol("contest_def_one_on_ones"),
|
|
3672
|
+
contestDefLosses: getCol("contest_def_losses"),
|
|
3673
|
+
ruckContests: getCol("ruck_contests"),
|
|
3674
|
+
scoreLaunches: getCol("score_launches")
|
|
3675
|
+
};
|
|
3676
|
+
const dateCol = cols.date;
|
|
3677
|
+
const roundCol = cols.round;
|
|
3678
|
+
const nRows = dateCol ? dateCol.length : 0;
|
|
3679
|
+
const hasFilters = options.season !== void 0 || options.round !== void 0;
|
|
3680
|
+
let rowIndices = null;
|
|
3681
|
+
let rowCount = nRows;
|
|
3682
|
+
if (hasFilters) {
|
|
3683
|
+
const matching = [];
|
|
3684
|
+
for (let i = 0; i < nRows; i++) {
|
|
3685
|
+
if (options.season !== void 0) {
|
|
3686
|
+
const dateStr = dateCol?.[i];
|
|
3687
|
+
if (typeof dateStr !== "string") continue;
|
|
3688
|
+
const year = Number(dateStr.slice(0, 4));
|
|
3689
|
+
if (year !== options.season) continue;
|
|
3690
|
+
}
|
|
3691
|
+
if (options.round !== void 0 && roundCol) {
|
|
3692
|
+
const roundVal = roundCol[i];
|
|
3693
|
+
const roundNum = typeof roundVal === "string" ? Number(roundVal) : roundVal;
|
|
3694
|
+
if (roundNum !== options.round) continue;
|
|
3695
|
+
}
|
|
3696
|
+
matching.push(i);
|
|
3697
|
+
}
|
|
3698
|
+
rowIndices = matching;
|
|
3699
|
+
rowCount = matching.length;
|
|
3700
|
+
}
|
|
3701
|
+
const stats = new Array(rowCount);
|
|
3702
|
+
for (let j = 0; j < rowCount; j++) {
|
|
3703
|
+
const i = rowIndices ? rowIndices[j] : j;
|
|
3704
|
+
stats[j] = mapRow(i, cols, isAflw, options.competition);
|
|
3705
|
+
}
|
|
3706
|
+
return ok(stats);
|
|
3707
|
+
}
|
|
3708
|
+
function numAt(column, i) {
|
|
3709
|
+
if (!column) return null;
|
|
3710
|
+
const v = column[i];
|
|
3711
|
+
return typeof v === "number" ? v : null;
|
|
3712
|
+
}
|
|
3713
|
+
function strAt(column, i) {
|
|
3714
|
+
if (!column) return null;
|
|
3715
|
+
const v = column[i];
|
|
3716
|
+
return typeof v === "string" ? v : null;
|
|
3717
|
+
}
|
|
3718
|
+
function roundAt(column, i) {
|
|
3719
|
+
if (!column) return 0;
|
|
3720
|
+
const v = column[i];
|
|
3721
|
+
if (typeof v === "number") return v;
|
|
3722
|
+
if (typeof v === "string") {
|
|
3723
|
+
const n = Number(v);
|
|
3724
|
+
return Number.isNaN(n) ? 0 : n;
|
|
3725
|
+
}
|
|
3726
|
+
return 0;
|
|
3727
|
+
}
|
|
3728
|
+
function mapRow(i, c, isAflw, competition) {
|
|
3729
|
+
const dateStr = strAt(c.date, i);
|
|
3730
|
+
let firstName;
|
|
3731
|
+
let lastName;
|
|
3732
|
+
if (isAflw) {
|
|
3733
|
+
const playerName = strAt(c.playerName, i) ?? "";
|
|
3734
|
+
const commaIdx = playerName.indexOf(", ");
|
|
3735
|
+
firstName = commaIdx >= 0 ? playerName.slice(0, commaIdx) : playerName;
|
|
3736
|
+
lastName = commaIdx >= 0 ? playerName.slice(commaIdx + 2) : "";
|
|
3737
|
+
} else {
|
|
3738
|
+
firstName = strAt(c.firstName, i) ?? "";
|
|
3739
|
+
lastName = strAt(c.lastName, i) ?? "";
|
|
3740
|
+
}
|
|
3741
|
+
const team = strAt(c.team, i) ?? "";
|
|
3742
|
+
const homeTeam = strAt(c.homeTeam, i);
|
|
3743
|
+
const awayTeam = strAt(c.awayTeam, i);
|
|
3744
|
+
return {
|
|
3745
|
+
matchId: String(c.matchId?.[i] ?? ""),
|
|
3746
|
+
season: dateStr ? Number(dateStr.slice(0, 4)) : 0,
|
|
3747
|
+
roundNumber: roundAt(c.round, i),
|
|
3748
|
+
team: normaliseTeamName(team),
|
|
3749
|
+
competition,
|
|
3750
|
+
date: dateStr ? new Date(dateStr) : null,
|
|
3751
|
+
homeTeam: homeTeam ? normaliseTeamName(homeTeam) : null,
|
|
3752
|
+
awayTeam: awayTeam ? normaliseTeamName(awayTeam) : null,
|
|
3753
|
+
playerId: String(c.playerId?.[i] ?? ""),
|
|
3754
|
+
givenName: firstName,
|
|
3755
|
+
surname: lastName,
|
|
3756
|
+
displayName: `${firstName} ${lastName}`.trim(),
|
|
3757
|
+
jumperNumber: numAt(c.jumperNumber, i),
|
|
3758
|
+
kicks: numAt(c.kicks, i),
|
|
3759
|
+
handballs: numAt(c.handballs, i),
|
|
3760
|
+
disposals: numAt(c.disposals, i),
|
|
3761
|
+
marks: numAt(c.marks, i),
|
|
3762
|
+
goals: numAt(c.goals, i),
|
|
3763
|
+
behinds: numAt(c.behinds, i),
|
|
3764
|
+
tackles: numAt(c.tackles, i),
|
|
3765
|
+
hitouts: numAt(c.hitouts, i),
|
|
3766
|
+
freesFor: numAt(c.freesFor, i),
|
|
3767
|
+
freesAgainst: numAt(c.freesAgainst, i),
|
|
3768
|
+
contestedPossessions: numAt(c.contestedPossessions, i),
|
|
3769
|
+
uncontestedPossessions: numAt(c.uncontestedPossessions, i),
|
|
3770
|
+
contestedMarks: numAt(c.contestedMarks, i),
|
|
3771
|
+
intercepts: numAt(c.intercepts, i),
|
|
3772
|
+
centreClearances: numAt(c.centreClearances, i),
|
|
3773
|
+
stoppageClearances: numAt(c.stoppageClearances, i),
|
|
3774
|
+
totalClearances: numAt(c.totalClearances, i),
|
|
3775
|
+
inside50s: numAt(c.inside50s, i),
|
|
3776
|
+
rebound50s: numAt(c.rebound50s, i),
|
|
3777
|
+
clangers: numAt(c.clangers, i),
|
|
3778
|
+
turnovers: numAt(c.turnovers, i),
|
|
3779
|
+
onePercenters: numAt(c.onePercenters, i),
|
|
3780
|
+
bounces: numAt(c.bounces, i),
|
|
3781
|
+
goalAssists: numAt(c.goalAssists, i),
|
|
3782
|
+
disposalEfficiency: numAt(c.disposalEfficiency, i),
|
|
3783
|
+
metresGained: numAt(c.metresGained, i),
|
|
3784
|
+
goalAccuracy: null,
|
|
3785
|
+
marksInside50: numAt(c.marksInside50, i),
|
|
3786
|
+
tacklesInside50: numAt(c.tacklesInside50, i),
|
|
3787
|
+
shotsAtGoal: numAt(c.shotsAtGoal, i),
|
|
3788
|
+
scoreInvolvements: numAt(c.scoreInvolvements, i),
|
|
3789
|
+
totalPossessions: numAt(c.totalPossessions, i),
|
|
3790
|
+
timeOnGroundPercentage: numAt(c.timeOnGround, i),
|
|
3791
|
+
ratingPoints: numAt(c.ratingPoints, i),
|
|
3792
|
+
position: strAt(c.position, i),
|
|
3793
|
+
goalEfficiency: null,
|
|
3794
|
+
shotEfficiency: null,
|
|
3795
|
+
interchangeCounts: null,
|
|
3796
|
+
brownlowVotes: numAt(c.brownlowVotes, i),
|
|
3797
|
+
supercoachScore: numAt(c.supercoachScore, i),
|
|
3798
|
+
dreamTeamPoints: numAt(c.dreamTeamPoints, i),
|
|
3799
|
+
effectiveDisposals: numAt(c.effectiveDisposals, i),
|
|
3800
|
+
effectiveKicks: numAt(c.effectiveKicks, i),
|
|
3801
|
+
kickEfficiency: null,
|
|
3802
|
+
kickToHandballRatio: null,
|
|
3803
|
+
pressureActs: numAt(c.pressureActs, i),
|
|
3804
|
+
defHalfPressureActs: numAt(c.defHalfPressureActs, i),
|
|
3805
|
+
spoils: numAt(c.spoils, i),
|
|
3806
|
+
hitoutsToAdvantage: numAt(c.hitoutsToAdvantage, i),
|
|
3807
|
+
hitoutWinPercentage: numAt(c.hitoutWinPercentage, i),
|
|
3808
|
+
hitoutToAdvantageRate: null,
|
|
3809
|
+
groundBallGets: numAt(c.groundBallGets, i),
|
|
3810
|
+
f50GroundBallGets: numAt(c.f50GroundBallGets, i),
|
|
3811
|
+
interceptMarks: numAt(c.interceptMarks, i),
|
|
3812
|
+
marksOnLead: numAt(c.marksOnLead, i),
|
|
3813
|
+
contestedPossessionRate: null,
|
|
3814
|
+
contestOffOneOnOnes: numAt(c.contestOffOneOnOnes, i),
|
|
3815
|
+
contestOffWins: numAt(c.contestOffWins, i),
|
|
3816
|
+
contestOffWinsPercentage: null,
|
|
3817
|
+
contestDefOneOnOnes: numAt(c.contestDefOneOnOnes, i),
|
|
3818
|
+
contestDefLosses: numAt(c.contestDefLosses, i),
|
|
3819
|
+
contestDefLossPercentage: null,
|
|
3820
|
+
centreBounceAttendances: null,
|
|
3821
|
+
kickins: null,
|
|
3822
|
+
kickinsPlayon: null,
|
|
3823
|
+
ruckContests: numAt(c.ruckContests, i),
|
|
3824
|
+
scoreLaunches: numAt(c.scoreLaunches, i),
|
|
3825
|
+
source: "fryzigg"
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3470
3829
|
// src/transforms/player-stats.ts
|
|
3471
3830
|
function toNullable(value) {
|
|
3472
3831
|
return value ?? null;
|
|
@@ -3669,6 +4028,16 @@ async function fetchPlayerStats(query) {
|
|
|
3669
4028
|
}
|
|
3670
4029
|
return atResult;
|
|
3671
4030
|
}
|
|
4031
|
+
case "fryzigg": {
|
|
4032
|
+
const fzClient = new FryziggClient();
|
|
4033
|
+
const fzResult = await fzClient.fetchPlayerStats(competition);
|
|
4034
|
+
if (!fzResult.success) return fzResult;
|
|
4035
|
+
return transformFryziggPlayerStats(fzResult.data, {
|
|
4036
|
+
competition,
|
|
4037
|
+
season: query.season,
|
|
4038
|
+
round: query.round
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
3672
4041
|
default:
|
|
3673
4042
|
return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
|
|
3674
4043
|
}
|
|
@@ -3795,16 +4164,6 @@ async function fetchSquad(query) {
|
|
|
3795
4164
|
competition
|
|
3796
4165
|
});
|
|
3797
4166
|
}
|
|
3798
|
-
|
|
3799
|
-
// src/sources/fryzigg.ts
|
|
3800
|
-
function fetchFryziggStats() {
|
|
3801
|
-
return err(
|
|
3802
|
-
new ScrapeError(
|
|
3803
|
-
"Fryzigg data is only available in R-specific RDS binary format and cannot be consumed from TypeScript. Use the AFL API source for player statistics instead.",
|
|
3804
|
-
"fryzigg"
|
|
3805
|
-
)
|
|
3806
|
-
);
|
|
3807
|
-
}
|
|
3808
4167
|
export {
|
|
3809
4168
|
AflApiClient,
|
|
3810
4169
|
AflApiError,
|
|
@@ -3820,6 +4179,7 @@ export {
|
|
|
3820
4179
|
CompseasonListSchema,
|
|
3821
4180
|
CompseasonSchema,
|
|
3822
4181
|
FootyWireClient,
|
|
4182
|
+
FryziggClient,
|
|
3823
4183
|
LadderEntryRawSchema,
|
|
3824
4184
|
LadderResponseSchema,
|
|
3825
4185
|
MatchItemListSchema,
|
|
@@ -3854,7 +4214,6 @@ export {
|
|
|
3854
4214
|
fetchAwards,
|
|
3855
4215
|
fetchCoachesVotes,
|
|
3856
4216
|
fetchFixture,
|
|
3857
|
-
fetchFryziggStats,
|
|
3858
4217
|
fetchLadder,
|
|
3859
4218
|
fetchLineup,
|
|
3860
4219
|
fetchMatchResults,
|
|
@@ -3871,6 +4230,7 @@ export {
|
|
|
3871
4230
|
parseAflTablesDate,
|
|
3872
4231
|
parseFootyWireDate,
|
|
3873
4232
|
toAestString,
|
|
4233
|
+
transformFryziggPlayerStats,
|
|
3874
4234
|
transformLadderEntries,
|
|
3875
4235
|
transformMatchItems,
|
|
3876
4236
|
transformMatchRoster,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fitzroy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@clack/prompts": "^1.1.0",
|
|
46
|
+
"@jackemcpherson/rds-js": "0.2.0",
|
|
46
47
|
"cheerio": "^1.0.0",
|
|
47
48
|
"citty": "^0.2.1",
|
|
48
49
|
"fitzroy": "^1.4.1",
|