Spaces:
Sleeping
Sleeping
| # Runtime model | |
| The application defaults to an automatic runtime that lazily downloads | |
| `bartowski/SmolLM2-360M-Instruct-GGUF` and runs its Q8_0 quantization | |
| through `llama-cpp-python` (see `src/underdog_lab/config.py`). A 360M | |
| SmolLM2 QLoRA adapter was trained and benchmarked | |
| (`results/ship_decision.json`) but did not clear the ship gate, so it is | |
| not the default. If model loading fails, the challenge remains usable | |
| through a deterministic fallback and records the degraded behavior. | |
| For local llama.cpp inference: | |
| ```bash | |
| export UNDERDOG_EXTRACTOR=llama_cpp | |
| export UNDERDOG_LLAMA_CLI=/path/to/llama-cli | |
| export UNDERDOG_MODEL_PATH=/path/to/model-q4_k_m.gguf | |
| python app.py | |
| ``` | |
| The selected GGUF should be a compatible model at or below 4B parameters. | |
| Test the exact conversion with the pinned llama.cpp revision before publishing | |
| the Space. | |
| To force the in-process runtime with a different model: | |
| ```bash | |
| export UNDERDOG_EXTRACTOR=python_llama_cpp | |
| export UNDERDOG_MODEL_REPO=bartowski/SmolLM2-360M-Instruct-GGUF | |
| export UNDERDOG_MODEL_FILENAME=SmolLM2-360M-Instruct-Q8_0.gguf | |
| python app.py | |
| ``` | |
| # World Cup 2026 forecasting model | |
| `underdog_lab.world_cup.forecasting.MODEL` is a `DixonColesEloModel` | |
| (Elo-to-goals Poisson rates with a Dixon-Coles low-score correlation term) | |
| fit by `scripts/fit_elo_dixon_coles.py` via time-decayed maximum likelihood | |
| on `data/historical/matches.csv` (11,094 real international matches, | |
| 2015-01-03 to 2026-06-12, sourced from eloratings.net). | |
| - `elo_fit_report.json` — the final fit (trained on all data through the | |
| 2026 World Cup `information_cutoff`, 180-day time-decay half-life): | |
| `intercept=0.1534`, `elo_scale=0.00207`, `home_advantage_elo=65.6`, | |
| `rho=-0.0998`. The negative `rho` reflects the well-documented excess of | |
| low-scoring draws (0-0, 1-1) versus independent Poisson. | |
| `home_advantage_elo` has no effect on World Cup group fixtures, which are | |
| always `neutral_venue=True`. | |
| - `backtest_report.json` — a walk-forward backtest (`scripts/ | |
| backtest_walk_forward.py`): for each test year 2018-2026, the model is | |
| refit on only the matches strictly before that year (no lookahead) and | |
| scored on that year's matches. The fitted model beats both the uniform | |
| baseline and the previous hand-set model (independent Poisson, | |
| `elo_scale=0.00165`, no home advantage, `rho=0`) on mean log loss, Brier | |
| score, and RPS across all folds. It also beats the previous model on the | |
| neutral-venue subset, which better matches World Cup inference, so the | |
| ship gate (`ship_gate.ship`) passed and `MODEL` was updated to these | |
| fitted parameters. A basic calibration table using the actual mean | |
| predicted probability in each decile is included. The model is | |
| under-confident through much of the middle range, so calibration remains | |
| an explicit follow-up rather than a completed claim. | |
| - `upgrade_evaluation.json` (`scripts/upgrade_evaluation.py`) — two further | |
| changes evaluated with a selection (2018-2025) / confirmation (2026) | |
| split, so neither was chosen by peeking at the held-out year: | |
| - **Recency half-life (adopted).** The previous fit used a fixed 3-year | |
| (1095-day) half-life, never tuned. A sweep over {180, 365, 547, 730, | |
| 1095, 1460, 2190} days found 180 days has the lowest mean log loss on | |
| the 2018-2025 selection folds (0.79417 vs 0.79454 for 1095 days), and | |
| it also beats the 1095-day fit on the held-out 2026 fold both overall | |
| (0.80110 vs 0.80406) and on the neutral-venue subset (0.80382 vs | |
| 0.80464). All three checks passed, so `MODEL` and | |
| `fit_elo_dixon_coles.py`'s default half-life were updated from 1095 to | |
| 180 days. | |
| - **Ensemble with a second, independently-computed Elo rating (not | |
| adopted).** `forecasting/self_elo.py` computes a from-scratch Elo | |
| rating (fixed K-factor, goal-difference multiplier, neutral start, | |
| derived only from `matches.csv` results — independent of | |
| eloratings.net). A Dixon-Coles model fit on this rating alone scores | |
| markedly worse than the eloratings-Elo model on the 2018-2025 selection | |
| folds, and every blend weight tested (a logarithmic opinion pool, | |
| `forecasting/ensemble.py`, weights 0.3-0.9 toward the eloratings model) | |
| scored worse than the eloratings-only model. The ensemble failed all | |
| three gate checks (selection, confirmation, confirmation-neutral) and | |
| `MODEL` remains a single eloratings-Elo fit. This is an honest negative | |
| result, not a placeholder: a from-scratch Elo computed over 11 years of | |
| data starting every team at a neutral 1500 is a weaker signal than | |
| eloratings.net's long-calibrated ratings, and blending in a weaker | |
| signal cannot improve a stronger one under a log-odds pool. | |
| Tournament simulation now uses the official matches 73-104 path and the 495 | |
| third-place mappings from Annex C. It remains a single fitted statistical | |
| model rather than an ensemble (the ensemble evaluation above did not clear | |
| its gate); refit and re-run the walk-forward gate when new results are | |
| incorporated. | |
| - `recalibration_evaluation.json` (`scripts/recalibration_evaluation.py`, | |
| adopted) — a post-hoc temperature-scaling correction | |
| (`forecasting/calibration.py`) evaluated with the same selection | |
| (2018-2025) / confirmation (2026) split: out-of-fold forecasts from the | |
| walk-forward folds (180-day half-life, refit per fold, no lookahead) are | |
| pooled, a single temperature `T` is fit on the 2018-2025 pool by | |
| minimizing mean log loss, and `T` is then checked against the `T=1` (no-op) | |
| baseline on the held-out 2026 fold. The fitted `T=0.8857` (mild | |
| sharpening, since `T<1` pushes probabilities away from uniform) beats | |
| `T=1` on mean log loss on the 7,914-match 2018-2025 selection pool (0.79179 vs | |
| 0.79417), on the 2026 confirmation fold overall (0.79312 vs 0.80110), and | |
| on the 143-match confirmation fold neutral-venue subset (0.79164 vs | |
| 0.80382). The overall confirmation fold contains 351 matches. Paired | |
| bootstrap intervals for fitted-minus-baseline log loss remain below zero | |
| on all three reported slices. The neutral subset overlaps the overall 2026 | |
| fold, so these are robustness slices rather than three independent | |
| confirmation samples. The gate passed, so | |
| `world_cup/forecasting.py::match_forecast` now | |
| applies `apply_temperature(forecast, CALIBRATION_TEMPERATURE)` to | |
| `MODEL`'s output before returning it. `lambda_home`, `lambda_away`, and | |
| `most_likely_score` are unaffected — temperature scaling only rescales | |
| `p_home`/`p_draw`/`p_away`. | |
| - `vector_calibration_evaluation.json` | |
| (`scripts/vector_calibration_evaluation.py`, not adopted) evaluates a | |
| regularized five-parameter multiclass vector scaling layer on top of the | |
| shipped global temperature. Regularization is selected by rolling-origin | |
| validation across 2021-2025, rather than by the viewed 2026 slice. The | |
| candidate improves log loss, Brier, and RPS on the selection pool and the | |
| viewed 2026 robustness slices, but neutral-match ECE worsens from `0.1025` | |
| to `0.1612`. It therefore fails the research gate and is not wired into | |
| `match_forecast`. More importantly, 2026 has already been viewed during | |
| prior model decisions, so even a clean diagnostic pass would still require | |
| a future pre-registered confirmation period before production adoption. | |
| ## Market-assisted experiment | |
| `forecasting/market.py` implements proportional, power, and Shin margin | |
| removal plus a one-parameter logarithmic pool between the independent model | |
| and timestamped market probabilities. `scripts/market_blend_evaluation.py` | |
| fits that single weight on 2018-2025 and gates it on 2026 overall and neutral | |
| matches, with minimum sample sizes and a paired bootstrap interval. | |
| All three reported paired intervals must remain below zero before adoption. | |
| The market-assisted path is not active because no licensed, timestamped odds | |
| dataset is checked in. It must remain separately labeled from the independent | |
| forecast even if it later passes the gate; incorporating market consensus is | |
| not evidence that the independent model beats the market. See | |
| `data/market/README.md` for the required input contract. | |
| ## Tournament-specific experiments | |
| - `host_adjustment_evaluation.json` | |
| (`scripts/host_adjustment_evaluation.py`, not adopted) evaluates no host | |
| boost, fixed `+25`/`+50` Elo boosts, and a fitted one-parameter boost on | |
| explicitly identified host-team matches. Models are refit before each | |
| tournament edition and host effects are isolated from generic home | |
| advantage. Mean log loss improves for positive boosts, but the completed | |
| confirmation-edition cluster-bootstrap interval crosses zero. The previous | |
| ungated `+50` World Cup host heuristic was therefore removed from | |
| `TournamentTeam.rating`. | |
| - `opener_draw_evaluation.json` (`scripts/opener_draw_evaluation.py`) is a | |
| research-only draw-specific experiment. Opener labels are inferred because | |
| the historical source lacks official stage and matchday fields. The | |
| experiment excludes 2026, validates by completed tournament edition, and | |
| can never activate production code automatically. | |