First, a disclaimer:
- This is a hobby project that started somewhere in 2018 and has escalated quite a bit over the years
- The code is exactly like that of a hobby project: chaotic, illogical, incorrect, sparsely documented, and appearing to be written by an insane lunatic (me)
- From 2026 onwards, most changes are AI generated and possibly even more incoherent and overly complicated than I could've ever done
- PRs are welcome and will be looked at. They will also be frowned upon, because who in their sane mind would go through the effort trying to understand this mess?
- Complaints about the state of the codebase will be met with maniacal laughter.
For a nice explanation of what this all about anyway, see korfbalelo.nl.
The following is AI generated (because I'm lazy) and could be partly or totally wrong:
Korfbal Elo is a Kotlin + Vue project for rating Dutch korfbal teams, predicting match outcomes, and simulating league finish probabilities (champion/promotion/relegation).
It combines:
- A Kotlin rating/simulation engine (
src/main/kotlin) - Data ingestion (historical files + Sportlink API scraping with cache)
- A Vue 3 frontend (
web/) that serves and visualizes generated artifacts
- Kotlin JVM (Gradle, Java toolchain 25)
- Vue 3 + TypeScript + Vite + Pinia
- Data files in CSV/TXT/JSON (many are generated and committed)
This repo is configured for Java 25 in build.gradle.kts and CI.
sdk list java | grep -E "25.*(tem|open|zulu)"
sdk install java <candidate>
sdk use java <candidate>
java -versionRoot and frontend both pin Node major version 20 via .nvmrc.
nvm use
node -vnpm >= 10 is recommended. npm 8 works but prints engine warnings for npm-run-all2.
./gradlew clean test
./gradlew runrunexecutesnl.korfbalelo.elo.ApplicationNew- It recalculates ratings and writes web-consumed artifacts like:
web/public/ranking.csvweb/public/graph.csvweb/public/meta.jsonweb/src/origins.tsaggr/*.csv
./gradlew predict --args="2026-01-01"- Runs
nl.korfbalelo.elo.SeasonPredicter - Generates season probability CSVs/JSON under
web/public/csv/...andweb/public/*.json - Note: CI currently passes a second argument (
zaal) butSeasonPredicter.mainonly uses the first argument (date)
cd web
npm ci
npm run devUseful checks:
cd web
npm run build
npm run lintBaseline gates (fast PR feedback):
./gradlew testcd web && npm run build
For output-producing backend changes also run:
./gradlew run./gradlew predict --args="2026-01-01"
See docs/quality-gates.md for details and invariants.
CI contract note: PR pipeline enforces earliest-date guard + predict determinism by default; add PR label changes generated match output to intentionally skip those contract checks.
- Historical data:
matches/*.txt|csv - Club lifecycle commands (spawn/merge/alias/end):
club_events.txt - Current season scrape:
src/main/kotlin/nl/korfbalelo/mijnkorfbal/Scraper.kt- Uses cache files under
cache/ - Writes
matches/current.txtandweb/public/specialszaal2526.json
- Uses cache files under
- Entrypoint:
src/main/kotlin/nl/korfbalelo/elo/ApplicationNew.kt - Team model + Glicko-style update:
src/main/kotlin/nl/korfbalelo/elo/Team.kt - Match application + alias mapping:
src/main/kotlin/nl/korfbalelo/elo/RankingNew.kt - Event parsing/execution:
src/main/kotlin/nl/korfbalelo/elo/RankingEvent.kt
- Kotlin simulator/exporter:
src/main/kotlin/nl/korfbalelo/elo/SeasonPredicter.kt - Poule simulator core:
src/main/kotlin/nl/korfbalelo/elo/PoulePredicter.kt - Outputs consumed by frontend:
web/public/csv/<season>/<poule>.csvweb/public/<season>.jsonweb/public/meta.json
- Vue app shell/routes:
web/src/App.vue,web/src/router/index.ts - Data stores:
web/src/stores/* - Season + what-if simulator:
web/src/simulator/SeasonSimulator.tsweb/src/simulator/PoulePredicter.tsweb/src/simulator/SimulationWorker.ts
Important: promotion/relegation logic exists in both Kotlin (SeasonPredicter.kt) and frontend TS (SeasonSimulator.ts) and should stay aligned.
src/main/kotlin/nl/korfbalelo/elo: rating engine, event model, simulation, exportssrc/main/kotlin/nl/korfbalelo/mijnkorfbal: Sportlink API models + scrapersrc/test/kotlin: minimal tests (some disabled)matches/: historical + current match data filesweb/: Vue frontendweb/public/: generated and static data served by frontend.github/workflows/: CI and scheduled daily update workflow
.github/workflows/pr.yml- Java 25
./gradlew build- generated-output contracts:
./.ci/check_generated_csv_guard.py <base-ref>./.ci/check_predict_determinism.sh- optional PR label override:
changes generated match output
web: npm ci && npm run build
.github/workflows/daily-check.yml- Runs
./gradlew run - If
matches/current.txtchanged, trims outdoor season CSV history from the earliest changed date and runs./gradlew predict --args="$EARLIEST_DATE veld" - Commits and pushes updated data artifacts
- Forces fresh Sportlink API reads (
-Delo.scraper.forceNetwork=true) and saves cache snapshot keysportlink-cache-v1-<os>-daily-* - Other workflows restore the latest daily snapshot (they do not overwrite it)
- Runs
Source code is licensed under the MIT License; see LICENSE.
Match data, fixture data, ranking data, generated CSV/JSON artifacts, and other data/content files are published for transparency and reproducibility. They may include data derived from third-party or public sources. Reuse of those data/content files is not covered by the MIT source-code license.
SeasonPredicter.doOutdooris hardcoded (false) unless changed in code.- Test coverage is limited;
GlickoTestis disabled. - Large generated data files can change often; avoid unrelated formatting churn.
web/distandweb/node_modulesare build outputs and should not be committed.
These passed during doc creation:
./gradlew clean testcd web && npm ci && npm run build