campus-stats 0.4.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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/cache.d.ts +10 -0
- package/dist/cache.js +69 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +537 -0
- package/dist/client.d.ts +88 -0
- package/dist/client.js +273 -0
- package/dist/data-bundle.d.ts +11 -0
- package/dist/data-bundle.js +17 -0
- package/dist/db/migrate.d.ts +6 -0
- package/dist/db/migrate.js +22 -0
- package/dist/db/sqlite-store.d.ts +12 -0
- package/dist/db/sqlite-store.js +344 -0
- package/dist/db/store-api.d.ts +9 -0
- package/dist/db/store-api.js +70 -0
- package/dist/fantasy.d.ts +36 -0
- package/dist/fantasy.js +152 -0
- package/dist/http.d.ts +25 -0
- package/dist/http.js +63 -0
- package/dist/identity/match.d.ts +22 -0
- package/dist/identity/match.js +106 -0
- package/dist/identity/propose.d.ts +8 -0
- package/dist/identity/propose.js +45 -0
- package/dist/identity/rules.d.ts +8 -0
- package/dist/identity/rules.js +17 -0
- package/dist/identity/store.d.ts +5 -0
- package/dist/identity/store.js +20 -0
- package/dist/identity/types.d.ts +32 -0
- package/dist/identity/types.js +8 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +4 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/injuries.d.ts +12 -0
- package/dist/injuries.js +15 -0
- package/dist/refs.d.ts +7 -0
- package/dist/refs.js +1 -0
- package/dist/scoring.d.ts +37 -0
- package/dist/scoring.js +56 -0
- package/dist/sources/fbref-map.d.ts +14 -0
- package/dist/sources/fbref-map.js +56 -0
- package/dist/sources/fbref-parse.d.ts +17 -0
- package/dist/sources/fbref-parse.js +87 -0
- package/dist/sources/fbref.d.ts +27 -0
- package/dist/sources/fbref.js +72 -0
- package/dist/sources/index.d.ts +3 -0
- package/dist/sources/index.js +2 -0
- package/dist/sources/statsbomb-player-stats.d.ts +63 -0
- package/dist/sources/statsbomb-player-stats.js +107 -0
- package/dist/sources/statsbomb.d.ts +50 -0
- package/dist/sources/statsbomb.js +170 -0
- package/dist/sources/types.d.ts +23 -0
- package/dist/sources/types.js +1 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.js +1 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- **Rename (npm):** package **`campus`** → **`campus-stats`** so public
|
|
6
|
+
`npm install campus-stats` works (unscoped `campus` is taken on npmjs.com)
|
|
7
|
+
- Publish target: **npmjs.com** primary; GitLab Package Registry as mirror
|
|
8
|
+
- CLI bins: `campus` and `campus-stats` (same entrypoint)
|
|
9
|
+
- Import path: `import { CampusClient } from "campus-stats"`
|
|
10
|
+
- Product brand, `CampusClient`, `.campus/` cache, and `campus-data` unchanged
|
|
11
|
+
|
|
12
|
+
## 0.3.0
|
|
13
|
+
|
|
14
|
+
- **Rename:** package/CLI `campo-stats` → **`campus`** (brand alignment)
|
|
15
|
+
- API rename: `CampoClient` → `CampusClient`, `CampoCache` → `CampusCache`
|
|
16
|
+
- Local cache directory: `.campo-stats/` → `.campus/`
|
|
17
|
+
- Data bundle package: `campo-stats-data` → `campus-data`
|
|
18
|
+
- Smoke test for package identity (`name`, bin, cache paths)
|
|
19
|
+
|
|
20
|
+
## 0.2.0
|
|
21
|
+
|
|
22
|
+
- App-facing `CampusClient` API (`open` / `fromBundle` / `syncFantasy` / queries)
|
|
23
|
+
- `sync --fantasy` / `--all`, `update`, `pull`, `available`
|
|
24
|
+
- Lineups + season squad queries; default fantasy scoring
|
|
25
|
+
- Injuries API stub (empty — no open source yet)
|
|
26
|
+
- GitLab CI cron job `refresh_fantasy_data` + `docs/cron.md`
|
|
27
|
+
- Competition `international` and team `kind` (`club` | `national`)
|
|
28
|
+
|
|
29
|
+
## 0.1.0
|
|
30
|
+
|
|
31
|
+
- Initial package layout (distributed via GitLab Package Registry, not npmjs)
|
|
32
|
+
- StatsBomb + FBref (schedule) adapters
|
|
33
|
+
- JSON cache and optional SQLite persistence
|
|
34
|
+
- Player match stats (StatsBomb, capped)
|
|
35
|
+
- Cross-source team identity proposals
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Campus / campus contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Campus (`campus-stats`)
|
|
2
|
+
|
|
3
|
+
**Open, normalized, queryable data for women's football — installable in your app.**
|
|
4
|
+
|
|
5
|
+
`campus-stats` is the npm package for **Campus**: a library + CLI that pulls
|
|
6
|
+
women's football stats from public sources, normalizes them into one schema,
|
|
7
|
+
and gives your web/API/app access to competitions, seasons, clubs, national
|
|
8
|
+
teams, matches, lineups, player stats, and basic fantasy points.
|
|
9
|
+
|
|
10
|
+
Requires **Node.js ≥ 22**.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Published on **npmjs.com**:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install campus-stats
|
|
20
|
+
|
|
21
|
+
npx campus sync --competition "Liga F"
|
|
22
|
+
npx campus teams --competition "Liga F"
|
|
23
|
+
npx campus matches --competition "Liga F" --team "Barcelona"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The CLI binaries are `campus` and `campus-stats` (same entrypoint).
|
|
27
|
+
|
|
28
|
+
Maintainer publish setup: [`docs/npm.md`](./docs/npm.md).
|
|
29
|
+
Optional GitLab mirror: [`docs/gitlab-package.md`](./docs/gitlab-package.md).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Use as a library (recommended for apps)
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { CampusClient } from "campus-stats";
|
|
37
|
+
|
|
38
|
+
const client = await CampusClient.open();
|
|
39
|
+
await client.syncFantasy({ includePlayerStats: true });
|
|
40
|
+
// or: const client = await CampusClient.fromBundle();
|
|
41
|
+
|
|
42
|
+
const clubs = client.teams({ competition: "Liga F", kind: "club" });
|
|
43
|
+
const nations = client.teams({ competition: "Women's World Cup", kind: "national" });
|
|
44
|
+
const matches = client.matches({ competition: "Liga F", team: "Barcelona" });
|
|
45
|
+
const squad = client.squad({ competition: "Liga F", team: "Barcelona" });
|
|
46
|
+
const points = client.fantasyPoints({ competition: "Liga F", player: "Walsh" });
|
|
47
|
+
|
|
48
|
+
return Response.json({ clubs, nations, matches, squad, points });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### What you get from the package
|
|
52
|
+
|
|
53
|
+
| API | Purpose |
|
|
54
|
+
|-----|---------|
|
|
55
|
+
| `CampusClient` | Main entry for apps: sync / pull / query |
|
|
56
|
+
| `syncFantasyBundle` / `updateCachedCompetitions` | Low-level sync helpers |
|
|
57
|
+
| `scoreFantasyPoints` | Default fantasy scoring rules |
|
|
58
|
+
| `listInjuries` | Stable stub (empty until a source exists) |
|
|
59
|
+
| Types | `Competition`, `Team`, `Match`, `Player`, `LineupEntry`, … |
|
|
60
|
+
| CLI bins `campus` / `campus-stats` | Same data from the terminal |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Keep data fresh (cron)
|
|
65
|
+
|
|
66
|
+
1. GitLab **Pipeline schedule** on `main` (e.g. `0 6 * * *`) runs
|
|
67
|
+
`refresh_fantasy_data` and publishes `campus-data/latest/cache.json`.
|
|
68
|
+
2. Apps call `CampusClient.fromBundle()` or `npx campus pull`.
|
|
69
|
+
|
|
70
|
+
Details: [`docs/cron.md`](./docs/cron.md).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## CLI quick start
|
|
75
|
+
|
|
76
|
+
After `npm install campus-stats`:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx campus sync --fantasy # all women's comps (clubs + selecciones)
|
|
80
|
+
npx campus sync --fantasy --with-players # + lineups + player stats (capped)
|
|
81
|
+
npx campus pull # download cron bundle
|
|
82
|
+
npx campus competitions
|
|
83
|
+
npx campus matches --competition "Liga F" --team "Barcelona"
|
|
84
|
+
npx campus squad --competition "Liga F" --team "Barcelona"
|
|
85
|
+
npx campus fantasy-points --competition "Liga F"
|
|
86
|
+
npx campus injuries # empty + documented deferral
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Store: `.campus/cache.json` (or `--sqlite` / `--db <path>`).
|
|
90
|
+
|
|
91
|
+
Single-competition sync and identities still work:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx campus sync --competition "Liga F"
|
|
95
|
+
npx campus sync --source fbref --competition "WSL"
|
|
96
|
+
npx campus identities propose --competition "Liga F"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Supported data (open sources)
|
|
102
|
+
|
|
103
|
+
| Source | Coverage | Notes |
|
|
104
|
+
|--------|----------|-------|
|
|
105
|
+
| **StatsBomb Open Data** | Liga F, WSL, NWSL, Serie A Women, Frauen Bundesliga, Women's World Cup, UEFA Women's Euro | Clubs + national teams; optional player stats / lineups |
|
|
106
|
+
| **FBref** | WSL + Liga F schedules (pilot) | Live HTML may hit Cloudflare |
|
|
107
|
+
|
|
108
|
+
Injuries are **not** in these open feeds yet — the API is ready, the list is empty.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Why this shape
|
|
113
|
+
|
|
114
|
+
- **One canonical schema** (`Competition`, `Season`, `Team`, `Match`, …). Adapters translate; the app never sees provider field names.
|
|
115
|
+
- **Provenance on every record** (`sources: [{ source, id }]`) so cross-source identity is possible.
|
|
116
|
+
- **Local-first cache** (JSON by default, SQLite optional) — no hosted database required for the library.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Develop from a clone
|
|
121
|
+
|
|
122
|
+
For contributors working on this repo:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git clone https://gitlab.com/marina34/campus.git
|
|
126
|
+
cd campus
|
|
127
|
+
npm install
|
|
128
|
+
npm run build
|
|
129
|
+
npm test
|
|
130
|
+
npx campus --help
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Project plan & docs
|
|
136
|
+
|
|
137
|
+
| Epic | Description |
|
|
138
|
+
|------|-------------|
|
|
139
|
+
| [00 — Foundation](./docs/epics/00-fundacion-v0.md) | Schema, StatsBomb, CLI, JSON cache |
|
|
140
|
+
| [01 — FBref](./docs/epics/01-adapter-fbref.md) | Second public source |
|
|
141
|
+
| [02 — Identity](./docs/epics/02-resolucion-identidad.md) | Cross-source team resolution |
|
|
142
|
+
| [03 — Player stats](./docs/epics/03-stats-jugadora.md) | Per-match player stats |
|
|
143
|
+
| [04 — SQLite](./docs/epics/04-persistencia-sqlite.md) | Persistence beyond JSON |
|
|
144
|
+
| [05 — Package](./docs/epics/05-publicacion-npm.md) | npmjs + GitLab package distribution |
|
|
145
|
+
|
|
146
|
+
More: [`docs/cron.md`](./docs/cron.md) · [`docs/injuries.md`](./docs/injuries.md) · [`docs/npm.md`](./docs/npm.md) · [`docs/gitlab-package.md`](./docs/gitlab-package.md) · [`CHANGELOG.md`](./CHANGELOG.md)
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Data source & terms
|
|
151
|
+
|
|
152
|
+
**Code is MIT. Data is not ours to relicense.**
|
|
153
|
+
|
|
154
|
+
- [StatsBomb Open Data](https://github.com/statsbomb/open-data): free for research and genuine football analytics. Credit StatsBomb in published analysis ([media pack](https://statsbomb.com/media-pack/)).
|
|
155
|
+
- FBref / Sports Reference: respect site terms, `robots.txt`, and rate limits.
|
|
156
|
+
|
|
157
|
+
Pass those requirements downstream.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
Code: MIT (see [`LICENSE`](./LICENSE)). Data: subject to each source's terms.
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CampusCache } from "./types.js";
|
|
2
|
+
import type { SyncResult } from "./sources/types.js";
|
|
3
|
+
export declare const DEFAULT_CACHE_DIR = ".campus";
|
|
4
|
+
export declare const DEFAULT_CACHE_FILE = "cache.json";
|
|
5
|
+
export declare function emptyCache(): CampusCache;
|
|
6
|
+
export declare function cachePath(cwd?: string): string;
|
|
7
|
+
export declare function loadCache(filePath?: string): Promise<CampusCache>;
|
|
8
|
+
export declare function saveCache(cache: CampusCache, filePath?: string): Promise<void>;
|
|
9
|
+
/** Merge a sync result into the cache without wiping unrelated competitions. */
|
|
10
|
+
export declare function mergeSyncResult(cache: CampusCache, result: SyncResult): CampusCache;
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const DEFAULT_CACHE_DIR = ".campus";
|
|
4
|
+
export const DEFAULT_CACHE_FILE = "cache.json";
|
|
5
|
+
export function emptyCache() {
|
|
6
|
+
return {
|
|
7
|
+
competitions: [],
|
|
8
|
+
seasons: [],
|
|
9
|
+
teams: [],
|
|
10
|
+
matches: [],
|
|
11
|
+
identities: [],
|
|
12
|
+
players: [],
|
|
13
|
+
playerMatchStats: [],
|
|
14
|
+
lineups: [],
|
|
15
|
+
injuries: [],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function cachePath(cwd = process.cwd()) {
|
|
19
|
+
return path.join(cwd, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE);
|
|
20
|
+
}
|
|
21
|
+
export async function loadCache(filePath = cachePath()) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = await readFile(filePath, "utf8");
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return {
|
|
26
|
+
competitions: parsed.competitions ?? [],
|
|
27
|
+
seasons: parsed.seasons ?? [],
|
|
28
|
+
teams: parsed.teams ?? [],
|
|
29
|
+
matches: parsed.matches ?? [],
|
|
30
|
+
identities: parsed.identities ?? [],
|
|
31
|
+
players: parsed.players ?? [],
|
|
32
|
+
playerMatchStats: parsed.playerMatchStats ?? [],
|
|
33
|
+
lineups: parsed.lineups ?? [],
|
|
34
|
+
injuries: parsed.injuries ?? [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const code = err.code;
|
|
39
|
+
if (code === "ENOENT")
|
|
40
|
+
return emptyCache();
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function saveCache(cache, filePath = cachePath()) {
|
|
45
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
46
|
+
await writeFile(filePath, JSON.stringify(cache, null, 2) + "\n", "utf8");
|
|
47
|
+
}
|
|
48
|
+
function mergeById(existing, incoming) {
|
|
49
|
+
const map = new Map();
|
|
50
|
+
for (const item of existing)
|
|
51
|
+
map.set(item.id, item);
|
|
52
|
+
for (const item of incoming)
|
|
53
|
+
map.set(item.id, item);
|
|
54
|
+
return [...map.values()];
|
|
55
|
+
}
|
|
56
|
+
/** Merge a sync result into the cache without wiping unrelated competitions. */
|
|
57
|
+
export function mergeSyncResult(cache, result) {
|
|
58
|
+
return {
|
|
59
|
+
competitions: mergeById(cache.competitions, result.competitions),
|
|
60
|
+
seasons: mergeById(cache.seasons, result.seasons),
|
|
61
|
+
teams: mergeById(cache.teams, result.teams),
|
|
62
|
+
matches: mergeById(cache.matches, result.matches),
|
|
63
|
+
identities: cache.identities,
|
|
64
|
+
players: mergeById(cache.players, result.players ?? []),
|
|
65
|
+
playerMatchStats: mergeById(cache.playerMatchStats, result.playerMatchStats ?? []),
|
|
66
|
+
lineups: mergeById(cache.lineups, result.lineups ?? []),
|
|
67
|
+
injuries: mergeById(cache.injuries, result.injuries ?? []),
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/cli.d.ts
ADDED