campus-stats 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,18 @@
1
1
  /**
2
- * Published women's fantasy data bundle (refreshed by GitLab CI cron).
3
- * Generic Package Registry paths under project 86296665.
2
+ * Published women's fantasy data bundle (refreshed by GitHub Actions cron).
3
+ * Served as assets on the rolling Release tag `data-latest`.
4
4
  */
5
- export const GITLAB_PROJECT_ID = "86296665";
6
- export const DATA_BUNDLE_PACKAGE = "campus-data";
7
- export const DATA_BUNDLE_VERSION = "latest";
5
+ export const DATA_BUNDLE_OWNER = "Minacava";
6
+ export const DATA_BUNDLE_REPO = "campus-stats";
7
+ export const DATA_BUNDLE_RELEASE_TAG = "data-latest";
8
8
  export const DATA_BUNDLE_FILE = "cache.json";
9
+ export const DATA_BUNDLE_META_FILE = "meta.json";
9
10
  /** Absolute URL of the periodically refreshed cache snapshot. */
10
- export function dataBundleUrl(projectId = GITLAB_PROJECT_ID, version = DATA_BUNDLE_VERSION) {
11
- return (`https://gitlab.com/api/v4/projects/${projectId}/packages/generic/` +
12
- `${DATA_BUNDLE_PACKAGE}/${version}/${DATA_BUNDLE_FILE}`);
11
+ export function dataBundleUrl(tag = DATA_BUNDLE_RELEASE_TAG) {
12
+ return (`https://github.com/${DATA_BUNDLE_OWNER}/${DATA_BUNDLE_REPO}` +
13
+ `/releases/download/${tag}/${DATA_BUNDLE_FILE}`);
13
14
  }
14
- export function dataBundleMetaUrl(projectId = GITLAB_PROJECT_ID, version = DATA_BUNDLE_VERSION) {
15
- return (`https://gitlab.com/api/v4/projects/${projectId}/packages/generic/` +
16
- `${DATA_BUNDLE_PACKAGE}/${version}/meta.json`);
15
+ export function dataBundleMetaUrl(tag = DATA_BUNDLE_RELEASE_TAG) {
16
+ return (`https://github.com/${DATA_BUNDLE_OWNER}/${DATA_BUNDLE_REPO}` +
17
+ `/releases/download/${tag}/${DATA_BUNDLE_META_FILE}`);
17
18
  }
@@ -1,5 +1,5 @@
1
1
  import type { CampusCache } from "../types.js";
2
- export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS competitions (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n gender TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS seasons (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n competition_id TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS teams (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS matches (\n id TEXT PRIMARY KEY,\n competition_id TEXT NOT NULL,\n season_id TEXT NOT NULL,\n date TEXT,\n home_team_id TEXT NOT NULL,\n away_team_id TEXT NOT NULL,\n home_score INTEGER,\n away_score INTEGER,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS identities (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n name TEXT NOT NULL,\n aliases_json TEXT NOT NULL,\n sources_json TEXT NOT NULL,\n confidence TEXT NOT NULL,\n status TEXT NOT NULL,\n competition_hint TEXT\n);\nCREATE TABLE IF NOT EXISTS players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n nickname TEXT,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS player_match_stats (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n minutes INTEGER,\n goals INTEGER NOT NULL,\n assists INTEGER NOT NULL,\n yellow_cards INTEGER NOT NULL,\n red_cards INTEGER NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS lineups (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n started INTEGER NOT NULL,\n jersey_number INTEGER,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS injuries (\n id TEXT PRIMARY KEY,\n player_id TEXT NOT NULL,\n team_id TEXT,\n status TEXT NOT NULL,\n description TEXT,\n from_date TEXT,\n to_date TEXT,\n sources_json TEXT NOT NULL\n);\n";
2
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS competitions (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n gender TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS seasons (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n competition_id TEXT NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS teams (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS matches (\n id TEXT PRIMARY KEY,\n competition_id TEXT NOT NULL,\n season_id TEXT NOT NULL,\n date TEXT,\n home_team_id TEXT NOT NULL,\n away_team_id TEXT NOT NULL,\n home_score INTEGER,\n away_score INTEGER,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS identities (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n name TEXT NOT NULL,\n aliases_json TEXT NOT NULL,\n sources_json TEXT NOT NULL,\n confidence TEXT NOT NULL,\n status TEXT NOT NULL,\n competition_hint TEXT\n);\nCREATE TABLE IF NOT EXISTS players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n nickname TEXT,\n country TEXT,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS player_match_stats (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n minutes INTEGER,\n goals INTEGER NOT NULL,\n assists INTEGER NOT NULL,\n yellow_cards INTEGER NOT NULL,\n red_cards INTEGER NOT NULL,\n sources_json TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS lineups (\n id TEXT PRIMARY KEY,\n match_id TEXT NOT NULL,\n team_id TEXT NOT NULL,\n player_id TEXT NOT NULL,\n started INTEGER NOT NULL,\n jersey_number INTEGER,\n sources_json TEXT NOT NULL\n);\n";
3
3
  export declare class SqliteStore {
4
4
  private readonly db;
5
5
  constructor(dbPath: string);
@@ -69,16 +69,6 @@ CREATE TABLE IF NOT EXISTS lineups (
69
69
  jersey_number INTEGER,
70
70
  sources_json TEXT NOT NULL
71
71
  );
72
- CREATE TABLE IF NOT EXISTS injuries (
73
- id TEXT PRIMARY KEY,
74
- player_id TEXT NOT NULL,
75
- team_id TEXT,
76
- status TEXT NOT NULL,
77
- description TEXT,
78
- from_date TEXT,
79
- to_date TEXT,
80
- sources_json TEXT NOT NULL
81
- );
82
72
  `;
83
73
  function j(value) {
84
74
  return JSON.stringify(value);
@@ -109,7 +99,6 @@ export class SqliteStore {
109
99
  "players",
110
100
  "player_match_stats",
111
101
  "lineups",
112
- "injuries",
113
102
  ]) {
114
103
  this.db.prepare(`DELETE FROM ${table}`).run();
115
104
  }
@@ -228,19 +217,6 @@ export class SqliteStore {
228
217
  jerseyNumber: row.jersey_number == null ? null : Number(row.jersey_number),
229
218
  sources: parseJson(String(row.sources_json)),
230
219
  }));
231
- const injuries = this.db
232
- .prepare("SELECT * FROM injuries")
233
- .all()
234
- .map((row) => ({
235
- id: String(row.id),
236
- playerId: String(row.player_id),
237
- teamId: row.team_id == null ? undefined : String(row.team_id),
238
- status: String(row.status),
239
- description: row.description == null ? undefined : String(row.description),
240
- fromDate: row.from_date == null ? undefined : String(row.from_date),
241
- toDate: row.to_date == null ? undefined : String(row.to_date),
242
- sources: parseJson(String(row.sources_json)),
243
- }));
244
220
  return {
245
221
  competitions,
246
222
  seasons,
@@ -250,7 +226,6 @@ export class SqliteStore {
250
226
  players,
251
227
  playerMatchStats,
252
228
  lineups,
253
- injuries,
254
229
  };
255
230
  }
256
231
  insertCache(cache) {
@@ -329,16 +304,5 @@ export class SqliteStore {
329
304
  for (const l of cache.lineups) {
330
305
  upsertLineup.run(l.id, l.matchId, l.teamId, l.playerId, l.started ? 1 : 0, l.jerseyNumber ?? null, j(l.sources));
331
306
  }
332
- const upsertInjury = this.db.prepare(`INSERT INTO injuries (
333
- id, player_id, team_id, status, description, from_date, to_date, sources_json
334
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
335
- ON CONFLICT(id) DO UPDATE SET
336
- player_id=excluded.player_id, team_id=excluded.team_id,
337
- status=excluded.status, description=excluded.description,
338
- from_date=excluded.from_date, to_date=excluded.to_date,
339
- sources_json=excluded.sources_json`);
340
- for (const i of cache.injuries) {
341
- upsertInjury.run(i.id, i.playerId, i.teamId ?? null, i.status, i.description ?? null, i.fromDate ?? null, i.toDate ?? null, j(i.sources));
342
- }
343
307
  }
344
308
  }
package/dist/fantasy.js CHANGED
@@ -100,7 +100,6 @@ function emptySyncResult() {
100
100
  players: [],
101
101
  playerMatchStats: [],
102
102
  lineups: [],
103
- injuries: [],
104
103
  };
105
104
  }
106
105
  function mergeSyncResults(a, b) {
@@ -109,13 +108,11 @@ function mergeSyncResults(a, b) {
109
108
  players: a.players ?? [],
110
109
  playerMatchStats: a.playerMatchStats ?? [],
111
110
  lineups: a.lineups ?? [],
112
- injuries: a.injuries ?? [],
113
111
  }), {
114
112
  ...b,
115
113
  players: b.players ?? [],
116
114
  playerMatchStats: b.playerMatchStats ?? [],
117
115
  lineups: b.lineups ?? [],
118
- injuries: b.injuries ?? [],
119
116
  });
120
117
  return {
121
118
  competitions: cache.competitions,
@@ -125,7 +122,6 @@ function mergeSyncResults(a, b) {
125
122
  players: cache.players,
126
123
  playerMatchStats: cache.playerMatchStats,
127
124
  lineups: cache.lineups,
128
- injuries: cache.injuries,
129
125
  };
130
126
  }
131
127
  function pickMatchesForPlayerStats(result, opts) {
package/dist/http.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Polite HTTP helper for HTML sources (FBref, etc.).
3
3
  * Sequential requests share a minimum delay; failed GETs retry with backoff.
4
4
  */
5
- const DEFAULT_UA = "campus/0.0.1 (women's football research; +https://gitlab.com/marina34/campus)";
5
+ const DEFAULT_UA = "campus/0.0.1 (women's football research; +https://github.com/Minacava/campus-stats)";
6
6
  function defaultSleep(ms) {
7
7
  return new Promise((resolve) => setTimeout(resolve, ms));
8
8
  }
package/dist/index.d.ts CHANGED
@@ -1,16 +1,16 @@
1
- export type { SourceRef, TeamKind, Competition, Season, Team, Match, Player, PlayerMatchStats, LineupEntry, InjuryRecord, CampusCache, } from "./types.js";
1
+ export type { SourceRef, TeamKind, Competition, Season, Team, Match, Player, PlayerMatchStats, LineupEntry, CampusCache, } from "./types.js";
2
2
  export type { IdentityKind, IdentityConfidence, IdentityStatus, CanonicalIdentity, } from "./identity/types.js";
3
3
  export type { FootballSource, SyncResult } from "./sources/types.js";
4
- export { StatsBombSource } from "./sources/statsbomb.js";
5
- export type { WomenCompetitionInfo } from "./sources/statsbomb.js";
6
- export { FbrefSource, listFbrefPilotNames } from "./sources/fbref.js";
4
+ export { StatsBombSource, FbrefSource, listFbrefPilotNames, createFootballSource, KNOWN_SOURCE_IDS, listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./sources/index.js";
5
+ export type { WomenCompetitionInfo, StatsBombAccessMode, StatsBombSourceOptions, FbrefSourceOptions, SourceId, CreateSourceOptions, StatsBombPaidExtras, StatsBombEndpointInfo, StatsBombEndpointId, StatsBombEndpointAvailability, } from "./sources/index.js";
6
+ export { SB_USERNAME_ENV, SB_PASSWORD_ENV, CAMPUS_STATSBOMB_USERNAME_ENV, CAMPUS_STATSBOMB_PASSWORD_ENV, CAMPUS_STATSBOMB_API_BASE_URL_ENV, DEFAULT_STATSBOMB_API_BASE, defaultConfigPath, loadConfigFile, resolveCredentials, requireStatsBombPaidLogin, maskSecret, describeCredentials, requireApiKey, } from "./credentials.js";
7
+ export type { StatsBombCredentials, CampusConfigFile, CredentialSource, ResolvedCredentials, ResolveCredentialsOptions, } from "./credentials.js";
7
8
  export { emptyCache, cachePath, loadCache, saveCache, mergeSyncResult, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, } from "./cache.js";
8
9
  export { SqliteStore, SCHEMA_SQL } from "./db/sqlite-store.js";
9
10
  export { FANTASY_PLAYER_STATS_LIMIT, listWomenCompetitions, syncFantasyBundle, updateCachedCompetitions, competitionIsInternational, } from "./fantasy.js";
10
11
  export type { FantasySyncOptions } from "./fantasy.js";
11
- export { GITLAB_PROJECT_ID, DATA_BUNDLE_PACKAGE, DATA_BUNDLE_VERSION, DATA_BUNDLE_FILE, dataBundleUrl, dataBundleMetaUrl, } from "./data-bundle.js";
12
+ export { DATA_BUNDLE_OWNER, DATA_BUNDLE_REPO, DATA_BUNDLE_RELEASE_TAG, DATA_BUNDLE_FILE, DATA_BUNDLE_META_FILE, dataBundleUrl, dataBundleMetaUrl, } from "./data-bundle.js";
12
13
  export { CampusClient } from "./client.js";
13
14
  export type { SquadMember } from "./client.js";
14
15
  export { DEFAULT_FANTASY_RULES, scorePlayerMatchStats, scoreFantasyPoints, } from "./scoring.js";
15
16
  export type { FantasyScoringRules, FantasyPointRow } from "./scoring.js";
16
- export { INJURIES_AVAILABLE, INJURIES_STATUS_MESSAGE, listInjuries, } from "./injuries.js";
package/dist/index.js CHANGED
@@ -1,9 +1,8 @@
1
- export { StatsBombSource } from "./sources/statsbomb.js";
2
- export { FbrefSource, listFbrefPilotNames } from "./sources/fbref.js";
1
+ export { StatsBombSource, FbrefSource, listFbrefPilotNames, createFootballSource, KNOWN_SOURCE_IDS, listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./sources/index.js";
2
+ export { SB_USERNAME_ENV, SB_PASSWORD_ENV, CAMPUS_STATSBOMB_USERNAME_ENV, CAMPUS_STATSBOMB_PASSWORD_ENV, CAMPUS_STATSBOMB_API_BASE_URL_ENV, DEFAULT_STATSBOMB_API_BASE, defaultConfigPath, loadConfigFile, resolveCredentials, requireStatsBombPaidLogin, maskSecret, describeCredentials, requireApiKey, } from "./credentials.js";
3
3
  export { emptyCache, cachePath, loadCache, saveCache, mergeSyncResult, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, } from "./cache.js";
4
4
  export { SqliteStore, SCHEMA_SQL } from "./db/sqlite-store.js";
5
5
  export { FANTASY_PLAYER_STATS_LIMIT, listWomenCompetitions, syncFantasyBundle, updateCachedCompetitions, competitionIsInternational, } from "./fantasy.js";
6
- export { GITLAB_PROJECT_ID, DATA_BUNDLE_PACKAGE, DATA_BUNDLE_VERSION, DATA_BUNDLE_FILE, dataBundleUrl, dataBundleMetaUrl, } from "./data-bundle.js";
6
+ export { DATA_BUNDLE_OWNER, DATA_BUNDLE_REPO, DATA_BUNDLE_RELEASE_TAG, DATA_BUNDLE_FILE, DATA_BUNDLE_META_FILE, dataBundleUrl, dataBundleMetaUrl, } from "./data-bundle.js";
7
7
  export { CampusClient } from "./client.js";
8
8
  export { DEFAULT_FANTASY_RULES, scorePlayerMatchStats, scoreFantasyPoints, } from "./scoring.js";
9
- export { INJURIES_AVAILABLE, INJURIES_STATUS_MESSAGE, listInjuries, } from "./injuries.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Factory for CLI / library source selection.
3
+ */
4
+ import type { StatsBombCredentials } from "../credentials.js";
5
+ import { type FbrefSourceOptions } from "./fbref.js";
6
+ import { type StatsBombSourceOptions } from "./statsbomb.js";
7
+ import type { FootballSource } from "./types.js";
8
+ export type SourceId = "statsbomb" | "fbref";
9
+ export declare const KNOWN_SOURCE_IDS: SourceId[];
10
+ export interface CreateSourceOptions {
11
+ statsbomb?: StatsBombSourceOptions;
12
+ fbref?: FbrefSourceOptions;
13
+ /** Convenience: StatsBomb customer login for paid API mode. */
14
+ credentials?: StatsBombCredentials;
15
+ }
16
+ export declare function createFootballSource(id: string, options?: CreateSourceOptions): FootballSource;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Factory for CLI / library source selection.
3
+ */
4
+ import { FbrefSource } from "./fbref.js";
5
+ import { StatsBombSource, } from "./statsbomb.js";
6
+ export const KNOWN_SOURCE_IDS = ["statsbomb", "fbref"];
7
+ export function createFootballSource(id, options = {}) {
8
+ const normalized = id.trim().toLowerCase();
9
+ if (normalized === "statsbomb") {
10
+ return new StatsBombSource({
11
+ ...options.statsbomb,
12
+ credentials: options.credentials ?? options.statsbomb?.credentials,
13
+ });
14
+ }
15
+ if (normalized === "fbref") {
16
+ return new FbrefSource(options.fbref);
17
+ }
18
+ if (normalized === "live") {
19
+ throw new Error('Source "live" was removed. Use --source statsbomb with SB_USERNAME / ' +
20
+ "SB_PASSWORD for the StatsBomb paid API (see docs/byok.md).");
21
+ }
22
+ throw new Error(`Unknown source: "${id}". Use ${KNOWN_SOURCE_IDS.join(", ")}.`);
23
+ }
@@ -6,6 +6,9 @@
6
6
  * polite HttpClient defaults. This environment often receives Cloudflare
7
7
  * 403s; use fixtures / --fixture for offline sync.
8
8
  *
9
+ * Sports Reference does not offer a public FBref API key — Campus has no
10
+ * paid-key path for this source (see docs/byok.md).
11
+ *
9
12
  * See docs/fbref-pilot.md for competition ids and HTML shape.
10
13
  */
11
14
  import { HttpClient } from "../http.js";
@@ -6,6 +6,9 @@
6
6
  * polite HttpClient defaults. This environment often receives Cloudflare
7
7
  * 403s; use fixtures / --fixture for offline sync.
8
8
  *
9
+ * Sports Reference does not offer a public FBref API key — Campus has no
10
+ * paid-key path for this source (see docs/byok.md).
11
+ *
9
12
  * See docs/fbref-pilot.md for competition ids and HTML shape.
10
13
  */
11
14
  import { HttpClient } from "../http.js";
@@ -1,3 +1,9 @@
1
1
  export type { FootballSource, SyncResult } from "./types.js";
2
2
  export { StatsBombSource } from "./statsbomb.js";
3
+ export type { StatsBombAccessMode, StatsBombSourceOptions, WomenCompetitionInfo, } from "./statsbomb.js";
3
4
  export { FbrefSource, listFbrefPilotNames } from "./fbref.js";
5
+ export type { FbrefSourceOptions } from "./fbref.js";
6
+ export { createFootballSource, KNOWN_SOURCE_IDS } from "./create.js";
7
+ export type { SourceId, CreateSourceOptions } from "./create.js";
8
+ export { listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, buildPaidEndpointUrl, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./statsbomb-endpoints.js";
9
+ export type { StatsBombPaidExtras, StatsBombEndpointInfo, StatsBombEndpointId, StatsBombEndpointAvailability, } from "./statsbomb-endpoints.js";
@@ -1,2 +1,4 @@
1
1
  export { StatsBombSource } from "./statsbomb.js";
2
2
  export { FbrefSource, listFbrefPilotNames } from "./fbref.js";
3
+ export { createFootballSource, KNOWN_SOURCE_IDS } from "./create.js";
4
+ export { listStatsBombEndpoints, paidExtrasSummary, statsBombPaidUrls, buildPaidEndpointUrl, STATSBOMB_API_VERSIONS, STATSBOMB_ENDPOINT_CATALOG, } from "./statsbomb-endpoints.js";
@@ -0,0 +1,57 @@
1
+ /**
2
+ * StatsBomb Data API endpoint catalogue.
3
+ *
4
+ * Free Open Data and the paid customer API share many concepts; paid adds
5
+ * aggregate / 360 / mapping endpoints and licensed competition coverage.
6
+ *
7
+ * Host (paid): https://data.statsbombservices.com
8
+ * Auth: HTTP Basic (SB_USERNAME / SB_PASSWORD) — same as statsbombpy.
9
+ */
10
+ /** Default API versions (statsbombpy fallbacks when live map is unavailable). */
11
+ export declare const STATSBOMB_API_VERSIONS: {
12
+ readonly competitions: "v4";
13
+ readonly matches: "v6";
14
+ readonly lineups: "v5";
15
+ readonly events: "v11";
16
+ readonly frames360: "v2";
17
+ readonly playerMatchStats: "v8";
18
+ readonly playerSeasonStats: "v7";
19
+ readonly teamMatchStats: "v4";
20
+ readonly teamSeasonStats: "v4";
21
+ };
22
+ export type StatsBombEndpointId = "competitions" | "matches" | "lineups" | "events" | "frames360" | "playerMatchStats" | "teamMatchStats" | "playerSeasonStats" | "teamSeasonStats" | "playerMapping" | "endpointVersions";
23
+ export type StatsBombEndpointAvailability = "wired" | "optional-paid";
24
+ export interface StatsBombEndpointInfo {
25
+ id: StatsBombEndpointId;
26
+ name: string;
27
+ paidPath?: string;
28
+ openDataPath?: string;
29
+ availability: StatsBombEndpointAvailability;
30
+ wiredInCampus: boolean;
31
+ requiresPaidLicense: boolean;
32
+ notes: string;
33
+ }
34
+ export declare const STATSBOMB_ENDPOINT_CATALOG: StatsBombEndpointInfo[];
35
+ /** Optional paid extras the CLI can request once a login is present. */
36
+ export interface StatsBombPaidExtras {
37
+ playerMatchStats?: boolean;
38
+ teamMatchStats?: boolean;
39
+ playerSeasonStats?: boolean;
40
+ teamSeasonStats?: boolean;
41
+ frames360?: boolean;
42
+ }
43
+ export declare function listStatsBombEndpoints(): StatsBombEndpointInfo[];
44
+ export declare function paidExtrasSummary(extras?: StatsBombPaidExtras): string[];
45
+ export declare function buildPaidEndpointUrl(template: string, params: Record<string, string | number>, apiBaseUrl?: string): string;
46
+ export declare function statsBombPaidUrls(apiBaseUrl?: string): {
47
+ competitions: () => string;
48
+ matches: (competitionId: number, seasonId: number) => string;
49
+ lineups: (matchId: string | number) => string;
50
+ events: (matchId: string | number) => string;
51
+ frames360: (matchId: string | number) => string;
52
+ playerMatchStats: (matchId: string | number) => string;
53
+ teamMatchStats: (matchId: string | number) => string;
54
+ playerSeasonStats: (competitionId: number, seasonId: number) => string;
55
+ teamSeasonStats: (competitionId: number, seasonId: number) => string;
56
+ endpointVersions: () => string;
57
+ };
@@ -0,0 +1,169 @@
1
+ /**
2
+ * StatsBomb Data API endpoint catalogue.
3
+ *
4
+ * Free Open Data and the paid customer API share many concepts; paid adds
5
+ * aggregate / 360 / mapping endpoints and licensed competition coverage.
6
+ *
7
+ * Host (paid): https://data.statsbombservices.com
8
+ * Auth: HTTP Basic (SB_USERNAME / SB_PASSWORD) — same as statsbombpy.
9
+ */
10
+ import { DEFAULT_STATSBOMB_API_BASE } from "../credentials.js";
11
+ /** Default API versions (statsbombpy fallbacks when live map is unavailable). */
12
+ export const STATSBOMB_API_VERSIONS = {
13
+ competitions: "v4",
14
+ matches: "v6",
15
+ lineups: "v5",
16
+ events: "v11",
17
+ frames360: "v2",
18
+ playerMatchStats: "v8",
19
+ playerSeasonStats: "v7",
20
+ teamMatchStats: "v4",
21
+ teamSeasonStats: "v4",
22
+ };
23
+ export const STATSBOMB_ENDPOINT_CATALOG = [
24
+ {
25
+ id: "competitions",
26
+ name: "Competitions",
27
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.competitions}/competitions`,
28
+ openDataPath: "/competitions.json",
29
+ availability: "wired",
30
+ wiredInCampus: true,
31
+ requiresPaidLicense: false,
32
+ notes: "Paid returns only competition-seasons in your contract; open-data is the public subset.",
33
+ },
34
+ {
35
+ id: "matches",
36
+ name: "Matches in competition season",
37
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.matches}/competitions/{competition_id}/seasons/{season_id}/matches`,
38
+ openDataPath: "/matches/{competition_id}/{season_id}.json",
39
+ availability: "wired",
40
+ wiredInCampus: true,
41
+ requiresPaidLicense: false,
42
+ notes: "Core schedule + scores.",
43
+ },
44
+ {
45
+ id: "lineups",
46
+ name: "Lineups for match",
47
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.lineups}/lineups/{match_id}`,
48
+ openDataPath: "/lineups/{match_id}.json",
49
+ availability: "wired",
50
+ wiredInCampus: true,
51
+ requiresPaidLicense: false,
52
+ notes: "Used when --with-players (capped).",
53
+ },
54
+ {
55
+ id: "events",
56
+ name: "Events in match",
57
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.events}/events/{match_id}`,
58
+ openDataPath: "/events/{match_id}.json",
59
+ availability: "wired",
60
+ wiredInCampus: true,
61
+ requiresPaidLicense: false,
62
+ notes: "Used when --with-players.",
63
+ },
64
+ {
65
+ id: "playerMatchStats",
66
+ name: "Player match stats (aggregated)",
67
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.playerMatchStats}/matches/{match_id}/player-stats`,
68
+ availability: "optional-paid",
69
+ wiredInCampus: false,
70
+ requiresPaidLicense: true,
71
+ notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-player-match-stats.",
72
+ },
73
+ {
74
+ id: "teamMatchStats",
75
+ name: "Team match stats (aggregated)",
76
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.teamMatchStats}/matches/{match_id}/team-stats`,
77
+ availability: "optional-paid",
78
+ wiredInCampus: false,
79
+ requiresPaidLicense: true,
80
+ notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-team-match-stats.",
81
+ },
82
+ {
83
+ id: "playerSeasonStats",
84
+ name: "Player season stats",
85
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.playerSeasonStats}/competitions/{competition_id}/seasons/{season_id}/player-stats`,
86
+ availability: "optional-paid",
87
+ wiredInCampus: false,
88
+ requiresPaidLicense: true,
89
+ notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-player-season-stats.",
90
+ },
91
+ {
92
+ id: "teamSeasonStats",
93
+ name: "Team season stats",
94
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.teamSeasonStats}/competitions/{competition_id}/seasons/{season_id}/team-stats`,
95
+ availability: "optional-paid",
96
+ wiredInCampus: false,
97
+ requiresPaidLicense: true,
98
+ notes: "From the StatsBomb paid API when your licence includes it. Use --with-paid-team-season-stats.",
99
+ },
100
+ {
101
+ id: "frames360",
102
+ name: "360 freeze frames",
103
+ paidPath: `/api/${STATSBOMB_API_VERSIONS.frames360}/360-frames/{match_id}`,
104
+ openDataPath: "/three-sixty/{match_id}.json",
105
+ availability: "optional-paid",
106
+ wiredInCampus: false,
107
+ requiresPaidLicense: true,
108
+ notes: "From the StatsBomb paid API (360 licence). Use --with-paid-360.",
109
+ },
110
+ {
111
+ id: "playerMapping",
112
+ name: "Player mapping (live ↔ offline ids)",
113
+ paidPath: "/api/v*/player-mapping (see StatsBomb Data Hub)",
114
+ availability: "optional-paid",
115
+ wiredInCampus: false,
116
+ requiresPaidLicense: true,
117
+ notes: "From the StatsBomb paid API / Data Hub when licensed.",
118
+ },
119
+ {
120
+ id: "endpointVersions",
121
+ name: "Endpoint versions map",
122
+ paidPath: "/api/endpoint-versions",
123
+ availability: "optional-paid",
124
+ wiredInCampus: false,
125
+ requiresPaidLicense: true,
126
+ notes: "From the StatsBomb paid API; Campus uses version fallbacks today.",
127
+ },
128
+ ];
129
+ export function listStatsBombEndpoints() {
130
+ return STATSBOMB_ENDPOINT_CATALOG;
131
+ }
132
+ export function paidExtrasSummary(extras = {}) {
133
+ const enabled = [];
134
+ if (extras.playerMatchStats)
135
+ enabled.push("playerMatchStats");
136
+ if (extras.teamMatchStats)
137
+ enabled.push("teamMatchStats");
138
+ if (extras.playerSeasonStats)
139
+ enabled.push("playerSeasonStats");
140
+ if (extras.teamSeasonStats)
141
+ enabled.push("teamSeasonStats");
142
+ if (extras.frames360)
143
+ enabled.push("frames360");
144
+ return enabled;
145
+ }
146
+ export function buildPaidEndpointUrl(template, params, apiBaseUrl = DEFAULT_STATSBOMB_API_BASE) {
147
+ let path = template;
148
+ for (const [key, value] of Object.entries(params)) {
149
+ path = path.replace(`{${key}}`, String(value));
150
+ }
151
+ const base = apiBaseUrl.replace(/\/$/, "");
152
+ return path.startsWith("http") ? path : `${base}${path}`;
153
+ }
154
+ export function statsBombPaidUrls(apiBaseUrl = DEFAULT_STATSBOMB_API_BASE) {
155
+ const base = apiBaseUrl.replace(/\/$/, "");
156
+ const v = STATSBOMB_API_VERSIONS;
157
+ return {
158
+ competitions: () => `${base}/api/${v.competitions}/competitions`,
159
+ matches: (competitionId, seasonId) => `${base}/api/${v.matches}/competitions/${competitionId}/seasons/${seasonId}/matches`,
160
+ lineups: (matchId) => `${base}/api/${v.lineups}/lineups/${matchId}`,
161
+ events: (matchId) => `${base}/api/${v.events}/events/${matchId}`,
162
+ frames360: (matchId) => `${base}/api/${v.frames360}/360-frames/${matchId}`,
163
+ playerMatchStats: (matchId) => `${base}/api/${v.playerMatchStats}/matches/${matchId}/player-stats`,
164
+ teamMatchStats: (matchId) => `${base}/api/${v.teamMatchStats}/matches/${matchId}/team-stats`,
165
+ playerSeasonStats: (competitionId, seasonId) => `${base}/api/${v.playerSeasonStats}/competitions/${competitionId}/seasons/${seasonId}/player-stats`,
166
+ teamSeasonStats: (competitionId, seasonId) => `${base}/api/${v.teamSeasonStats}/competitions/${competitionId}/seasons/${seasonId}/team-stats`,
167
+ endpointVersions: () => `${base}/api/endpoint-versions`,
168
+ };
169
+ }
@@ -1,16 +1,21 @@
1
1
  /**
2
- * StatsBomb Open Data adapter.
2
+ * StatsBomb adapter — free Open Data by default, optional paid API.
3
3
  *
4
- * Upstream: https://github.com/statsbomb/open-data
5
- * Free for research and genuine football-analytics use. If you publish
6
- * analysis built on this data, credit StatsBomb
7
- * (https://statsbomb.com/media-pack/). Pass that requirement downstream.
4
+ * Free (no login):
5
+ * https://github.com/statsbomb/open-data
6
+ * Credit StatsBomb in published analysis (https://statsbomb.com/media-pack/).
8
7
  *
9
- * This adapter only surfaces women's competitions
10
- * (`competition_gender === "female"`).
8
+ * Paid (customer login same as statsbombpy):
9
+ * SB_USERNAME + SB_PASSWORD → https://data.statsbombservices.com
10
+ * Coverage and freshness follow your StatsBomb contract.
11
+ *
12
+ * Women's competitions only (`competition_gender === "female"`).
11
13
  */
12
14
  import type { LineupEntry, Match, Player, PlayerMatchStats } from "../types.js";
15
+ import { type StatsBombCredentials } from "../credentials.js";
13
16
  import type { FootballSource, SyncResult } from "./types.js";
17
+ import { type StatsBombPaidExtras } from "./statsbomb-endpoints.js";
18
+ export type StatsBombAccessMode = "open-data" | "paid";
14
19
  export interface WomenCompetitionInfo {
15
20
  id: number;
16
21
  name: string;
@@ -22,25 +27,70 @@ export interface WomenCompetitionInfo {
22
27
  }>;
23
28
  }
24
29
  export interface StatsBombSourceOptions {
25
- /** Override open-data root (default: GitHub raw master/data). */
30
+ /**
31
+ * Customer login for the paid StatsBomb API. When both username and
32
+ * password are set, Campus hits data.statsbombservices.com instead of
33
+ * free Open Data.
34
+ */
35
+ credentials?: StatsBombCredentials;
36
+ /**
37
+ * Override open-data root (default: GitHub raw master/data).
38
+ * Alias: `baseUrl` (kept for fixtures / existing callers).
39
+ */
40
+ openDataBaseUrl?: string;
41
+ /** @deprecated Prefer `openDataBaseUrl`. */
26
42
  baseUrl?: string;
43
+ /** Override paid API host (default: data.statsbombservices.com). */
44
+ apiBaseUrl?: string;
27
45
  /** Inject fetch for offline fixtures / tests. */
28
46
  fetchJson?: <T>(url: string) => Promise<T>;
29
47
  /** When true, also pull lineups/events and aggregate v1 player stats. */
30
48
  includePlayerStats?: boolean;
31
49
  /** Cap matches enriched with player stats (default 5). */
32
50
  playerStatsLimit?: number;
51
+ /**
52
+ * Optional paid-only endpoints (aggregates, 360, …). Ignored in open-data
53
+ * mode. Requires a StatsBomb customer login + licence that includes them.
54
+ */
55
+ paidExtras?: StatsBombPaidExtras;
33
56
  }
34
57
  export declare class StatsBombSource implements FootballSource {
35
58
  readonly id = "statsbomb";
36
- private readonly baseUrl;
59
+ readonly accessMode: StatsBombAccessMode;
60
+ private readonly openDataBaseUrl;
61
+ private readonly apiBaseUrl;
62
+ private readonly username?;
63
+ private readonly password?;
37
64
  private readonly fetchJson;
38
65
  private readonly includePlayerStats;
39
66
  private readonly playerStatsLimit;
67
+ private readonly paidExtras;
40
68
  constructor(options?: StatsBombSourceOptions);
69
+ private paidUrls;
70
+ private competitionsUrl;
71
+ private matchesUrl;
72
+ private lineupsUrl;
73
+ private eventsUrl;
41
74
  /** Catalogue of women's competitions (clubs + national-team tournaments). */
42
75
  listWomenCompetitions(): Promise<WomenCompetitionInfo[]>;
43
76
  syncCompetition(competitionName: string): Promise<SyncResult>;
77
+ /**
78
+ * Hit optional paid-only endpoints for a sample competition/season/match.
79
+ * Confirms the licence covers them; Campus does not yet map these into the
80
+ * canonical schema (probe only).
81
+ */
82
+ probePaidExtras(context: {
83
+ competitionId: number;
84
+ seasonId: number;
85
+ matchId?: string | number;
86
+ }): Promise<Array<{
87
+ id: string;
88
+ ok: boolean;
89
+ url: string;
90
+ itemCount?: number;
91
+ error?: string;
92
+ mappedToCampusSchema: false;
93
+ }>>;
44
94
  /** Load aggregated v1 player stats + lineups for the given canonical matches. */
45
95
  loadPlayerStatsForMatches(matches: Match[]): Promise<{
46
96
  players: Player[];