| # La Liga Score Predictor Model Card |
|
|
| ## Scope |
| This document describes the public La Liga Score Predictor model. |
|
|
| Included: |
| - model architecture |
| - feature families |
| - inference behavior |
| - output schema |
| - evaluation approach |
|
|
| Excluded on purpose: |
| - other internal model variants |
| - source-provider specific ingestion details |
| - private infrastructure |
|
|
| ## Model Summary |
|
|
| - Model version: `la_liga_score_predictor` |
| - Public package version: `2026.04.1` |
| - Model type: `catboost_two_stage` |
| - Task: pre-match football score prediction |
| - Competition scope: Spanish La Liga only |
| - Primary outputs: |
| - expected home goals |
| - expected away goals |
| - predicted score |
| - home/draw/away probabilities |
| - confidence level |
| - abstain / score-range signal for fragile matches |
|
|
| ## High-Level Architecture |
| The La Liga Score Predictor model is a two-stage CatBoost setup. |
|
|
| Stage 1: |
| - one CatBoost model predicts expected home goals |
| - one CatBoost model predicts expected away goals |
|
|
| Stage 2: |
| - one CatBoost model predicts match-outcome probabilities: |
| - `home_win` |
| - `draw` |
| - `away_win` |
|
|
| Final score decoding: |
| - score decode mode: `poisson_argmax` |
| - home Poisson scale: `1.10` |
| - away Poisson scale: `1.05` |
|
|
| Probability post-processing: |
| - temperature calibration: `0.93` |
|
|
| Low-confidence handling: |
| - abstain mode enabled |
| - score-range output enabled for fragile matches |
|
|
| ## Competition Scope |
| This public release is intended only for Spanish La Liga fixtures. |
|
|
| It should not be treated as a validated model for other leagues without retraining or separate evaluation. |
|
|
| ## What the Model Learns From |
| The model is trained on structured pre-match football features only. |
|
|
| It does not need interactive conversation context, database access, or any auxiliary external signal outside the supported feature schema for public inference. |
|
|
| ## What This Public Package Does Not Include |
| This release does not include: |
| - a full production historical La Liga dataset |
| - a hosted or live data source |
| - a promise of reproducing a private/internal prediction environment exactly |
|
|
| The public package includes the model and the wrapper logic. |
| For `predict_match(...)`, the caller must still supply compatible historical match data. |
|
|
| This public release should be treated as: |
| - a model-and-inference bundle |
| - not a bundled football data service |
|
|
| ## Feature Families |
| The current model artifact uses `48` numeric features. |
|
|
| ### 1. Recent Scoring and Conceding |
| - recent average goals scored |
| - recent average goals conceded |
| - all-match context |
| - home-only or away-only context where appropriate |
|
|
| ### 2. Recent Results Shape |
| - recent win rates |
| - recent draw rates |
| - recent goal-difference trends |
|
|
| ### 3. Rest and Scheduling |
| - home rest days |
| - away rest days |
|
|
| ### 4. Team Strength |
| - pre-match Elo values |
| - Elo difference |
|
|
| ### 5. Team Identity Signals |
| - home team id |
| - away team id |
|
|
| ### 6. Player Availability and Usage Aggregates |
| Aggregated over recent matches: |
| - minutes |
| - goals |
| - assists |
| - yellow cards |
| - red cards |
| - starter counts |
| - used-player counts |
| - injured-player counts |
| - suspended-player counts |
|
|
| ### 7. Tactical and Staff Context |
| - home tactic id |
| - away tactic id |
| - home coach id |
| - away coach id |
| - tactic stability over recent matches |
| - tactic matchup encoding |
|
|
| ## Exact Feature Columns |
| The model artifact currently expects these exact feature columns: |
|
|
| ```text |
| home_avg_goals_last5_all |
| away_avg_goals_last5_all |
| home_avg_goals_last5_home |
| away_avg_goals_last5_away |
| home_avg_conceded_last5_all |
| away_avg_conceded_last5_all |
| home_avg_conceded_last5_home |
| away_avg_conceded_last5_away |
| home_win_rate_last10_all |
| away_win_rate_last10_all |
| home_win_rate_last10_home |
| away_win_rate_last10_away |
| home_draw_rate_last10 |
| away_draw_rate_last10 |
| home_goal_diff_last5 |
| away_goal_diff_last5 |
| home_rest_days |
| away_rest_days |
| home_elo_pre |
| away_elo_pre |
| elo_diff_pre |
| home_team_id |
| away_team_id |
| home_player_minutes_total_prev5 |
| away_player_minutes_total_prev5 |
| home_player_goals_total_prev5 |
| away_player_goals_total_prev5 |
| home_player_assists_total_prev5 |
| away_player_assists_total_prev5 |
| home_player_yellow_cards_total_prev5 |
| away_player_yellow_cards_total_prev5 |
| home_player_red_cards_total_prev5 |
| away_player_red_cards_total_prev5 |
| home_player_starters_count_prev5 |
| away_player_starters_count_prev5 |
| home_player_used_count_prev5 |
| away_player_used_count_prev5 |
| home_player_injured_count_prev5 |
| away_player_injured_count_prev5 |
| home_player_suspended_count_prev5 |
| away_player_suspended_count_prev5 |
| home_tactic_id |
| away_tactic_id |
| home_coach_id |
| away_coach_id |
| home_tactic_stability_last5 |
| away_tactic_stability_last5 |
| tactic_matchup_code |
| ``` |
|
|
| ## Inference Flow |
|
|
| ### Step 1: Validate Input |
| The predictor checks that every required feature exists and is numeric. |
|
|
| ### Step 2: Sanitize Identifier-Like Fields |
| The inference path stabilizes: |
| - team ids |
| - coach ids |
| - tactic ids |
|
|
| This avoids malformed id-like values distorting predictions. |
|
|
| ### Step 3: Predict Expected Goals |
| The home-goals and away-goals models produce expected goal values. |
|
|
| ### Step 4: Predict Outcome Probabilities |
| The outcome model produces: |
| - home-win probability |
| - draw probability |
| - away-win probability |
|
|
| ### Step 5: Apply Calibration |
| The probability distribution is temperature-calibrated. |
|
|
| ### Step 6: Decode Final Score |
| The model uses Poisson-based score decoding to choose the final scoreline. |
|
|
| ### Step 7: Compute Confidence |
| The response includes: |
| - `confidence_level` |
| - `confidence_score` |
| - `confidence_margin` |
|
|
| ### Step 8: Optional Abstain Signal |
| If the match is fragile enough: |
| - `abstain_recommended = true` |
| - a score range is returned |
|
|
| ## Reproducibility Note |
| This public release ships the same champion model artifact used by the public bundle, but exact outputs are still data-dependent. |
|
|
| In practice: |
| - same model + same feature row -> same prediction |
| - same model + different historical context -> potentially different prediction |
|
|
| That matters because `predict_match(...)` builds the feature row from historical input data before the model runs. |
|
|
| ## Output Schema |
| Typical output fields: |
|
|
| ```json |
| { |
| "model_version": "la_liga_score_predictor", |
| "model_type": "catboost_two_stage", |
| "expected_home_goals": 1.72, |
| "expected_away_goals": 0.94, |
| "predicted_home_goals": 2, |
| "predicted_away_goals": 1, |
| "predicted_score": "2-1", |
| "result_probabilities": { |
| "home_win": 0.54, |
| "draw": 0.25, |
| "away_win": 0.21 |
| }, |
| "confidence_level": "medium", |
| "confidence_score": 0.54, |
| "confidence_margin": 0.19, |
| "abstain_recommended": false |
| } |
| ``` |
|
|
| If abstain mode is triggered, output can also include: |
|
|
| ```json |
| { |
| "predicted_score_range": { |
| "home_min": 1, |
| "home_max": 3, |
| "away_min": 0, |
| "away_max": 2 |
| } |
| } |
| ``` |
|
|
| ## Field Glossary |
| - `predicted_score` |
| - the final exact score chosen after expected-goal estimation, probability calibration, and score decoding |
| - `result_probabilities` |
| - calibrated `home_win`, `draw`, and `away_win` probabilities |
| - `raw_result_probabilities` |
| - the outcome probabilities before calibration |
| - `expected_home_goals` |
| - expected goals estimate for the home team |
| - `expected_away_goals` |
| - expected goals estimate for the away team |
| - `confidence_level` |
| - coarse confidence label: `high`, `medium`, or `low` |
| - `confidence_score` |
| - the highest calibrated outcome probability |
| - `confidence_margin` |
| - the gap between the top and second-highest calibrated outcome probabilities |
| - `abstain_recommended` |
| - flag indicating that the match is fragile enough to soften exact-score trust |
| - `predicted_score_range` |
| - optional score band returned when abstain mode is triggered |
| - `top_outcome` |
| - the strongest calibrated outcome class in the decoder diagnostics |
| - `top_outcome_probability` |
| - the probability of that strongest outcome class |
| - `second_outcome_probability` |
| - the probability of the runner-up outcome class |
| - `draw_probability` |
| - the calibrated draw probability |
| - `xg_delta` |
| - `expected_home_goals - expected_away_goals` |
| - useful for understanding which side carries the stronger expected scoring edge |
| - `close_call_draw_override` |
| - indicates a draw-favoring tie-break in a tightly balanced fixture |
| - `outcome_enforced` |
| - indicates that the outcome model was confident enough to force score direction |
| - `specialist_rule_triggered` |
| - indicates that an internal score adjustment rule ran |
| - `specialist_rule_name` |
| - the name of the triggered rule, if any |
|
|
| ## Confidence Logic |
| The model exposes confidence for usability, not certainty. |
|
|
| Interpretation: |
| - `high`: clearer distribution separation |
| - `medium`: usable signal but not dominant |
| - `low`: fragile match, higher variance, score-range matters more |
|
|
| This is why public docs should explain: |
| - prediction is guidance |
| - not a guarantee |
|
|
| ## Training and Selection Approach |
| The model was selected through offline evaluation and score-decoding guardrails. |
|
|
| Key ideas: |
| - no leakage from post-match information |
| - recent-window checks matter, not only full-history averages |
| - decode settings were selected to balance: |
| - exact score rate |
| - outcome accuracy |
| - total-goal error |
|
|
| From the current training report: |
| - selection method: `decode_sweep_no_retrain_recent_guardrails` |
|
|
| Current report snapshot: |
| - full-history exact: `0.2056` |
| - full-history outcome: `0.6177` |
| - recent-window exact: `0.1535` |
| - recent-window outcome: `0.5307` |
|
|
| These numbers are artifact-era selection metrics, not a promise for future live windows. |
|
|
| ## Public Inference Requirements |
| For public use, inference needs only: |
| - the bundled model artifacts |
| - the three `.cbm` files referenced by them |
| - Python dependencies for CatBoost inference |
| - either: |
| - a compatible historical match CSV for `predict_match(...)` |
| - or one complete numeric feature row matching the required schema for `predict_features(...)` |
|
|
| It does not require: |
| - a live database |
| - a web server |
| - a scheduled pipeline |
| - any source-specific connector |
|
|
| ## Public Inference Interfaces |
| The public package exposes four main inference methods. |
|
|
| ### `predict_match(home_team, away_team, match_date)` |
| - builds features from a compatible historical match CSV |
| - best for normal application use |
| - returns the full response, including advanced fields and diagnostics |
|
|
| ### `predict_match_simple(home_team, away_team, match_date)` |
| - builds features from a compatible historical match CSV |
| - best for lighter product integrations |
| - returns the smaller public response shape |
| |
| ### `predict_features(features)` |
| - expects the full numeric feature row |
| - best for advanced users managing engineered features directly |
| - returns the full response, including advanced fields and diagnostics |
|
|
| ### `predict_features_simple(features)` |
| - expects the full numeric feature row |
| - best for advanced users who want a smaller public response shape |
|
|
| ## Public Usage Example |
| The simplest public interface is: |
|
|
| ```python |
| from la_liga_score_predictor import LaLigaScorePredictor |
| |
| predictor = LaLigaScorePredictor.from_defaults( |
| dataset_csv_path="sample_history.csv" |
| ) |
| |
| prediction = predictor.predict_match( |
| home_team="Athletic", |
| away_team="Osasuna", |
| match_date="2026-04-21", |
| ) |
| |
| simple_prediction = predictor.predict_match_simple( |
| home_team="Athletic", |
| away_team="Osasuna", |
| match_date="2026-04-21", |
| ) |
| ``` |
|
|
| Where: |
| - `predict_match()` is the easiest interface when you have a compatible history CSV |
| - `predict_match_simple()` is the lighter product-facing variant |
| - `predict_features()` is available for direct numeric feature rows |
| - `predict_features_simple()` is the lighter direct-feature variant |
|
|
| ## Limitations |
| - The model is only as good as the input feature row. |
| - It is a pre-match model, not a live in-match model. |
| - It is league and feature-schema sensitive. |
| - It can miss chaotic or low-signal matches. |
| - Confidence is a helpful indicator, not certainty. |
|
|
| ## What We Intentionally Exclude From the Public Technical Story |
| - source-provider implementation details |
| - private ingestion and refresh mechanics |
| - other internal model variants and experiment history |
|
|
| That keeps the public story clear: |
| - one champion model |
| - one inference path |
| - one reproducible public artifact |
|
|
| ## Suggested Companion Files |
| This technical note works best alongside: |
| - `RELEASE_GUIDE.md` |
| - a public `README.md` |
| - a small `sample_history.csv` |
| - a simple `predict_one.py` |
|
|
| ## Release Cadence Note |
| The public model artifact is planned to update twice per month. |
|
|
| For each public release, technical notes should be updated only if one of these changes: |
| - feature schema |
| - decode settings |
| - calibration behavior |
| - output contract |
| - artifact version |
|
|