fitzroy 1.6.2 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +403 -3
- package/dist/index.d.ts +41 -6
- package/dist/index.js +378 -12
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -135,7 +135,7 @@ function parseFootyWireDate(dateStr, defaultYear) {
|
|
|
135
135
|
const minutes = Number.parseInt(minStr, 10);
|
|
136
136
|
if (ampm.toLowerCase() === "pm" && aestHours < 12) aestHours += 12;
|
|
137
137
|
if (ampm.toLowerCase() === "am" && aestHours === 12) aestHours = 0;
|
|
138
|
-
const date =
|
|
138
|
+
const date = melbourneLocalToUtc(defaultYear, monthIndex, day, aestHours, minutes);
|
|
139
139
|
if (Number.isNaN(date.getTime())) return null;
|
|
140
140
|
return date;
|
|
141
141
|
}
|
|
@@ -168,6 +168,20 @@ function resolveDefaultSeason(competition = "AFLM") {
|
|
|
168
168
|
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
169
169
|
return competition === "AFLW" ? year - 1 : year;
|
|
170
170
|
}
|
|
171
|
+
function melbourneLocalToUtc(year, monthIndex, day, hours, minutes) {
|
|
172
|
+
const aestGuess = new Date(Date.UTC(year, monthIndex, day, hours - 10, minutes));
|
|
173
|
+
const parts = new Intl.DateTimeFormat("en-AU", {
|
|
174
|
+
timeZone: "Australia/Melbourne",
|
|
175
|
+
day: "2-digit",
|
|
176
|
+
hour: "2-digit",
|
|
177
|
+
hour12: false
|
|
178
|
+
}).formatToParts(aestGuess);
|
|
179
|
+
const getNum = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
180
|
+
if (getNum("day") === day && getNum("hour") === hours % 24) {
|
|
181
|
+
return aestGuess;
|
|
182
|
+
}
|
|
183
|
+
return new Date(Date.UTC(year, monthIndex, day, hours - 11, minutes));
|
|
184
|
+
}
|
|
171
185
|
function buildUtcDate(year, monthStr, day) {
|
|
172
186
|
const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
|
|
173
187
|
if (monthIndex === void 0) {
|
|
@@ -3563,6 +3577,374 @@ var init_player_details = __esm({
|
|
|
3563
3577
|
}
|
|
3564
3578
|
});
|
|
3565
3579
|
|
|
3580
|
+
// src/sources/fryzigg.ts
|
|
3581
|
+
import { isDataFrame, parseRds, RdsError } from "@jackemcpherson/rds-js";
|
|
3582
|
+
var FRYZIGG_URLS, USER_AGENT3, FryziggClient;
|
|
3583
|
+
var init_fryzigg = __esm({
|
|
3584
|
+
"src/sources/fryzigg.ts"() {
|
|
3585
|
+
"use strict";
|
|
3586
|
+
init_errors();
|
|
3587
|
+
init_result();
|
|
3588
|
+
FRYZIGG_URLS = {
|
|
3589
|
+
AFLM: "http://www.fryziggafl.net/static/fryziggafl.rds",
|
|
3590
|
+
AFLW: "http://www.fryziggafl.net/static/aflw_player_stats.rds"
|
|
3591
|
+
};
|
|
3592
|
+
USER_AGENT3 = "fitzRoy-ts/1.0 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
3593
|
+
FryziggClient = class {
|
|
3594
|
+
fetchFn;
|
|
3595
|
+
constructor(options) {
|
|
3596
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Fetch the full player statistics dataset for a competition.
|
|
3600
|
+
*
|
|
3601
|
+
* Returns column-major DataFrame from rds-js. The caller is responsible
|
|
3602
|
+
* for filtering rows and mapping to domain types.
|
|
3603
|
+
*
|
|
3604
|
+
* @param competition - AFLM or AFLW.
|
|
3605
|
+
* @returns Column-major DataFrame with all rows, or an error.
|
|
3606
|
+
*/
|
|
3607
|
+
async fetchPlayerStats(competition) {
|
|
3608
|
+
const url = FRYZIGG_URLS[competition];
|
|
3609
|
+
try {
|
|
3610
|
+
const response = await this.fetchFn(url, {
|
|
3611
|
+
headers: { "User-Agent": USER_AGENT3 }
|
|
3612
|
+
});
|
|
3613
|
+
if (!response.ok) {
|
|
3614
|
+
return err(
|
|
3615
|
+
new ScrapeError(`Fryzigg request failed: ${response.status} (${url})`, "fryzigg")
|
|
3616
|
+
);
|
|
3617
|
+
}
|
|
3618
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
3619
|
+
const result = await parseRds(buffer);
|
|
3620
|
+
if (!isDataFrame(result)) {
|
|
3621
|
+
return err(new ScrapeError("Fryzigg RDS file did not contain a data frame", "fryzigg"));
|
|
3622
|
+
}
|
|
3623
|
+
return ok(result);
|
|
3624
|
+
} catch (cause) {
|
|
3625
|
+
if (cause instanceof RdsError) {
|
|
3626
|
+
return err(new ScrapeError(`Fryzigg RDS parse error: ${cause.message}`, "fryzigg"));
|
|
3627
|
+
}
|
|
3628
|
+
return err(
|
|
3629
|
+
new ScrapeError(
|
|
3630
|
+
`Fryzigg request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
3631
|
+
"fryzigg"
|
|
3632
|
+
)
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
});
|
|
3639
|
+
|
|
3640
|
+
// src/transforms/fryzigg-player-stats.ts
|
|
3641
|
+
function transformFryziggPlayerStats(frame, options) {
|
|
3642
|
+
const colIndex = /* @__PURE__ */ new Map();
|
|
3643
|
+
for (let i = 0; i < frame.names.length; i++) {
|
|
3644
|
+
const name = frame.names[i];
|
|
3645
|
+
if (name !== void 0) {
|
|
3646
|
+
colIndex.set(name, i);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
for (const group of REQUIRED_COLUMN_GROUPS) {
|
|
3650
|
+
if (!group.some((name) => colIndex.has(name))) {
|
|
3651
|
+
return err(
|
|
3652
|
+
new ScrapeError(
|
|
3653
|
+
`Fryzigg data frame missing required column: "${group.join('" or "')}"`,
|
|
3654
|
+
"fryzigg"
|
|
3655
|
+
)
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
const getCol = (name) => {
|
|
3660
|
+
if (name === void 0) return void 0;
|
|
3661
|
+
const idx = colIndex.get(name);
|
|
3662
|
+
if (idx === void 0) return void 0;
|
|
3663
|
+
return frame.columns[idx];
|
|
3664
|
+
};
|
|
3665
|
+
const isAflw = options.competition === "AFLW";
|
|
3666
|
+
const mapping = isAflw ? AFLW_COLUMNS : AFLM_COLUMNS;
|
|
3667
|
+
const cols = {
|
|
3668
|
+
matchId: getCol("match_id"),
|
|
3669
|
+
date: getCol(mapping.date),
|
|
3670
|
+
homeTeam: getCol(mapping.homeTeam),
|
|
3671
|
+
awayTeam: getCol(mapping.awayTeam),
|
|
3672
|
+
team: getCol(mapping.team),
|
|
3673
|
+
round: getCol(mapping.round),
|
|
3674
|
+
jumperNumber: getCol(mapping.jumperNumber),
|
|
3675
|
+
playerId: getCol("player_id"),
|
|
3676
|
+
firstName: getCol(mapping.firstName),
|
|
3677
|
+
lastName: getCol(mapping.lastName),
|
|
3678
|
+
playerName: getCol(mapping.playerName),
|
|
3679
|
+
kicks: getCol("kicks"),
|
|
3680
|
+
handballs: getCol("handballs"),
|
|
3681
|
+
disposals: getCol("disposals"),
|
|
3682
|
+
marks: getCol("marks"),
|
|
3683
|
+
goals: getCol("goals"),
|
|
3684
|
+
behinds: getCol("behinds"),
|
|
3685
|
+
tackles: getCol("tackles"),
|
|
3686
|
+
hitouts: getCol("hitouts"),
|
|
3687
|
+
freesFor: getCol(mapping.freesFor),
|
|
3688
|
+
freesAgainst: getCol(mapping.freesAgainst),
|
|
3689
|
+
contestedPossessions: getCol("contested_possessions"),
|
|
3690
|
+
uncontestedPossessions: getCol("uncontested_possessions"),
|
|
3691
|
+
contestedMarks: getCol("contested_marks"),
|
|
3692
|
+
intercepts: getCol("intercepts"),
|
|
3693
|
+
centreClearances: getCol("centre_clearances"),
|
|
3694
|
+
stoppageClearances: getCol("stoppage_clearances"),
|
|
3695
|
+
totalClearances: getCol(mapping.totalClearances),
|
|
3696
|
+
inside50s: getCol(mapping.inside50s),
|
|
3697
|
+
rebound50s: getCol(mapping.rebound50s),
|
|
3698
|
+
clangers: getCol("clangers"),
|
|
3699
|
+
turnovers: getCol("turnovers"),
|
|
3700
|
+
onePercenters: getCol("one_percenters"),
|
|
3701
|
+
bounces: getCol("bounces"),
|
|
3702
|
+
goalAssists: getCol("goal_assists"),
|
|
3703
|
+
disposalEfficiency: getCol(mapping.disposalEfficiency),
|
|
3704
|
+
metresGained: getCol("metres_gained"),
|
|
3705
|
+
marksInside50: getCol(mapping.marksInside50),
|
|
3706
|
+
tacklesInside50: getCol(mapping.tacklesInside50),
|
|
3707
|
+
shotsAtGoal: getCol("shots_at_goal"),
|
|
3708
|
+
scoreInvolvements: getCol("score_involvements"),
|
|
3709
|
+
totalPossessions: getCol(mapping.totalPossessions),
|
|
3710
|
+
timeOnGround: getCol(mapping.timeOnGround),
|
|
3711
|
+
ratingPoints: getCol("rating_points"),
|
|
3712
|
+
position: getCol(mapping.position),
|
|
3713
|
+
brownlowVotes: getCol("brownlow_votes"),
|
|
3714
|
+
supercoachScore: getCol("supercoach_score"),
|
|
3715
|
+
dreamTeamPoints: getCol(mapping.dreamTeamPoints),
|
|
3716
|
+
effectiveDisposals: getCol("effective_disposals"),
|
|
3717
|
+
effectiveKicks: getCol("effective_kicks"),
|
|
3718
|
+
pressureActs: getCol("pressure_acts"),
|
|
3719
|
+
defHalfPressureActs: getCol("def_half_pressure_acts"),
|
|
3720
|
+
spoils: getCol("spoils"),
|
|
3721
|
+
hitoutsToAdvantage: getCol("hitouts_to_advantage"),
|
|
3722
|
+
hitoutWinPercentage: getCol("hitout_win_percentage"),
|
|
3723
|
+
groundBallGets: getCol("ground_ball_gets"),
|
|
3724
|
+
f50GroundBallGets: getCol("f50_ground_ball_gets"),
|
|
3725
|
+
interceptMarks: getCol("intercept_marks"),
|
|
3726
|
+
marksOnLead: getCol("marks_on_lead"),
|
|
3727
|
+
contestOffOneOnOnes: getCol("contest_off_one_on_ones"),
|
|
3728
|
+
contestOffWins: getCol("contest_off_wins"),
|
|
3729
|
+
contestDefOneOnOnes: getCol("contest_def_one_on_ones"),
|
|
3730
|
+
contestDefLosses: getCol("contest_def_losses"),
|
|
3731
|
+
ruckContests: getCol("ruck_contests"),
|
|
3732
|
+
scoreLaunches: getCol("score_launches")
|
|
3733
|
+
};
|
|
3734
|
+
const dateCol = cols.date;
|
|
3735
|
+
const roundCol = cols.round;
|
|
3736
|
+
const nRows = dateCol ? dateCol.length : 0;
|
|
3737
|
+
const hasFilters = options.season !== void 0 || options.round !== void 0;
|
|
3738
|
+
let rowIndices = null;
|
|
3739
|
+
let rowCount = nRows;
|
|
3740
|
+
if (hasFilters) {
|
|
3741
|
+
const matching = [];
|
|
3742
|
+
for (let i = 0; i < nRows; i++) {
|
|
3743
|
+
if (options.season !== void 0) {
|
|
3744
|
+
const dateStr = dateCol?.[i];
|
|
3745
|
+
if (typeof dateStr !== "string") continue;
|
|
3746
|
+
const year = Number(dateStr.slice(0, 4));
|
|
3747
|
+
if (year !== options.season) continue;
|
|
3748
|
+
}
|
|
3749
|
+
if (options.round !== void 0 && roundCol) {
|
|
3750
|
+
const roundVal = roundCol[i];
|
|
3751
|
+
const roundNum = typeof roundVal === "string" ? Number(roundVal) : roundVal;
|
|
3752
|
+
if (roundNum !== options.round) continue;
|
|
3753
|
+
}
|
|
3754
|
+
matching.push(i);
|
|
3755
|
+
}
|
|
3756
|
+
rowIndices = matching;
|
|
3757
|
+
rowCount = matching.length;
|
|
3758
|
+
}
|
|
3759
|
+
const stats = new Array(rowCount);
|
|
3760
|
+
for (let j = 0; j < rowCount; j++) {
|
|
3761
|
+
const i = rowIndices ? rowIndices[j] : j;
|
|
3762
|
+
stats[j] = mapRow(i, cols, isAflw, options.competition);
|
|
3763
|
+
}
|
|
3764
|
+
return ok(stats);
|
|
3765
|
+
}
|
|
3766
|
+
function numAt(column, i) {
|
|
3767
|
+
if (!column) return null;
|
|
3768
|
+
const v = column[i];
|
|
3769
|
+
return typeof v === "number" ? v : null;
|
|
3770
|
+
}
|
|
3771
|
+
function strAt(column, i) {
|
|
3772
|
+
if (!column) return null;
|
|
3773
|
+
const v = column[i];
|
|
3774
|
+
return typeof v === "string" ? v : null;
|
|
3775
|
+
}
|
|
3776
|
+
function roundAt(column, i) {
|
|
3777
|
+
if (!column) return 0;
|
|
3778
|
+
const v = column[i];
|
|
3779
|
+
if (typeof v === "number") return v;
|
|
3780
|
+
if (typeof v === "string") {
|
|
3781
|
+
const n = Number(v);
|
|
3782
|
+
return Number.isNaN(n) ? 0 : n;
|
|
3783
|
+
}
|
|
3784
|
+
return 0;
|
|
3785
|
+
}
|
|
3786
|
+
function mapRow(i, c, isAflw, competition) {
|
|
3787
|
+
const dateStr = strAt(c.date, i);
|
|
3788
|
+
let firstName;
|
|
3789
|
+
let lastName;
|
|
3790
|
+
if (isAflw) {
|
|
3791
|
+
const playerName = strAt(c.playerName, i) ?? "";
|
|
3792
|
+
const commaIdx = playerName.indexOf(", ");
|
|
3793
|
+
firstName = commaIdx >= 0 ? playerName.slice(0, commaIdx) : playerName;
|
|
3794
|
+
lastName = commaIdx >= 0 ? playerName.slice(commaIdx + 2) : "";
|
|
3795
|
+
} else {
|
|
3796
|
+
firstName = strAt(c.firstName, i) ?? "";
|
|
3797
|
+
lastName = strAt(c.lastName, i) ?? "";
|
|
3798
|
+
}
|
|
3799
|
+
const team = strAt(c.team, i) ?? "";
|
|
3800
|
+
const homeTeam = strAt(c.homeTeam, i);
|
|
3801
|
+
const awayTeam = strAt(c.awayTeam, i);
|
|
3802
|
+
return {
|
|
3803
|
+
matchId: String(c.matchId?.[i] ?? ""),
|
|
3804
|
+
season: dateStr ? Number(dateStr.slice(0, 4)) : 0,
|
|
3805
|
+
roundNumber: roundAt(c.round, i),
|
|
3806
|
+
team: normaliseTeamName(team),
|
|
3807
|
+
competition,
|
|
3808
|
+
date: dateStr ? new Date(dateStr) : null,
|
|
3809
|
+
homeTeam: homeTeam ? normaliseTeamName(homeTeam) : null,
|
|
3810
|
+
awayTeam: awayTeam ? normaliseTeamName(awayTeam) : null,
|
|
3811
|
+
playerId: String(c.playerId?.[i] ?? ""),
|
|
3812
|
+
givenName: firstName,
|
|
3813
|
+
surname: lastName,
|
|
3814
|
+
displayName: `${firstName} ${lastName}`.trim(),
|
|
3815
|
+
jumperNumber: numAt(c.jumperNumber, i),
|
|
3816
|
+
kicks: numAt(c.kicks, i),
|
|
3817
|
+
handballs: numAt(c.handballs, i),
|
|
3818
|
+
disposals: numAt(c.disposals, i),
|
|
3819
|
+
marks: numAt(c.marks, i),
|
|
3820
|
+
goals: numAt(c.goals, i),
|
|
3821
|
+
behinds: numAt(c.behinds, i),
|
|
3822
|
+
tackles: numAt(c.tackles, i),
|
|
3823
|
+
hitouts: numAt(c.hitouts, i),
|
|
3824
|
+
freesFor: numAt(c.freesFor, i),
|
|
3825
|
+
freesAgainst: numAt(c.freesAgainst, i),
|
|
3826
|
+
contestedPossessions: numAt(c.contestedPossessions, i),
|
|
3827
|
+
uncontestedPossessions: numAt(c.uncontestedPossessions, i),
|
|
3828
|
+
contestedMarks: numAt(c.contestedMarks, i),
|
|
3829
|
+
intercepts: numAt(c.intercepts, i),
|
|
3830
|
+
centreClearances: numAt(c.centreClearances, i),
|
|
3831
|
+
stoppageClearances: numAt(c.stoppageClearances, i),
|
|
3832
|
+
totalClearances: numAt(c.totalClearances, i),
|
|
3833
|
+
inside50s: numAt(c.inside50s, i),
|
|
3834
|
+
rebound50s: numAt(c.rebound50s, i),
|
|
3835
|
+
clangers: numAt(c.clangers, i),
|
|
3836
|
+
turnovers: numAt(c.turnovers, i),
|
|
3837
|
+
onePercenters: numAt(c.onePercenters, i),
|
|
3838
|
+
bounces: numAt(c.bounces, i),
|
|
3839
|
+
goalAssists: numAt(c.goalAssists, i),
|
|
3840
|
+
disposalEfficiency: numAt(c.disposalEfficiency, i),
|
|
3841
|
+
metresGained: numAt(c.metresGained, i),
|
|
3842
|
+
goalAccuracy: null,
|
|
3843
|
+
marksInside50: numAt(c.marksInside50, i),
|
|
3844
|
+
tacklesInside50: numAt(c.tacklesInside50, i),
|
|
3845
|
+
shotsAtGoal: numAt(c.shotsAtGoal, i),
|
|
3846
|
+
scoreInvolvements: numAt(c.scoreInvolvements, i),
|
|
3847
|
+
totalPossessions: numAt(c.totalPossessions, i),
|
|
3848
|
+
timeOnGroundPercentage: numAt(c.timeOnGround, i),
|
|
3849
|
+
ratingPoints: numAt(c.ratingPoints, i),
|
|
3850
|
+
position: strAt(c.position, i),
|
|
3851
|
+
goalEfficiency: null,
|
|
3852
|
+
shotEfficiency: null,
|
|
3853
|
+
interchangeCounts: null,
|
|
3854
|
+
brownlowVotes: numAt(c.brownlowVotes, i),
|
|
3855
|
+
supercoachScore: numAt(c.supercoachScore, i),
|
|
3856
|
+
dreamTeamPoints: numAt(c.dreamTeamPoints, i),
|
|
3857
|
+
effectiveDisposals: numAt(c.effectiveDisposals, i),
|
|
3858
|
+
effectiveKicks: numAt(c.effectiveKicks, i),
|
|
3859
|
+
kickEfficiency: null,
|
|
3860
|
+
kickToHandballRatio: null,
|
|
3861
|
+
pressureActs: numAt(c.pressureActs, i),
|
|
3862
|
+
defHalfPressureActs: numAt(c.defHalfPressureActs, i),
|
|
3863
|
+
spoils: numAt(c.spoils, i),
|
|
3864
|
+
hitoutsToAdvantage: numAt(c.hitoutsToAdvantage, i),
|
|
3865
|
+
hitoutWinPercentage: numAt(c.hitoutWinPercentage, i),
|
|
3866
|
+
hitoutToAdvantageRate: null,
|
|
3867
|
+
groundBallGets: numAt(c.groundBallGets, i),
|
|
3868
|
+
f50GroundBallGets: numAt(c.f50GroundBallGets, i),
|
|
3869
|
+
interceptMarks: numAt(c.interceptMarks, i),
|
|
3870
|
+
marksOnLead: numAt(c.marksOnLead, i),
|
|
3871
|
+
contestedPossessionRate: null,
|
|
3872
|
+
contestOffOneOnOnes: numAt(c.contestOffOneOnOnes, i),
|
|
3873
|
+
contestOffWins: numAt(c.contestOffWins, i),
|
|
3874
|
+
contestOffWinsPercentage: null,
|
|
3875
|
+
contestDefOneOnOnes: numAt(c.contestDefOneOnOnes, i),
|
|
3876
|
+
contestDefLosses: numAt(c.contestDefLosses, i),
|
|
3877
|
+
contestDefLossPercentage: null,
|
|
3878
|
+
centreBounceAttendances: null,
|
|
3879
|
+
kickins: null,
|
|
3880
|
+
kickinsPlayon: null,
|
|
3881
|
+
ruckContests: numAt(c.ruckContests, i),
|
|
3882
|
+
scoreLaunches: numAt(c.scoreLaunches, i),
|
|
3883
|
+
source: "fryzigg"
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
var REQUIRED_COLUMN_GROUPS, AFLM_COLUMNS, AFLW_COLUMNS;
|
|
3887
|
+
var init_fryzigg_player_stats = __esm({
|
|
3888
|
+
"src/transforms/fryzigg-player-stats.ts"() {
|
|
3889
|
+
"use strict";
|
|
3890
|
+
init_errors();
|
|
3891
|
+
init_result();
|
|
3892
|
+
init_team_mapping();
|
|
3893
|
+
REQUIRED_COLUMN_GROUPS = [
|
|
3894
|
+
["match_id"],
|
|
3895
|
+
["match_date", "date"],
|
|
3896
|
+
["player_id"],
|
|
3897
|
+
["player_team", "team"]
|
|
3898
|
+
];
|
|
3899
|
+
AFLM_COLUMNS = {
|
|
3900
|
+
date: "match_date",
|
|
3901
|
+
homeTeam: "match_home_team",
|
|
3902
|
+
awayTeam: "match_away_team",
|
|
3903
|
+
team: "player_team",
|
|
3904
|
+
round: "match_round",
|
|
3905
|
+
jumperNumber: "guernsey_number",
|
|
3906
|
+
firstName: "player_first_name",
|
|
3907
|
+
lastName: "player_last_name",
|
|
3908
|
+
playerName: void 0,
|
|
3909
|
+
freesFor: "free_kicks_for",
|
|
3910
|
+
freesAgainst: "free_kicks_against",
|
|
3911
|
+
totalClearances: "clearances",
|
|
3912
|
+
inside50s: "inside_fifties",
|
|
3913
|
+
rebound50s: "rebounds",
|
|
3914
|
+
disposalEfficiency: "disposal_efficiency_percentage",
|
|
3915
|
+
marksInside50: "marks_inside_fifty",
|
|
3916
|
+
tacklesInside50: "tackles_inside_fifty",
|
|
3917
|
+
timeOnGround: "time_on_ground_percentage",
|
|
3918
|
+
position: "player_position",
|
|
3919
|
+
dreamTeamPoints: "afl_fantasy_score",
|
|
3920
|
+
totalPossessions: void 0
|
|
3921
|
+
};
|
|
3922
|
+
AFLW_COLUMNS = {
|
|
3923
|
+
date: "date",
|
|
3924
|
+
homeTeam: "home_team",
|
|
3925
|
+
awayTeam: "away_team",
|
|
3926
|
+
team: "team",
|
|
3927
|
+
round: "fixture_round",
|
|
3928
|
+
jumperNumber: "number",
|
|
3929
|
+
firstName: void 0,
|
|
3930
|
+
lastName: void 0,
|
|
3931
|
+
playerName: "player_name",
|
|
3932
|
+
freesFor: "frees_for",
|
|
3933
|
+
freesAgainst: "frees_against",
|
|
3934
|
+
totalClearances: "total_clearances",
|
|
3935
|
+
inside50s: "inside50s",
|
|
3936
|
+
rebound50s: "rebound50s",
|
|
3937
|
+
disposalEfficiency: "disposal_efficiency",
|
|
3938
|
+
marksInside50: "marks_inside50",
|
|
3939
|
+
tacklesInside50: "tackles_inside50",
|
|
3940
|
+
timeOnGround: "time_on_ground",
|
|
3941
|
+
position: "position",
|
|
3942
|
+
dreamTeamPoints: "fantasy_score",
|
|
3943
|
+
totalPossessions: "total_possessions"
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
});
|
|
3947
|
+
|
|
3566
3948
|
// src/transforms/player-stats.ts
|
|
3567
3949
|
function toNullable(value) {
|
|
3568
3950
|
return value ?? null;
|
|
@@ -3771,6 +4153,16 @@ async function fetchPlayerStats(query) {
|
|
|
3771
4153
|
}
|
|
3772
4154
|
return atResult;
|
|
3773
4155
|
}
|
|
4156
|
+
case "fryzigg": {
|
|
4157
|
+
const fzClient = new FryziggClient();
|
|
4158
|
+
const fzResult = await fzClient.fetchPlayerStats(competition);
|
|
4159
|
+
if (!fzResult.success) return fzResult;
|
|
4160
|
+
return transformFryziggPlayerStats(fzResult.data, {
|
|
4161
|
+
competition,
|
|
4162
|
+
season: query.season,
|
|
4163
|
+
round: query.round
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
3774
4166
|
default:
|
|
3775
4167
|
return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
|
|
3776
4168
|
}
|
|
@@ -3785,6 +4177,8 @@ var init_player_stats2 = __esm({
|
|
|
3785
4177
|
init_afl_api();
|
|
3786
4178
|
init_afl_tables();
|
|
3787
4179
|
init_footywire();
|
|
4180
|
+
init_fryzigg();
|
|
4181
|
+
init_fryzigg_player_stats();
|
|
3788
4182
|
init_player_stats();
|
|
3789
4183
|
}
|
|
3790
4184
|
});
|
|
@@ -4379,7 +4773,13 @@ var init_validation2 = __esm({
|
|
|
4379
4773
|
"use strict";
|
|
4380
4774
|
init_team_mapping();
|
|
4381
4775
|
init_date_utils();
|
|
4382
|
-
VALID_SOURCES = [
|
|
4776
|
+
VALID_SOURCES = [
|
|
4777
|
+
"afl-api",
|
|
4778
|
+
"footywire",
|
|
4779
|
+
"afl-tables",
|
|
4780
|
+
"squiggle",
|
|
4781
|
+
"fryzigg"
|
|
4782
|
+
];
|
|
4383
4783
|
VALID_COMPETITIONS = ["AFLM", "AFLW"];
|
|
4384
4784
|
VALID_FORMATS = ["table", "json", "csv"];
|
|
4385
4785
|
VALID_SUMMARIES = ["totals", "averages"];
|
|
@@ -5400,7 +5800,7 @@ resolveAliases();
|
|
|
5400
5800
|
var main = defineCommand11({
|
|
5401
5801
|
meta: {
|
|
5402
5802
|
name: "fitzroy",
|
|
5403
|
-
version: "1.
|
|
5803
|
+
version: "1.7.1",
|
|
5404
5804
|
description: "TypeScript port of the fitzRoy R package \u2014 fetch AFL data from the command line"
|
|
5405
5805
|
},
|
|
5406
5806
|
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
|
@@ -82,7 +82,7 @@ function parseFootyWireDate(dateStr, defaultYear) {
|
|
|
82
82
|
const minutes = Number.parseInt(minStr, 10);
|
|
83
83
|
if (ampm.toLowerCase() === "pm" && aestHours < 12) aestHours += 12;
|
|
84
84
|
if (ampm.toLowerCase() === "am" && aestHours === 12) aestHours = 0;
|
|
85
|
-
const date =
|
|
85
|
+
const date = melbourneLocalToUtc(defaultYear, monthIndex, day, aestHours, minutes);
|
|
86
86
|
if (Number.isNaN(date.getTime())) return null;
|
|
87
87
|
return date;
|
|
88
88
|
}
|
|
@@ -154,6 +154,20 @@ var MONTH_ABBREV_TO_INDEX = /* @__PURE__ */ new Map([
|
|
|
154
154
|
["november", 10],
|
|
155
155
|
["december", 11]
|
|
156
156
|
]);
|
|
157
|
+
function melbourneLocalToUtc(year, monthIndex, day, hours, minutes) {
|
|
158
|
+
const aestGuess = new Date(Date.UTC(year, monthIndex, day, hours - 10, minutes));
|
|
159
|
+
const parts = new Intl.DateTimeFormat("en-AU", {
|
|
160
|
+
timeZone: "Australia/Melbourne",
|
|
161
|
+
day: "2-digit",
|
|
162
|
+
hour: "2-digit",
|
|
163
|
+
hour12: false
|
|
164
|
+
}).formatToParts(aestGuess);
|
|
165
|
+
const getNum = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
166
|
+
if (getNum("day") === day && getNum("hour") === hours % 24) {
|
|
167
|
+
return aestGuess;
|
|
168
|
+
}
|
|
169
|
+
return new Date(Date.UTC(year, monthIndex, day, hours - 11, minutes));
|
|
170
|
+
}
|
|
157
171
|
function buildUtcDate(year, monthStr, day) {
|
|
158
172
|
const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
|
|
159
173
|
if (monthIndex === void 0) {
|
|
@@ -3475,6 +3489,357 @@ async function fetchPlayerDetails(query) {
|
|
|
3475
3489
|
}
|
|
3476
3490
|
}
|
|
3477
3491
|
|
|
3492
|
+
// src/sources/fryzigg.ts
|
|
3493
|
+
import { isDataFrame, parseRds, RdsError } from "@jackemcpherson/rds-js";
|
|
3494
|
+
var FRYZIGG_URLS = {
|
|
3495
|
+
AFLM: "http://www.fryziggafl.net/static/fryziggafl.rds",
|
|
3496
|
+
AFLW: "http://www.fryziggafl.net/static/aflw_player_stats.rds"
|
|
3497
|
+
};
|
|
3498
|
+
var USER_AGENT3 = "fitzRoy-ts/1.0 (https://github.com/jackemcpherson/fitzRoy-ts)";
|
|
3499
|
+
var FryziggClient = class {
|
|
3500
|
+
fetchFn;
|
|
3501
|
+
constructor(options) {
|
|
3502
|
+
this.fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
3503
|
+
}
|
|
3504
|
+
/**
|
|
3505
|
+
* Fetch the full player statistics dataset for a competition.
|
|
3506
|
+
*
|
|
3507
|
+
* Returns column-major DataFrame from rds-js. The caller is responsible
|
|
3508
|
+
* for filtering rows and mapping to domain types.
|
|
3509
|
+
*
|
|
3510
|
+
* @param competition - AFLM or AFLW.
|
|
3511
|
+
* @returns Column-major DataFrame with all rows, or an error.
|
|
3512
|
+
*/
|
|
3513
|
+
async fetchPlayerStats(competition) {
|
|
3514
|
+
const url = FRYZIGG_URLS[competition];
|
|
3515
|
+
try {
|
|
3516
|
+
const response = await this.fetchFn(url, {
|
|
3517
|
+
headers: { "User-Agent": USER_AGENT3 }
|
|
3518
|
+
});
|
|
3519
|
+
if (!response.ok) {
|
|
3520
|
+
return err(
|
|
3521
|
+
new ScrapeError(`Fryzigg request failed: ${response.status} (${url})`, "fryzigg")
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
3525
|
+
const result = await parseRds(buffer);
|
|
3526
|
+
if (!isDataFrame(result)) {
|
|
3527
|
+
return err(new ScrapeError("Fryzigg RDS file did not contain a data frame", "fryzigg"));
|
|
3528
|
+
}
|
|
3529
|
+
return ok(result);
|
|
3530
|
+
} catch (cause) {
|
|
3531
|
+
if (cause instanceof RdsError) {
|
|
3532
|
+
return err(new ScrapeError(`Fryzigg RDS parse error: ${cause.message}`, "fryzigg"));
|
|
3533
|
+
}
|
|
3534
|
+
return err(
|
|
3535
|
+
new ScrapeError(
|
|
3536
|
+
`Fryzigg request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
3537
|
+
"fryzigg"
|
|
3538
|
+
)
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
};
|
|
3543
|
+
|
|
3544
|
+
// src/transforms/fryzigg-player-stats.ts
|
|
3545
|
+
var REQUIRED_COLUMN_GROUPS = [
|
|
3546
|
+
["match_id"],
|
|
3547
|
+
["match_date", "date"],
|
|
3548
|
+
["player_id"],
|
|
3549
|
+
["player_team", "team"]
|
|
3550
|
+
];
|
|
3551
|
+
var AFLM_COLUMNS = {
|
|
3552
|
+
date: "match_date",
|
|
3553
|
+
homeTeam: "match_home_team",
|
|
3554
|
+
awayTeam: "match_away_team",
|
|
3555
|
+
team: "player_team",
|
|
3556
|
+
round: "match_round",
|
|
3557
|
+
jumperNumber: "guernsey_number",
|
|
3558
|
+
firstName: "player_first_name",
|
|
3559
|
+
lastName: "player_last_name",
|
|
3560
|
+
playerName: void 0,
|
|
3561
|
+
freesFor: "free_kicks_for",
|
|
3562
|
+
freesAgainst: "free_kicks_against",
|
|
3563
|
+
totalClearances: "clearances",
|
|
3564
|
+
inside50s: "inside_fifties",
|
|
3565
|
+
rebound50s: "rebounds",
|
|
3566
|
+
disposalEfficiency: "disposal_efficiency_percentage",
|
|
3567
|
+
marksInside50: "marks_inside_fifty",
|
|
3568
|
+
tacklesInside50: "tackles_inside_fifty",
|
|
3569
|
+
timeOnGround: "time_on_ground_percentage",
|
|
3570
|
+
position: "player_position",
|
|
3571
|
+
dreamTeamPoints: "afl_fantasy_score",
|
|
3572
|
+
totalPossessions: void 0
|
|
3573
|
+
};
|
|
3574
|
+
var AFLW_COLUMNS = {
|
|
3575
|
+
date: "date",
|
|
3576
|
+
homeTeam: "home_team",
|
|
3577
|
+
awayTeam: "away_team",
|
|
3578
|
+
team: "team",
|
|
3579
|
+
round: "fixture_round",
|
|
3580
|
+
jumperNumber: "number",
|
|
3581
|
+
firstName: void 0,
|
|
3582
|
+
lastName: void 0,
|
|
3583
|
+
playerName: "player_name",
|
|
3584
|
+
freesFor: "frees_for",
|
|
3585
|
+
freesAgainst: "frees_against",
|
|
3586
|
+
totalClearances: "total_clearances",
|
|
3587
|
+
inside50s: "inside50s",
|
|
3588
|
+
rebound50s: "rebound50s",
|
|
3589
|
+
disposalEfficiency: "disposal_efficiency",
|
|
3590
|
+
marksInside50: "marks_inside50",
|
|
3591
|
+
tacklesInside50: "tackles_inside50",
|
|
3592
|
+
timeOnGround: "time_on_ground",
|
|
3593
|
+
position: "position",
|
|
3594
|
+
dreamTeamPoints: "fantasy_score",
|
|
3595
|
+
totalPossessions: "total_possessions"
|
|
3596
|
+
};
|
|
3597
|
+
function transformFryziggPlayerStats(frame, options) {
|
|
3598
|
+
const colIndex = /* @__PURE__ */ new Map();
|
|
3599
|
+
for (let i = 0; i < frame.names.length; i++) {
|
|
3600
|
+
const name = frame.names[i];
|
|
3601
|
+
if (name !== void 0) {
|
|
3602
|
+
colIndex.set(name, i);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
for (const group of REQUIRED_COLUMN_GROUPS) {
|
|
3606
|
+
if (!group.some((name) => colIndex.has(name))) {
|
|
3607
|
+
return err(
|
|
3608
|
+
new ScrapeError(
|
|
3609
|
+
`Fryzigg data frame missing required column: "${group.join('" or "')}"`,
|
|
3610
|
+
"fryzigg"
|
|
3611
|
+
)
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
const getCol = (name) => {
|
|
3616
|
+
if (name === void 0) return void 0;
|
|
3617
|
+
const idx = colIndex.get(name);
|
|
3618
|
+
if (idx === void 0) return void 0;
|
|
3619
|
+
return frame.columns[idx];
|
|
3620
|
+
};
|
|
3621
|
+
const isAflw = options.competition === "AFLW";
|
|
3622
|
+
const mapping = isAflw ? AFLW_COLUMNS : AFLM_COLUMNS;
|
|
3623
|
+
const cols = {
|
|
3624
|
+
matchId: getCol("match_id"),
|
|
3625
|
+
date: getCol(mapping.date),
|
|
3626
|
+
homeTeam: getCol(mapping.homeTeam),
|
|
3627
|
+
awayTeam: getCol(mapping.awayTeam),
|
|
3628
|
+
team: getCol(mapping.team),
|
|
3629
|
+
round: getCol(mapping.round),
|
|
3630
|
+
jumperNumber: getCol(mapping.jumperNumber),
|
|
3631
|
+
playerId: getCol("player_id"),
|
|
3632
|
+
firstName: getCol(mapping.firstName),
|
|
3633
|
+
lastName: getCol(mapping.lastName),
|
|
3634
|
+
playerName: getCol(mapping.playerName),
|
|
3635
|
+
kicks: getCol("kicks"),
|
|
3636
|
+
handballs: getCol("handballs"),
|
|
3637
|
+
disposals: getCol("disposals"),
|
|
3638
|
+
marks: getCol("marks"),
|
|
3639
|
+
goals: getCol("goals"),
|
|
3640
|
+
behinds: getCol("behinds"),
|
|
3641
|
+
tackles: getCol("tackles"),
|
|
3642
|
+
hitouts: getCol("hitouts"),
|
|
3643
|
+
freesFor: getCol(mapping.freesFor),
|
|
3644
|
+
freesAgainst: getCol(mapping.freesAgainst),
|
|
3645
|
+
contestedPossessions: getCol("contested_possessions"),
|
|
3646
|
+
uncontestedPossessions: getCol("uncontested_possessions"),
|
|
3647
|
+
contestedMarks: getCol("contested_marks"),
|
|
3648
|
+
intercepts: getCol("intercepts"),
|
|
3649
|
+
centreClearances: getCol("centre_clearances"),
|
|
3650
|
+
stoppageClearances: getCol("stoppage_clearances"),
|
|
3651
|
+
totalClearances: getCol(mapping.totalClearances),
|
|
3652
|
+
inside50s: getCol(mapping.inside50s),
|
|
3653
|
+
rebound50s: getCol(mapping.rebound50s),
|
|
3654
|
+
clangers: getCol("clangers"),
|
|
3655
|
+
turnovers: getCol("turnovers"),
|
|
3656
|
+
onePercenters: getCol("one_percenters"),
|
|
3657
|
+
bounces: getCol("bounces"),
|
|
3658
|
+
goalAssists: getCol("goal_assists"),
|
|
3659
|
+
disposalEfficiency: getCol(mapping.disposalEfficiency),
|
|
3660
|
+
metresGained: getCol("metres_gained"),
|
|
3661
|
+
marksInside50: getCol(mapping.marksInside50),
|
|
3662
|
+
tacklesInside50: getCol(mapping.tacklesInside50),
|
|
3663
|
+
shotsAtGoal: getCol("shots_at_goal"),
|
|
3664
|
+
scoreInvolvements: getCol("score_involvements"),
|
|
3665
|
+
totalPossessions: getCol(mapping.totalPossessions),
|
|
3666
|
+
timeOnGround: getCol(mapping.timeOnGround),
|
|
3667
|
+
ratingPoints: getCol("rating_points"),
|
|
3668
|
+
position: getCol(mapping.position),
|
|
3669
|
+
brownlowVotes: getCol("brownlow_votes"),
|
|
3670
|
+
supercoachScore: getCol("supercoach_score"),
|
|
3671
|
+
dreamTeamPoints: getCol(mapping.dreamTeamPoints),
|
|
3672
|
+
effectiveDisposals: getCol("effective_disposals"),
|
|
3673
|
+
effectiveKicks: getCol("effective_kicks"),
|
|
3674
|
+
pressureActs: getCol("pressure_acts"),
|
|
3675
|
+
defHalfPressureActs: getCol("def_half_pressure_acts"),
|
|
3676
|
+
spoils: getCol("spoils"),
|
|
3677
|
+
hitoutsToAdvantage: getCol("hitouts_to_advantage"),
|
|
3678
|
+
hitoutWinPercentage: getCol("hitout_win_percentage"),
|
|
3679
|
+
groundBallGets: getCol("ground_ball_gets"),
|
|
3680
|
+
f50GroundBallGets: getCol("f50_ground_ball_gets"),
|
|
3681
|
+
interceptMarks: getCol("intercept_marks"),
|
|
3682
|
+
marksOnLead: getCol("marks_on_lead"),
|
|
3683
|
+
contestOffOneOnOnes: getCol("contest_off_one_on_ones"),
|
|
3684
|
+
contestOffWins: getCol("contest_off_wins"),
|
|
3685
|
+
contestDefOneOnOnes: getCol("contest_def_one_on_ones"),
|
|
3686
|
+
contestDefLosses: getCol("contest_def_losses"),
|
|
3687
|
+
ruckContests: getCol("ruck_contests"),
|
|
3688
|
+
scoreLaunches: getCol("score_launches")
|
|
3689
|
+
};
|
|
3690
|
+
const dateCol = cols.date;
|
|
3691
|
+
const roundCol = cols.round;
|
|
3692
|
+
const nRows = dateCol ? dateCol.length : 0;
|
|
3693
|
+
const hasFilters = options.season !== void 0 || options.round !== void 0;
|
|
3694
|
+
let rowIndices = null;
|
|
3695
|
+
let rowCount = nRows;
|
|
3696
|
+
if (hasFilters) {
|
|
3697
|
+
const matching = [];
|
|
3698
|
+
for (let i = 0; i < nRows; i++) {
|
|
3699
|
+
if (options.season !== void 0) {
|
|
3700
|
+
const dateStr = dateCol?.[i];
|
|
3701
|
+
if (typeof dateStr !== "string") continue;
|
|
3702
|
+
const year = Number(dateStr.slice(0, 4));
|
|
3703
|
+
if (year !== options.season) continue;
|
|
3704
|
+
}
|
|
3705
|
+
if (options.round !== void 0 && roundCol) {
|
|
3706
|
+
const roundVal = roundCol[i];
|
|
3707
|
+
const roundNum = typeof roundVal === "string" ? Number(roundVal) : roundVal;
|
|
3708
|
+
if (roundNum !== options.round) continue;
|
|
3709
|
+
}
|
|
3710
|
+
matching.push(i);
|
|
3711
|
+
}
|
|
3712
|
+
rowIndices = matching;
|
|
3713
|
+
rowCount = matching.length;
|
|
3714
|
+
}
|
|
3715
|
+
const stats = new Array(rowCount);
|
|
3716
|
+
for (let j = 0; j < rowCount; j++) {
|
|
3717
|
+
const i = rowIndices ? rowIndices[j] : j;
|
|
3718
|
+
stats[j] = mapRow(i, cols, isAflw, options.competition);
|
|
3719
|
+
}
|
|
3720
|
+
return ok(stats);
|
|
3721
|
+
}
|
|
3722
|
+
function numAt(column, i) {
|
|
3723
|
+
if (!column) return null;
|
|
3724
|
+
const v = column[i];
|
|
3725
|
+
return typeof v === "number" ? v : null;
|
|
3726
|
+
}
|
|
3727
|
+
function strAt(column, i) {
|
|
3728
|
+
if (!column) return null;
|
|
3729
|
+
const v = column[i];
|
|
3730
|
+
return typeof v === "string" ? v : null;
|
|
3731
|
+
}
|
|
3732
|
+
function roundAt(column, i) {
|
|
3733
|
+
if (!column) return 0;
|
|
3734
|
+
const v = column[i];
|
|
3735
|
+
if (typeof v === "number") return v;
|
|
3736
|
+
if (typeof v === "string") {
|
|
3737
|
+
const n = Number(v);
|
|
3738
|
+
return Number.isNaN(n) ? 0 : n;
|
|
3739
|
+
}
|
|
3740
|
+
return 0;
|
|
3741
|
+
}
|
|
3742
|
+
function mapRow(i, c, isAflw, competition) {
|
|
3743
|
+
const dateStr = strAt(c.date, i);
|
|
3744
|
+
let firstName;
|
|
3745
|
+
let lastName;
|
|
3746
|
+
if (isAflw) {
|
|
3747
|
+
const playerName = strAt(c.playerName, i) ?? "";
|
|
3748
|
+
const commaIdx = playerName.indexOf(", ");
|
|
3749
|
+
firstName = commaIdx >= 0 ? playerName.slice(0, commaIdx) : playerName;
|
|
3750
|
+
lastName = commaIdx >= 0 ? playerName.slice(commaIdx + 2) : "";
|
|
3751
|
+
} else {
|
|
3752
|
+
firstName = strAt(c.firstName, i) ?? "";
|
|
3753
|
+
lastName = strAt(c.lastName, i) ?? "";
|
|
3754
|
+
}
|
|
3755
|
+
const team = strAt(c.team, i) ?? "";
|
|
3756
|
+
const homeTeam = strAt(c.homeTeam, i);
|
|
3757
|
+
const awayTeam = strAt(c.awayTeam, i);
|
|
3758
|
+
return {
|
|
3759
|
+
matchId: String(c.matchId?.[i] ?? ""),
|
|
3760
|
+
season: dateStr ? Number(dateStr.slice(0, 4)) : 0,
|
|
3761
|
+
roundNumber: roundAt(c.round, i),
|
|
3762
|
+
team: normaliseTeamName(team),
|
|
3763
|
+
competition,
|
|
3764
|
+
date: dateStr ? new Date(dateStr) : null,
|
|
3765
|
+
homeTeam: homeTeam ? normaliseTeamName(homeTeam) : null,
|
|
3766
|
+
awayTeam: awayTeam ? normaliseTeamName(awayTeam) : null,
|
|
3767
|
+
playerId: String(c.playerId?.[i] ?? ""),
|
|
3768
|
+
givenName: firstName,
|
|
3769
|
+
surname: lastName,
|
|
3770
|
+
displayName: `${firstName} ${lastName}`.trim(),
|
|
3771
|
+
jumperNumber: numAt(c.jumperNumber, i),
|
|
3772
|
+
kicks: numAt(c.kicks, i),
|
|
3773
|
+
handballs: numAt(c.handballs, i),
|
|
3774
|
+
disposals: numAt(c.disposals, i),
|
|
3775
|
+
marks: numAt(c.marks, i),
|
|
3776
|
+
goals: numAt(c.goals, i),
|
|
3777
|
+
behinds: numAt(c.behinds, i),
|
|
3778
|
+
tackles: numAt(c.tackles, i),
|
|
3779
|
+
hitouts: numAt(c.hitouts, i),
|
|
3780
|
+
freesFor: numAt(c.freesFor, i),
|
|
3781
|
+
freesAgainst: numAt(c.freesAgainst, i),
|
|
3782
|
+
contestedPossessions: numAt(c.contestedPossessions, i),
|
|
3783
|
+
uncontestedPossessions: numAt(c.uncontestedPossessions, i),
|
|
3784
|
+
contestedMarks: numAt(c.contestedMarks, i),
|
|
3785
|
+
intercepts: numAt(c.intercepts, i),
|
|
3786
|
+
centreClearances: numAt(c.centreClearances, i),
|
|
3787
|
+
stoppageClearances: numAt(c.stoppageClearances, i),
|
|
3788
|
+
totalClearances: numAt(c.totalClearances, i),
|
|
3789
|
+
inside50s: numAt(c.inside50s, i),
|
|
3790
|
+
rebound50s: numAt(c.rebound50s, i),
|
|
3791
|
+
clangers: numAt(c.clangers, i),
|
|
3792
|
+
turnovers: numAt(c.turnovers, i),
|
|
3793
|
+
onePercenters: numAt(c.onePercenters, i),
|
|
3794
|
+
bounces: numAt(c.bounces, i),
|
|
3795
|
+
goalAssists: numAt(c.goalAssists, i),
|
|
3796
|
+
disposalEfficiency: numAt(c.disposalEfficiency, i),
|
|
3797
|
+
metresGained: numAt(c.metresGained, i),
|
|
3798
|
+
goalAccuracy: null,
|
|
3799
|
+
marksInside50: numAt(c.marksInside50, i),
|
|
3800
|
+
tacklesInside50: numAt(c.tacklesInside50, i),
|
|
3801
|
+
shotsAtGoal: numAt(c.shotsAtGoal, i),
|
|
3802
|
+
scoreInvolvements: numAt(c.scoreInvolvements, i),
|
|
3803
|
+
totalPossessions: numAt(c.totalPossessions, i),
|
|
3804
|
+
timeOnGroundPercentage: numAt(c.timeOnGround, i),
|
|
3805
|
+
ratingPoints: numAt(c.ratingPoints, i),
|
|
3806
|
+
position: strAt(c.position, i),
|
|
3807
|
+
goalEfficiency: null,
|
|
3808
|
+
shotEfficiency: null,
|
|
3809
|
+
interchangeCounts: null,
|
|
3810
|
+
brownlowVotes: numAt(c.brownlowVotes, i),
|
|
3811
|
+
supercoachScore: numAt(c.supercoachScore, i),
|
|
3812
|
+
dreamTeamPoints: numAt(c.dreamTeamPoints, i),
|
|
3813
|
+
effectiveDisposals: numAt(c.effectiveDisposals, i),
|
|
3814
|
+
effectiveKicks: numAt(c.effectiveKicks, i),
|
|
3815
|
+
kickEfficiency: null,
|
|
3816
|
+
kickToHandballRatio: null,
|
|
3817
|
+
pressureActs: numAt(c.pressureActs, i),
|
|
3818
|
+
defHalfPressureActs: numAt(c.defHalfPressureActs, i),
|
|
3819
|
+
spoils: numAt(c.spoils, i),
|
|
3820
|
+
hitoutsToAdvantage: numAt(c.hitoutsToAdvantage, i),
|
|
3821
|
+
hitoutWinPercentage: numAt(c.hitoutWinPercentage, i),
|
|
3822
|
+
hitoutToAdvantageRate: null,
|
|
3823
|
+
groundBallGets: numAt(c.groundBallGets, i),
|
|
3824
|
+
f50GroundBallGets: numAt(c.f50GroundBallGets, i),
|
|
3825
|
+
interceptMarks: numAt(c.interceptMarks, i),
|
|
3826
|
+
marksOnLead: numAt(c.marksOnLead, i),
|
|
3827
|
+
contestedPossessionRate: null,
|
|
3828
|
+
contestOffOneOnOnes: numAt(c.contestOffOneOnOnes, i),
|
|
3829
|
+
contestOffWins: numAt(c.contestOffWins, i),
|
|
3830
|
+
contestOffWinsPercentage: null,
|
|
3831
|
+
contestDefOneOnOnes: numAt(c.contestDefOneOnOnes, i),
|
|
3832
|
+
contestDefLosses: numAt(c.contestDefLosses, i),
|
|
3833
|
+
contestDefLossPercentage: null,
|
|
3834
|
+
centreBounceAttendances: null,
|
|
3835
|
+
kickins: null,
|
|
3836
|
+
kickinsPlayon: null,
|
|
3837
|
+
ruckContests: numAt(c.ruckContests, i),
|
|
3838
|
+
scoreLaunches: numAt(c.scoreLaunches, i),
|
|
3839
|
+
source: "fryzigg"
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3478
3843
|
// src/transforms/player-stats.ts
|
|
3479
3844
|
function toNullable(value) {
|
|
3480
3845
|
return value ?? null;
|
|
@@ -3677,6 +4042,16 @@ async function fetchPlayerStats(query) {
|
|
|
3677
4042
|
}
|
|
3678
4043
|
return atResult;
|
|
3679
4044
|
}
|
|
4045
|
+
case "fryzigg": {
|
|
4046
|
+
const fzClient = new FryziggClient();
|
|
4047
|
+
const fzResult = await fzClient.fetchPlayerStats(competition);
|
|
4048
|
+
if (!fzResult.success) return fzResult;
|
|
4049
|
+
return transformFryziggPlayerStats(fzResult.data, {
|
|
4050
|
+
competition,
|
|
4051
|
+
season: query.season,
|
|
4052
|
+
round: query.round
|
|
4053
|
+
});
|
|
4054
|
+
}
|
|
3680
4055
|
default:
|
|
3681
4056
|
return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
|
|
3682
4057
|
}
|
|
@@ -3803,16 +4178,6 @@ async function fetchSquad(query) {
|
|
|
3803
4178
|
competition
|
|
3804
4179
|
});
|
|
3805
4180
|
}
|
|
3806
|
-
|
|
3807
|
-
// src/sources/fryzigg.ts
|
|
3808
|
-
function fetchFryziggStats() {
|
|
3809
|
-
return err(
|
|
3810
|
-
new ScrapeError(
|
|
3811
|
-
"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.",
|
|
3812
|
-
"fryzigg"
|
|
3813
|
-
)
|
|
3814
|
-
);
|
|
3815
|
-
}
|
|
3816
4181
|
export {
|
|
3817
4182
|
AflApiClient,
|
|
3818
4183
|
AflApiError,
|
|
@@ -3828,6 +4193,7 @@ export {
|
|
|
3828
4193
|
CompseasonListSchema,
|
|
3829
4194
|
CompseasonSchema,
|
|
3830
4195
|
FootyWireClient,
|
|
4196
|
+
FryziggClient,
|
|
3831
4197
|
LadderEntryRawSchema,
|
|
3832
4198
|
LadderResponseSchema,
|
|
3833
4199
|
MatchItemListSchema,
|
|
@@ -3862,7 +4228,6 @@ export {
|
|
|
3862
4228
|
fetchAwards,
|
|
3863
4229
|
fetchCoachesVotes,
|
|
3864
4230
|
fetchFixture,
|
|
3865
|
-
fetchFryziggStats,
|
|
3866
4231
|
fetchLadder,
|
|
3867
4232
|
fetchLineup,
|
|
3868
4233
|
fetchMatchResults,
|
|
@@ -3879,6 +4244,7 @@ export {
|
|
|
3879
4244
|
parseAflTablesDate,
|
|
3880
4245
|
parseFootyWireDate,
|
|
3881
4246
|
toAestString,
|
|
4247
|
+
transformFryziggPlayerStats,
|
|
3882
4248
|
transformLadderEntries,
|
|
3883
4249
|
transformMatchItems,
|
|
3884
4250
|
transformMatchRoster,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fitzroy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
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",
|