fitzroy 1.6.2 → 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 CHANGED
@@ -3563,6 +3563,374 @@ var init_player_details = __esm({
3563
3563
  }
3564
3564
  });
3565
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
+
3566
3934
  // src/transforms/player-stats.ts
3567
3935
  function toNullable(value) {
3568
3936
  return value ?? null;
@@ -3771,6 +4139,16 @@ async function fetchPlayerStats(query) {
3771
4139
  }
3772
4140
  return atResult;
3773
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
+ }
3774
4152
  default:
3775
4153
  return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
3776
4154
  }
@@ -3785,6 +4163,8 @@ var init_player_stats2 = __esm({
3785
4163
  init_afl_api();
3786
4164
  init_afl_tables();
3787
4165
  init_footywire();
4166
+ init_fryzigg();
4167
+ init_fryzigg_player_stats();
3788
4168
  init_player_stats();
3789
4169
  }
3790
4170
  });
@@ -4379,7 +4759,13 @@ var init_validation2 = __esm({
4379
4759
  "use strict";
4380
4760
  init_team_mapping();
4381
4761
  init_date_utils();
4382
- VALID_SOURCES = ["afl-api", "footywire", "afl-tables", "squiggle"];
4762
+ VALID_SOURCES = [
4763
+ "afl-api",
4764
+ "footywire",
4765
+ "afl-tables",
4766
+ "squiggle",
4767
+ "fryzigg"
4768
+ ];
4383
4769
  VALID_COMPETITIONS = ["AFLM", "AFLW"];
4384
4770
  VALID_FORMATS = ["table", "json", "csv"];
4385
4771
  VALID_SUMMARIES = ["totals", "averages"];
@@ -5400,7 +5786,7 @@ resolveAliases();
5400
5786
  var main = defineCommand11({
5401
5787
  meta: {
5402
5788
  name: "fitzroy",
5403
- version: "1.6.2",
5789
+ version: "1.7.0",
5404
5790
  description: "TypeScript port of the fitzRoy R package \u2014 fetch AFL data from the command line"
5405
5791
  },
5406
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
- * Attempt to fetch advanced player statistics from Fryzigg.
2161
+ * Fryzigg RDS client.
2157
2162
  *
2158
- * @returns Always returns an error Result explaining that Fryzigg is
2159
- * not supported in the TypeScript port.
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 function fetchFryziggStats(): Result<PlayerStats[], ScrapeError>;
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, fetchFryziggStats, fetchFixture, fetchCoachesVotes, fetchAwards, err, computeLadder, ValidationError, UnsupportedSourceError, TransformContext, TeamStatsSummaryType, TeamStatsQuery, TeamStatsEntry, TeamScoreSchema, TeamScore, TeamQuery, TeamPlayersSchema, TeamPlayers, TeamListSchema, TeamList, TeamItemSchema, TeamItem, Team, SquiggleStandingsResponseSchema, SquiggleStandingsResponse, SquiggleStandingSchema, SquiggleStanding, SquiggleGamesResponseSchema, SquiggleGamesResponse, SquiggleGameSchema, SquiggleGame, SquiggleClientOptions, SquiggleClient, SquadSchema, SquadQuery, SquadPlayerItemSchema, SquadPlayerItem, SquadPlayerInnerSchema, SquadPlayer, SquadListSchema, SquadList, Squad, SeasonRoundQuery, ScrapeError, ScoreSchema, Score, RoundType, RoundSchema, RoundListSchema, RoundList, Round, RosterPlayerSchema, RosterPlayer, RisingStarNomination, Result, QuarterScore, PlayerStatsQuery, PlayerStatsListSchema, PlayerStatsList, PlayerStatsItemSchema, PlayerStatsItem, PlayerStats, PlayerGameStatsSchema, PlayerGameStats, PlayerDetailsQuery, PlayerDetails, PeriodScoreSchema, PeriodScore, Ok, MatchStatus, MatchRosterSchema, MatchRoster, MatchResult, MatchQuery, MatchItemSchema, MatchItemListSchema, MatchItemList, MatchItem, LineupQuery, LineupPlayer, Lineup, LadderResponseSchema, LadderResponse, LadderQuery, LadderEntryRawSchema, LadderEntryRaw, LadderEntry, Ladder, FootyWireClientOptions, FootyWireClient, Fixture, Err, DataSource, CompseasonSchema, CompseasonListSchema, CompseasonList, Compseason, CompetitionSchema, CompetitionListSchema, CompetitionList, CompetitionCode, Competition, CoachesVoteQuery, CoachesVote, CfsVenueSchema, CfsVenue, CfsScoreSchema, CfsScore, CfsMatchTeamSchema, CfsMatchTeam, CfsMatchSchema, CfsMatch, BrownlowVote, AwardType, AwardQuery, Award, AllAustralianSelection, AflTablesClientOptions, AflTablesClient, AflCoachesClientOptions, AflCoachesClient, AflApiTokenSchema, AflApiToken, AflApiError, AflApiClientOptions, AflApiClient };
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
@@ -3475,6 +3475,357 @@ async function fetchPlayerDetails(query) {
3475
3475
  }
3476
3476
  }
3477
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
+
3478
3829
  // src/transforms/player-stats.ts
3479
3830
  function toNullable(value) {
3480
3831
  return value ?? null;
@@ -3677,6 +4028,16 @@ async function fetchPlayerStats(query) {
3677
4028
  }
3678
4029
  return atResult;
3679
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
+ }
3680
4041
  default:
3681
4042
  return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
3682
4043
  }
@@ -3803,16 +4164,6 @@ async function fetchSquad(query) {
3803
4164
  competition
3804
4165
  });
3805
4166
  }
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
4167
  export {
3817
4168
  AflApiClient,
3818
4169
  AflApiError,
@@ -3828,6 +4179,7 @@ export {
3828
4179
  CompseasonListSchema,
3829
4180
  CompseasonSchema,
3830
4181
  FootyWireClient,
4182
+ FryziggClient,
3831
4183
  LadderEntryRawSchema,
3832
4184
  LadderResponseSchema,
3833
4185
  MatchItemListSchema,
@@ -3862,7 +4214,6 @@ export {
3862
4214
  fetchAwards,
3863
4215
  fetchCoachesVotes,
3864
4216
  fetchFixture,
3865
- fetchFryziggStats,
3866
4217
  fetchLadder,
3867
4218
  fetchLineup,
3868
4219
  fetchMatchResults,
@@ -3879,6 +4230,7 @@ export {
3879
4230
  parseAflTablesDate,
3880
4231
  parseFootyWireDate,
3881
4232
  toAestString,
4233
+ transformFryziggPlayerStats,
3882
4234
  transformLadderEntries,
3883
4235
  transformMatchItems,
3884
4236
  transformMatchRoster,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitzroy",
3
- "version": "1.6.2",
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",