Feature Store Catalogue
This document explains where feature definitions live, how dbt models feed the Python models, which features are leakage-safe, and how to add a new feature safely.
Where Feature Definitions Live
| Source | Location | Layer |
|---|---|---|
| dbt ML models | analytics/dbt_nrl/models/ml/schema.yml |
ML Features |
| dbt Silver models | analytics/dbt_nrl/models/silver/schema.yml |
Silver (Staged) |
| dbt Gold models | analytics/dbt_nrl/models/gold/schema.yml |
Gold (Modelled) |
| Python backtest | nrl-bet-advisor/data/backtest.py |
Python (Runtime) |
| Python prediction | nrl-bet-advisor/data/prediction.py |
Python (Runtime) |
| Python lineup | nrl-bet-advisor/data/lineup.py |
Python (Runtime) |
The interactive Feature Store page at /features renders this metadata live from the YAML and Python source files.
How dbt Models Feed Python Models
Raw bucket objects (DO Spaces)
│
▼
Silver layer (dbt) ← staged Parquet via DuckDB
stg_nrl_draw
stg_nrl_ladder
stg_nrl_team_lists
│
▼
ML feature layer (dbt) ← point-in-time safe features
ml_nrl_team_form_features
ml_nrl_ladder_features
ml_nrl_match_training_examples
│
▼
Gold layer (dbt) ← model-ready analytics tables
gold_nrl_draw_latest
gold_nrl_stats_latest
│
▼
Python prediction engine ← runtime inference
data/prediction.py ← predict_round()
data/backtest.py ← walk-forward backtest
data/lineup.py ← team list features
Training uses ml_nrl_match_training_examples from DuckDB. Prediction uses runtime lookups via predict_round() which queries DuckDB and the Odds API.
Feature Groups
Team Form Features
Rolling statistics computed from completed matches before the fixture kickoff (point-in-time safe).
Key features:
- rolling_wins_l5 — wins in last 5 games
- rolling_total_points_l5 — average total points scored last 5 games
- home_ground_advantage — home-field win rate
- rest_days — days since last match
Source: ml_nrl_team_form_features (dbt ML layer)
Ladder Features
Derived from the most recent ladder snapshot with round < fixture round.
Key features:
- ladder_position — current ladder position (1 = top)
- ladder_points — competition points
- win_rate — wins / games played
- points_diff — for/against differential
Source: ml_nrl_ladder_features (dbt ML layer)
Weather Features
Weather conditions at match venue, provided as defaults with zero-fill when not scraped.
Key features (with defaults):
- temperature — °C at kickoff (default: 18.0)
- rainfall_mm — precipitation in mm (default: 0.0)
- wind_speed_kmh — wind speed (default: 10.0)
- is_wet_track — boolean flag for wet conditions (default: 0)
Source: WEATHER_FEATURES in data/backtest.py
Lineup / Player Features
Delta features comparing team list composition between rounds.
Key features:
- delta_true_new_player_rate — rate of debut players relative to opponent
- delta_transferred_player_rate — transferred player delta
- lineup_overall_delta — overall lineup strength delta
- lineup_spine_delta — halfback/hooker/fullback stability delta
Source: data/lineup.py → LINEUP_FEATURES in data/backtest.py
Odds / Market Features
Bookmaker-derived implied probabilities and market signals.
Key features:
- close_home_implied_prob — normalised implied home win probability at closing
- open_home_implied_prob — opening implied home win probability
- market_move — closing minus opening implied probability (steam signal)
- close_home_line — closing home handicap line
- close_total_points — closing over/under line
- elo_home_prob — Elo-based home win probability
Source: MARKET_FEATURES + MARKET_MODEL_FEATURES in data/backtest.py
Target / Result Fields
These fields are not used as model inputs. They are observed outcomes used for training labels and post-hoc evaluation.
| Field | Description |
|---|---|
home_margin |
home_score - away_score (training target for margin model) |
home_score |
Final home team score |
away_score |
Final away team score |
result |
Match outcome label (home win / draw / away win) |
Leakage risk: Never include score-derived features as model inputs. Only include them as
ylabels.
Paper Trading / CLV Fields
Diagnostic-only fields used to evaluate whether recommendations had positive Closing Line Value.
| Field | Description |
|---|---|
stake |
Flat stake allocated by edge policy |
model_ev |
Expected value at time of recommendation |
taken_price |
Odds at time of recommendation |
clv |
Closing Line Value (closing odds - taken odds) |
settlement |
WON / LOST / PUSH / NO BET |
These are diagnostics only — do not use clv or settlement as model inputs.
Leakage-Safe Feature Checklist
line-cover-goal-v1 research features
The opening-line cover study is evaluated by scripts/report_line_cover_goal.py.
Its model features are built from the opening line and prior-only backtest
state: open_home_line, absolute line, favourite-side indicator, de-vigged
opening H2H probability, their interaction, prior-only ELO difference, rolling
margin and win rate, points for/against, rest, finals, neutral venue, prior
match counts, and explicit history/form/ELO observation flags. Current team
lists, future weather, closing prices, movement, and result fields are
excluded. The favourite indicator is retained as a registered diagnostic
feature even though it is algebraically implied by the line sign; no other
deterministic duplicates are added.
The round-level evaluator uses gold_nrl_draw_latest.round when the historical
row maps to the draw; unmatched legacy rows use a documented calendar-week
proxy and are counted separately in the evidence ledger. Hyperparameters are
selected from pre-season history, while each candidate is refit only after a
completed evaluation round.
Before adding a new feature, verify:
- Point-in-time safe — the feature value is derived only from data available before the fixture kickoff timestamp.
- No score leakage — does not use home_score, away_score, home_margin, or any result field.
- No CLV/settlement leakage — does not use closing odds, CLV, or outcome labels.
- Deduplication safe — if from a rolling window, the window excludes the current match.
- NaN-handled — missing values have a sensible default (zero-fill for lineup, median for form features).
How to Add a New Feature Safely
1. Define the feature in dbt (preferred)
Create or extend an ML-layer model in analytics/dbt_nrl/models/ml/:
-- Example: ml_nrl_home_crowd_features.sql
SELECT
match_id,
season,
round,
kickoff_at_utc,
team_key,
is_home,
home_crowd_avg_l5 -- rolling 5-game average home crowd
FROM ...
WHERE kickoff_at_utc < {{ fixture_kickoff }} -- point-in-time filter
Add to schema.yml:
- name: ml_nrl_home_crowd_features
description: Rolling home crowd features at match-team grain.
columns:
- name: home_crowd_avg_l5
description: Rolling 5-game average home crowd, PIT-safe.
tests:
- not_null
2. Add to Python feature lists
In data/backtest.py:
CORE_FEATURES = [
...
"home_crowd_avg_l5", # add here
]
In data/prediction.py, ensure the feature is populated in base_feats (with a sensible default):
base_feats = {
...
"home_crowd_avg_l5": form.get("home_crowd_avg_l5") or 20000.0,
}
3. Rebuild and validate
cd analytics/dbt_nrl
dbt run --select ml_nrl_home_crowd_features
Then run the backtest to verify the feature improves AUC:
cd nrl-bet-advisor
python -m data.backtest
4. Update this catalogue
Add the new feature to the relevant group section above and document:
- Source table or function
- Calculation summary
- Known caveats or leakage risks
Known Caveats
| Feature | Caveat |
|---|---|
crowd_home_pct |
Requires manual Excel update; 0 real training samples until R13+ 2026 |
weather_* |
Defaults to moderate dry conditions when not scraped |
lineup_* |
Zero-filled when team list not available (e.g., early in round) |
market_move |
Currently set to 0.0 (no separate open price from odds API) |
elo_home_prob |
Regresses to mean at season boundary; early-season predictions less reliable |