docker compose up --builddocker compose exec api python -m app.etl.seed --limit 1000docker compose exec api python -m app.etl.seed --limit 1000 --forceTo load the full dataset:
docker compose exec api python -m app.etl.seed --all --forceFeature version can be set explicitly:
docker compose exec api python -m app.etl.seed --all --force --feature-version v1Note: If you pull new feature columns, you must reset the database (drop the volume) so the schema is recreated:
docker compose down -v
docker compose up -dTrain scripts do a temporal split (train/val/test), compute metrics (including baselines and per-class diagnostics), run leakage checks, and write a run record to DB with artifact links and checksums.
Summary:
- Features are built by ETL (
app.etl.seed) and stored in DB withfeature_version(e.g.v1). - Training uses time-based split and logs
accuracy,macro-F1,logloss,brier, and confusion matrix. - Calibration is optional and uses the validation split.
- Leakage checks are enforced by default; use
--allow-leakageonly to override for debugging. - Artifacts saved to
python-server/models: model, meta, schema.
docker compose exec api python -m app.ml.train_baseline --version v1With tuning + calibration + league/season filters:
docker compose exec api python -m app.ml.train_baseline --version v1 --league en.1 --season 2010-11 --tune --n-iter 12 --calibrateWith class weighting + CV:
docker compose exec api python -m app.ml.train_baseline --version v1 --class-weight balanced --cv-splits 5docker compose exec api python -m app.ml.train_main --version v1 --model xgboostWith tuning + walk-forward + calibration:
docker compose exec api python -m app.ml.train_main --version v1 --model xgboost --tune --walk-forward --n-iter 12 --calibrateWith class weighting + CV + verbose eval:
docker compose exec api python -m app.ml.train_main --version v1 --model xgboost --class-weight balanced --cv-splits 5 --verbose-eval 50Notes:
--tuneruns a random search over a broader grid and picks the best logloss.--walk-forwarduses time-series CV during tuning (falls back to holdout if data is small).--calibrate/--no-calibratecontrols validation-based calibration.--class-weight balancedapplies inverse-frequency sample weighting.--cv-splits Nreports rolling time-series CV summary metrics.--early-stopping-rounds Nenables early stopping when supported by your XGBoost build.--n-estimators Noverrides the number of boosting rounds (XGBoost only).- Each training run writes a label audit report under
reports/label_audit_<run_id>/. - Each training run writes a divergence report under
reports/training_divergence_<run_id>/.
No calibration (avoids sklearn cv="prefit" issue) with verbose evaluation:
docker compose exec -e PYTHONUNBUFFERED=1 api python -u -m app.ml.train_per_league --trainer main --model xgboost --continue-on-error --verbose-eval 1 --no-calibrateExplicit 5 boosting rounds:
docker compose exec -e PYTHONUNBUFFERED=1 api python -u -m app.ml.train_per_league --trainer main --model xgboost --continue-on-error --verbose-eval 1 --no-calibrate --n-estimators 5Override boosting rounds and enable early stopping (if supported by XGBoost):
docker compose exec -e PYTHONUNBUFFERED=1 api python -u -m app.ml.train_per_league --trainer main --model xgboost --continue-on-error --verbose-eval 1 --no-calibrate --n-estimators 50 --early-stopping-rounds 10docker compose exec api python -m app.ml.predict_to_db --version v1 --model main_xgboostGenerate dataset integrity artifacts (canonical key alignment, step counts, missingness chart, golden sample):
docker compose exec api python -m app.reporting.dataset_integrity --version v1Outputs are written under reports/<timestamp> by default:
dataset_integrity.md(row counts + key/label/null summaries per step)missingness_train_val.png(per-feature missing values histogram)golden_sample.csv(50 sampled rows with key, label, and features)
These audits are also visible in the UI under “Dataset integrity audits”.
- Start services:
docker compose up --build - Seed data (if not already seeded):
docker compose exec api python -m app.etl.seed --limit 1000 --force
- Generate a dataset integrity audit:
docker compose exec api python -m app.reporting.dataset_integrity --version v1
- Open UI at
http://localhost:5173and scroll to Dataset integrity audits. - Train a model to generate label audit + divergence reports:
docker compose exec api python -m app.ml.train_main --version v1 --model xgboost
- Open UI at
http://localhost:5173and scroll to Label audits. - Open UI at
http://localhost:5173and scroll to Training divergence monitor. - Inspect label audit artifacts on disk:
reports/label_audit_<run_id>/label_audit.mdreports/label_audit_<run_id>/class_counts.csvreports/label_audit_<run_id>/confusion_matrix.csvreports/label_audit_<run_id>/metrics.jsonreports/label_audit_<run_id>/predicted_vs_true.pngreports/label_audit_<run_id>/confusion_matrix.png
- Inspect divergence artifacts on disk:
reports/training_divergence_<run_id>/training_divergence.mdreports/training_divergence_<run_id>/feature_scaling.csvreports/training_divergence_<run_id>/step_metrics.csvreports/training_divergence_<run_id>/divergence_summary.csvreports/training_divergence_<run_id>/loss_lr.pngreports/training_divergence_<run_id>/grad_norm.png(if available)
docker compose exec api python -m app.reporting.label_audit --run-id <run_id>docker compose exec api python -m app.reporting.training_divergence --run-id <run_id>Label audits write an index file for the UI list:
reports/label_audits_index.json
Label audits are filterable by league/season in the UI (uses /label-audits?league=<code>&season=<label>).
Training divergence reports write an index file for the UI list:
reports/training_divergence_index.json
Training divergence reports are filterable by league/season in the UI (uses /training-divergence?league=<code>&season=<label>).
Training already logs validation/test metrics into model_runs. You can also generate a report from stored predictions:
docker compose exec api python -m app.reporting.prediction_reportArtefakty:
reports/model_eval/<timestamp>/metrics.jsonreports/model_eval/<timestamp>/metrics.csvreports/model_eval/<timestamp>/confusion_matrix.pngreports/model_eval/<timestamp>/calibration.png(jeśli wdrożone)reports/model_eval/<timestamp>/subsets.csv
Uruchomienie (CLI/CI):
docker compose exec api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifierZ filtrowaniem ligi/sezonu:
docker compose exec api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifier --league en.1 --season 2010-11Z konkretną wersją modelu:
docker compose exec api python -m app.reporting.model_eval --version v1 --model main_xgboost --model-version 20260207_011921_nogit --baseline baseline_classifier --baseline-version 20260207_011558_nogit- Domyślnie
--lookup best(najlepszy model zmodels/best_models.json, jeśli istnieje). --lookup latestwybiera najnowszy artefakt wpython-server/models.--model-version/--baseline-versionwymuszają konkretny artefakt (dokładna reprodukcja).
Przykłady:
Najnowsze artefakty (latest):
docker compose exec -T api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifier --lookup latestNajlepsze z registry (best):
docker compose exec -T api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifier --lookup bestKonkretny model główny + konkretna wersja baseline:
docker compose exec -T api python -m app.reporting.model_eval --version v1 --model main_xgboost --model-version 20260207_011921_nogit --baseline baseline_classifier --baseline-version 20260207_011558_nogitGet-ChildItem python-server/models -Filter "main_xgboost_*.json" | Select-Object Name
Get-ChildItem python-server/models -Filter "baseline_classifier_*.json" | Select-Object NameWiele treningów per liga/sezon:
--lookup bestjest zależny od--leaguei--season(najlepszy model dla tego segmentu).--lookup latestignoruje ligę/sezon (bierze najnowszy artefakt globalnie).- Dla pełnej kontroli użyj
--model-versioni--baseline-version.
Przykład (best dla konkretnej ligi/sezonu):
docker compose exec -T api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifier --lookup best --league en.1 --season 2010-11Trening na wszystkich sezonach wybranej ligi:
docker compose exec -T api python -m app.ml.train_main --version v1 --model xgboost --league en.1
docker compose exec -T api python -m app.ml.train_baseline --version v1 --league en.1Ewaluacja na tej samej lidze (wszystkie sezony):
docker compose exec -T api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifier --league en.1Różnica między baseline i main:
app.ml.train_baseline= model bazowy (logistyczna regresja).app.ml.train_main= model główny (XGBoost/CatBoost).
Przykład (ta sama liga, wszystkie sezony):
docker compose exec -T api python -m app.ml.train_baseline --version v1 --league en.1
docker compose exec -T api python -m app.ml.train_main --version v1 --model xgboost --league en.1Jak rozpoznać ligę/sezon modelu:
- Nazwa pliku mówi o typie modelu (np.
main_xgboost_*,baseline_classifier_*), ale nie o lidze. - Liga/sezon są zapisane w metadanych modelu (
*.json) i wmodel_runs/best_models.json.
Przykład odczytu metadanych:
Get-Content python-server/models/main_xgboost_20260207_011921_nogit.jsondocker compose exec api python -m unittest discover -s tests -p "test_*.py"Uruchomienie (komponenty):
cd react-server
npm run test:unitUruchomienie (Playwright):
cd react-server
npx playwright install
npm run test:e2eWymagane: działający API na http://localhost:8000 oraz UI na http://localhost:5173.
Uruchomienie:
cd react-server
npx playwright install
npm run test:e2eSprawdzenie healthchecków:
docker compose ps
curl http://localhost:8000/healthDeterministyczność predykcji (ten sam match_id + model_version):
curl -X POST "http://localhost:8000/predict?feature_version=v1&model_name=main_xgboost&model_version=20260207_011921_nogit" ^
-H "Content-Type: application/json" ^
-d "{\"match_id\": 123}"Powtórne wywołanie (powinien zwrócić ten sam rekord z DB):
curl -X POST "http://localhost:8000/predict?feature_version=v1&model_name=main_xgboost&model_version=20260207_011921_nogit" ^
-H "Content-Type: application/json" ^
-d "{\"match_id\": 123}"Podgląd historii predykcji:
curl "http://localhost:8000/predictions?match_id=123&model_name=main_xgboost"Narzędzie:
- k6 (standard)
Pliki:
scripts/perf/k6.jsscripts/perf/db_indexes.sqlscripts/perf/db_explain.sql
Przygotowanie:
- Start usług:
docker compose up -d --build
- Seed danych:
docker compose exec -T api python -m app.etl.seed --limit 1000 --force
- Trening modelu:
docker compose exec -T api python -m app.ml.train_main --version v1 --model xgboost
- Opcjonalnie wyczyść predykcje, aby wymusić zapis w DB:
docker compose exec -T db psql -U app -d football -c "TRUNCATE TABLE predictions;"
Uruchomienie k6 (lokalnie):
k6 run scripts/perf/k6.jsUruchomienie k6 (Docker):
docker run --rm -i -v ${PWD}:/work -w /work grafana/k6 run scripts/perf/k6.js -e BASE_URL=http://host.docker.internal:8000Uruchomienie k6 (Docker Compose):
docker compose --profile perf run --rm k6Zmienne środowiskowe (opcjonalne):
BASE_URL(domyślniehttp://localhost:8000)LEAGUE(np.en.1)SEASON(np.2010-11)FEATURE_VERSION(domyślniev1)MODEL_NAME(domyślniemain_xgboost)MATCH_LIMIT(domyślnie200)PREDICT_TIMEOUT(domyślnie20s)
Sterowanie obciążeniem (opcjonalne):
LIST_MATCHES_VUS(domyślnie3)LIST_PREDICTIONS_VUS(domyślnie2)LIST_DURATION(domyślnie30s)PREDICT_STEADY_RATE(domyślnie2/s)PREDICT_STEADY_DURATION(domyślnie60s)PREDICT_STEADY_VUS(domyślnie10)PREDICT_STEADY_MAX_VUS(domyślnie30)PREDICT_BURST(domyślniefalse, ustawtrueaby włączyć burst)PREDICT_BURST_RATE(domyślnie8/s)PREDICT_BURST_DURATION(domyślnie15s)PREDICT_BURST_VUS(domyślnie15)PREDICT_BURST_MAX_VUS(domyślnie40)
Progi (opcjonalne):
HTTP_REQ_FAILED_THRESHOLD(domyślnierate<0.05)API_FAILURE_THRESHOLD(domyślnierate<0.05)SERVER_ERROR_THRESHOLD(domyślnierate<0.01)MATCHES_P95_THRESHOLD(domyślniep(95)<1500)PREDICTIONS_P95_THRESHOLD(domyślniep(95)<1500)PREDICT_P95_THRESHOLD(domyślniep(95)<6000)
Eksport wyników:
k6 run scripts/perf/k6.js --summary-export reports/k6_summary.jsonIndeksy DB (opcjonalnie, dla istniejącej bazy):
Get-Content scripts/perf/db_indexes.sql | docker compose exec -T db psql -U app -d footballEXPLAIN (opcjonalnie):
Get-Content scripts/perf/db_explain.sql | docker compose exec -T db psql -U app -d footballIndeksy DB zastosowane:
ix_seasons_league_label,ix_matches_date,ix_predictions_model_created.
EXPLAIN before/after (skrócony):
- matches list: Before = Seq Scan
seasons+ Index Scanmatches+ Sort; ~10.251 ms. After = ten sam plan, ale ~0.176 ms (prawdopodobnie cache). - predictions list: Before = Index Scan
ix_predictions_model_name+ Sort; ~0.029 ms. After = Index Scanix_predictions_model_createdbez sortu; ~0.013 ms.
Uwaga:
- p95 ~30s oznacza timeouty pod obciążeniem — zbyt agresywne scenariusze saturują API/DB pool.
- Start services:
docker compose up -d --build
- Seed data:
docker compose exec -T api python -m app.etl.seed --limit 1000 --force
- Train models:
docker compose exec -T api python -m app.ml.train_main --version v1 --model xgboost docker compose exec -T api python -m app.ml.train_baseline --version v1
- Run 6.1 model evaluation harness:
docker compose exec -T api python -m app.reporting.model_eval --version v1 --model main_xgboost --baseline baseline_classifier
- Run integration tests:
docker compose exec -T api python -m unittest discover -s tests -p "test_*.py"
Single command (pipeline):
.\pipeline.ps1GET /healthGET /leaguesGET /seasons?league=en.1GET /matches?league=en.1&season=2010-11&limit=200GET /model-runs?league=en.1&season=2010-11&feature_version=v1GET /model-runs/top?league=en.1&season=2010-11&feature_version=v1&metric=test_log_loss&limit=5GET /dataset-integrityGET /label-audits?league=en.1&season=2010-11GET /training-divergence?league=en.1&season=2010-11POST /predict?feature_version=v1&model_name=main_xgboost&model_version=<optional>GET /predictions?match_id=123
Example flow:
- Seed data (
app.etl.seed). - Train a model (
app.ml.train_mainorapp.ml.train_baseline). - Fetch runs or top models via API.
Useful metrics for sorting:
test_log_loss(lower is better)test_brier_score(lower is better)test_f1_macro(higher is better)test_accuracy(higher is better)