# 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