No description
  • Python 93.3%
  • HTML 3.9%
  • Shell 1.9%
  • JavaScript 0.7%
  • CSS 0.1%
Find a file
John Crombie b1b6ee1d30
Some checks failed
CI / test (push) Has been cancelled
Record every tested criterion, with a full-period backtest (v0.31.13)
Adds docs/pdfs/TESTED_CRITERIA_20260902.pdf: the criteria for all 56 tested
entities (42 smart mode rules, 5 hypotheses, 6 agreement policies, 3 lay
policies), each with what it would have returned had it been active for the
whole data window.

Generated from the definition sources rather than transcribed, so the document
cannot drift from the code: rules.json, hypothesis_registry.json,
policy_registry.json, and the LAY_POLICIES literal read out of evaluator.py via
ast (importing it would initialise logging as a side effect). Filter semantics
come from calling the canonical check_smart_mode_bet() and evaluate_lay(), not
from a reimplementation.

Where reality differed from the task as stated:

- rules.json retired_at is stale and is NOT the lifecycle authority. The prod
  smart_rule_registry governs. 23 rules carry a file date contradicting the
  registry and 15 carry no date at all for a rule the registry has retired;
  only 3 agree. Building this from the JSON alone would have been wrong on 38
  of 42 rules. The document shows the registry date and flags the conflict.

- v10 reads as profitable in its own file note ("+11.1% ROI, 244 bets"). Over
  the full window it is -9.45% net on 692 bets, -$653.88. It was +6.07%
  cumulative at the end of April and was promoted to live on 24 April, at the
  point it looked best, then lost money in five of the six months since. The
  note records the window that got it promoted, not the result.

- Only 2 of 42 rules finish net positive (v31 +3.53% on 193 bets at SE 10.32pp;
  v27 +1.13% on 43 bets at SE 20.14pp). Both are inside their own margin of
  error. The obvious next step is to promote the best-scoring one; that is
  exactly the procedure that produced v10, so the document says so instead.

- h4/h5 are not offered a backtest figure. Replaying the signal from logged
  snapshots finds 149 and 48 matches where the live evaluator recorded 15 and
  3, an order-of-magnitude disagreement, and 5min strategy rows do not exist
  before 2026-07-01. Both numbers are reported and neither is claimed.

Window is 2026-03-06 to 2026-09-02 (134,034 settled rows), bounded by the
bet_eval snapshot. Results exist from 2026-01-25 but carry no snapshot rank,
and substituting a recalculated rank is forbidden by INVARIANTS and would be
anachronistic. Settled at SP, flat $10, canonical per-state commission.

Validation: the replay selects exactly the 532 rule bets production recorded
for v10 since promotion, no more and no fewer, and reproduces the same -14.53%
net ROI and 25.9% strike. Pointed at a different rule the agreement drops to
12.1%, so the check can fail. Content checks against the rendered PDF text were
mutation-tested: deleting an entry, leaking an unlabelled filter key, inserting
an em-dash, flipping a retired rule to live, and altering a backtest figure
each drive them red.

make test-local-fast: 794 passed, 4 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 23:19:34 +10:00
.github/workflows Add CI workflow: pytest + alembic migration check on Python 3.11 (v0.26.19) 2026-05-04 10:13:59 +10:00
alembic Add Racing Australia gear-change capture (v0.28.0) 2026-08-19 01:56:13 +10:00
data Retire all six agreement shadow policies (v0.26.46) 2026-08-16 00:50:56 +10:00
deploy Stop the imputer warning flood; exclude empty features at train (v0.26.34) 2026-08-15 02:46:17 +10:00
docs Record every tested criterion, with a full-period backtest (v0.31.13) 2026-09-02 23:19:34 +10:00
samples v0.13.62: Replace deprecated datetime.utcnow() for Python 3.12+ 2026-02-09 03:04:51 +11:00
scripts Lock a pass criterion for the shadow gate; raise quick_check bound (v0.31.12) 2026-08-24 14:34:19 +10:00
src Record every tested criterion, with a full-period backtest (v0.31.13) 2026-09-02 23:19:34 +10:00
tests Lock a pass criterion for the shadow gate; raise quick_check bound (v0.31.12) 2026-08-24 14:34:19 +10:00
.env.example v0.11.4: Add Mailgun email support 2026-01-29 00:49:32 +11:00
.gitignore Fix deadlock in update_hypothesis_status; record killed_date (v0.26.27) 2026-08-15 00:09:28 +10:00
alembic.ini Add Alembic database migration support (Phases 1-3) 2026-03-22 20:47:19 +11:00
CLAUDE.md Record the Sportsbet/TopSport chase; bring docs in line with v0.26.72 2026-08-18 11:07:07 +10:00
deploy.sh Fix a set -e regression that aborts deploys silently (v0.28.2) 2026-08-19 09:35:30 +10:00
Makefile Add Alembic database migration support (Phases 1-3) 2026-03-22 20:47:19 +11:00
pyproject.toml Add CI workflow: pytest + alembic migration check on Python 3.11 (v0.26.19) 2026-05-04 10:13:59 +10:00
README.md Extract shared score calculation, fix None-as-0 bug, audit BUGS.md 2026-03-23 15:20:45 +11:00
requirements-scraping.txt Reduce SQLite lock contention during peak race hours 2026-04-03 17:22:04 +11:00
requirements.txt Fix P3 items: dedup functions, remove debug payload, pin deps, code rollback (v0.26.9) 2026-04-27 02:50:55 +10:00
review_thorough_claude_20260421.md Fix 5 bugs: lay NameError, FV ghost feature, auth, readonly sessions, commission (v0.26.5) 2026-04-27 01:54:19 +10:00

Horse Racing Analysis Tool

A Python web application for analysing Australian horse racing data via the Betfair API. Provides statistical analysis, rankings, automated prediction tracking, and backtesting to support betting decisions.

Current Version: 0.13.82

Documentation Map

Features

Core Analysis

  • Race Data Download: Fetches race cards and runner data from Betfair API
  • Statistical Analysis: Analyses 9 weighted factors for each runner
  • Horse Rankings: Generates relative rankings with score visualisation
  • Dual Rank Display: Shows both our rank and market favourite position
  • VALUE Indicator: Highlights horses where our rank beats market rank
  • Race Filtering: Excludes races with <7 or >12 runners (configurable)
  • First-Up Detection: Flags horses returning from 60+ day spell

Market Intelligence

  • Odds Tracking: Automatic price polling (5 min normal, 1 min close to race)
  • Price Movement: Tracks firming/drifting trends
  • Market Rank: Identifies favourites and market position
  • Rate Monitoring: Real-time API quota display with warning banner

Prediction Tracking (v0.3.0+)

  • Auto-Save Predictions: Saves predictions for ALL races (analysis filters to 7-12 runners)
  • Results Fetching: Automatically retrieves race results from Betfair
  • Accuracy Metrics: Win rate, place rate, ROI simulation
  • Factor Analysis: Identifies which factors best predict winners
  • Weight Suggestions: Data-driven recommendations for weight adjustments
  • Historical Lookup: Search by horse, jockey, or trainer name

Smart Mode (v0.6.0+)

  • Smart Betting Strategy: Bet market favourite only when in our top 3 rankings
  • Race Badges: "SMART BET" (green), "WEAK FAV" (red), "v2 SKIP", "v3 SKIP", "NO ODDS" (grey)
  • Toggle Filter: Show only Smart Bet races
  • Smart Mode Stats: Separate accuracy metrics for Smart Mode strategy
  • Smart Mode Backtesting: Compare Smart ROI across weight versions
  • v2 Filters: Minimum odds ≥$2.00, exclude 11-runner fields
  • v3 Filters: Gallop only (excludes Trot/Pace harness racing)
  • v25-v37 Filters: Data-driven rule set with live, shadow, approved, hold, and retired lifecycle states
  • Stacked Mode: Run multiple non-overlapping filters in parallel (OR logic)
  • Current Live Stack: v34
  • Current Shadow Stack: v30, v31, v36, v37
  • Lifecycle-First Governance: Smart Mode versions are now governed by a separate smart_rule_registry with quantitative gates, review actions, and forward-evaluation lanes for live, shadow, and approved_for_live

Automated Betting (v0.9.65+)

  • Live Betfair Integration: Place real bets via Betfair API
  • Paper Trading Mode: Test strategies without real money
  • Smart Mode Automation: Auto-bet when rules criteria match
  • Confirmation Mode: Review bets before placement
  • Daily Limits: Configurable stake limits, loss limits, max bets per day
  • Real-time Dashboard: Monitor upcoming bets, pending confirmations, daily P/L

Authentication (v0.9.67+)

  • Role-Based Access: Admin (full access) and Viewer (no automation) roles
  • Flask-Login: Secure session management with 24-hour timeout
  • CSRF Protection: Cross-site request forgery prevention on login
  • Secure Cookies: HTTPOnly, SameSite, Secure cookie settings

Today's Picks (v0.11.5+)

  • Dedicated Picks Page: View all Smart Mode qualifying races for today
  • AEST Timezone: Shows races for 00:00-23:59 AEST (Australian Eastern Time)
  • Summary Cards: Total picks, Pending, Wins, Losses, Win Rate
  • Export Options: JSON and CSV download
  • Email Integration: Send picks to your email, save email preference
  • Auto-Refresh: Updates every 5 minutes

Early Race Import (v0.12.0+)

  • Immediate Import: Races saved to database as soon as available from Betfair
  • Initial Snapshot: Captures scores and odds when race first seen
  • Hourly Snapshots: Tracks how predictions change over time (12hr, 11hr, ... 2hr)
  • Full Timeline: Complete prediction history from first seen to race start
  • Trend Analysis: Compare early predictions vs final pre-race scores

Favourite Value (v0.13.82+)

  • Calibrated Logistic Regression: Predicts P(win) for market favourites
  • Feature Set: log_price, our_score, 9 factor scores, barrier, pre_race_move, runner_count, our_rank, race_type
  • Dashboard: Overview, model lifecycle, calibration, walk-forward backtest, predictions, and prediction log
  • 3-Method Comparison: Rules-only vs Probability-only vs Hybrid (both agree)
  • Shadow Variant Comparison: Prediction Log shows settled stats per active shadow policy, including start and backfill timestamps
  • Snapshot Timeline: Tracks P(win) evolution at 30min → 5min → 1min → bet_eval
  • Edge Threshold: Configurable edge threshold for probability-based bet recommendations
  • Governed Lifecycle: Candidate review, hard promotion gates, approval state, shadow continuity, rollback, and retirement workflow
  • Forward Evaluation Role: Logged in parallel with Runner Selection for evaluation; it is not a separate execution engine from Smart Mode automation

Runner Selection (experimental)

  • Separate Strategy B: Independent runner-level model and policy pipeline
  • Runner-Level P(win): Scores all runners instead of only pricing the favourite
  • Race-Level Selection Policy: Selects best candidate per race using minimum edge and race-level bet caps
  • Walk-Forward Review Surface: Dedicated /probability/runner-selection admin page for backtests, versions, and same-window comparison vs Favourite Value
  • Hard Promotion Gate: Production promotion is blocked unless the promoted candidate clears minimum sample, AUC, Brier, fold-stability, and data-quality thresholds
  • Paper Activation Guardrail: Runner Selection is only paper-eligible when its promoted production model clears that promotion gate
  • Governed Lifecycle: Candidate review, hard promotion gates, approval requirement, shadow continuity, rollback, and retirement workflow
  • Forward Evaluation Role: Logged in parallel with Favourite Value at bet_eval; live and paper automation still remain Smart Mode rules-driven

Betfair Commission (v0.13.82+)

  • Accurate P/L: 5% commission on net winnings factored into all ROI and P/L calculations
  • Commission Toggle: "Include Commission" checkbox on all stats/backtest pages
  • API Support: All endpoints accept ?include_commission=false for gross figures

Email Alerts (v0.11.2+)

  • Mailgun Integration: Reliable email delivery via HTTPS API
  • Daily Picks Email: Automated morning email with qualifying races
  • Send to Me: Manual send to any email address
  • HTML Formatting: Professional email template with race details

Backtesting & Version Control (v0.5.0+)

  • Weights Versioning: Track changes to ranking weights over time
  • Backtesting: Recalculate historical picks with different weight configurations
  • ROI Comparison: Compare hypothetical ROI across all weight versions
  • Smart Mode Comparison: See which weights best filter weak favourites
  • Weights Persistence: Custom weights survive server restarts
  • Multi-Point Snapshots: Track predictions from first seen through race start (initial, hourly, 60min down to 1min)
  • Auto-Refresh: Stats page polls for new results every 2 minutes

User Interface

  • Race View: Sortable columns (rank, barrier, price, score, form, weight)
  • Search Filters: Filter by venue, race name, or horse name
  • Stats Dashboard: Accuracy metrics, factor analysis, weight suggestions
  • Visual Indicators: Score display, price movement colours, first-up badges, VALUE badges
  • Australian Timezone: All times displayed in AEST/AEDT

Data Quality & Normalization

  • Sex Normalization: Incoming horse sex values are canonicalized to uppercase single-letter codes (G, M, F, H, C, R, S)
  • Historical Backfill: Existing runner sex values in the database have been normalized to remove lowercase and full-word variants
  • Venue State Backfill: Missing historical venue/state mappings have been backfilled so state-aware Smart Mode analysis is now based on cleaned race state data
  • Probability Forward Logging: Favourite Value and Runner Selection are both logged at bet_eval for forward evaluation, while Smart Mode rules still drive actual automation bets
  • Forward Rollouts: Forward evaluation is now grouped into named rollout windows so operator views can be reset without deleting historical evidence

Live Deployment

Quick Start

# Clone and setup
git clone <repository>
cd horse-betting

# Create virtual environment
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

# Configure credentials
cp .env.example .env
# Edit .env with your Betfair credentials

# Initialize database
python -m src.utils.db_setup

# Start the application
python -m src.web.app

Open http://localhost:5000 in your browser.

Project Structure

horse-betting/
├── src/
│   ├── api/
│   │   ├── client.py           # Betfair Exchange API client
│   │   └── timeform.py         # Timeform API (Phase 2)
│   ├── analysis/
│   │   └── ranking.py          # Ranking engine
│   ├── models/
│   │   ├── horse.py            # Horse data model
│   │   └── race.py             # Race data model
│   ├── core/                  # Shared replay, registry, feature primitives
│   ├── services/
│   │   ├── betting_service.py  # Betting logic, tiered staking & limits
│   │   ├── email_service.py    # Email alerts (Mailgun/SMTP)
│   │   ├── neds_enrichment.py  # TAB/Neds API enrichment (sex, track, weather)
│   │   ├── odds_tracker.py     # Background odds polling
│   │   ├── odds_comparison_service.py # Multi-bookmaker comparison
│   │   ├── prediction_tracker.py # Prediction & results tracking
│   │   ├── probability_model.py # Favourite-value compatibility shim
│   │   ├── race_manager.py     # Per-race snapshot threads
│   │   └── score_calculation.py # Canonical weighted score recalculation
│   ├── strategies/
│   │   ├── favourite_value/    # Strategy A: favourite-only pricing
│   │   └── runner_selection/   # Strategy B: runner-level selection
│   ├── web/
│   │   ├── app.py              # Flask application
│   │   ├── auth.py             # Authentication & roles
│   │   ├── state.py            # Shared application state
│   │   ├── blueprints/         # API route handlers (13 files)
│   │   │   ├── api_betting.py  # Bet control, limits, confirm/reject
│   │   │   ├── api_races.py    # Race data endpoints
│   │   │   ├── api_smart_mode.py # Smart Mode qualification, drift
│   │   │   ├── api_smart_mode_backtest.py # Backtesting engine
│   │   │   ├── api_stats.py    # Stats dashboard
│   │   │   ├── api_trends.py   # P/L trends, version performance
│   │   │   ├── api_model.py    # Favourite-value and runner-selection endpoints
│   │   │   ├── api_tracking.py # Odds tracking control
│   │   │   ├── api_user.py     # User profile, email prefs
│   │   │   ├── _utils.py       # Shared SQL filters, utilities
│   │   │   └── pages.py        # Page routes
│   │   └── templates/          # HTML templates
│   │       ├── index.html      # Main race view
│   │       ├── stats.html      # Stats dashboard
│   │       ├── automation.html # Betting automation
│   │       ├── smart_mode.html # Backtest & drift analysis
│   │       ├── favourite_value.html # Favourite-value dashboard
│   │       ├── runner_selection.html # Runner-selection review dashboard
│   │       ├── trends.html     # P/L trends
│   │       ├── timing.html     # Bet timing analysis
│   │       ├── todays_picks.html # Today's picks
│   │       └── login.html
│   ├── utils/
│   │   └── db_setup.py         # Database schema
│   └── config.py               # Configuration
├── tests/                      # Unit tests
├── deploy/
│   ├── DEPLOY.md               # Deployment guide
│   └── setup-ubuntu.sh         # Server setup script
├── docs/
│   ├── API.md                  # Betfair API documentation
│   ├── BUGS.md                 # Known issues tracker
│   ├── CHANGELOG.md            # Version history
│   ├── ROADMAP.md              # Future plans
│   ├── SMART_MODE.md           # Smart Mode strategy analysis
│   └── WEIGHTS_VERSIONS.md     # Weights version history
├── certs/                      # SSL certificates (gitignored)
├── data/                       # SQLite database, weights.json, users.json
└── README.md

Configuration

Ranking Weights (v1.4 - Current)

RANKING_WEIGHTS = {
    'recent_form': 0.25,        # 25% - Last 3-5 race positions
    'barrier': 0.23,            # 23% - Starting position (boosted in v1.4)
    'market_movement': 0.10,    # 10% - Price firming/drifting
    'pace': 0.10,               # 10% - Estimated pace suitability
    'weight': 0.08,             # 8% - Weight carried
    'days_since_run': 0.08,     # 8% - Freshness (first-up flag)
    'jockey': 0.06,             # 6% - Jockey (name-based)
    'market_rank': 0.05,        # 5% - Betting market position (reduced in v1.4)
    'trainer': 0.05,            # 5% - Trainer (name-based)
}

Weights can be adjusted via the Stats Dashboard "Apply Suggested Weights" feature.

  • Maximum weight cap: 35% (prevents single-factor dominance)
  • Minimum weight floor: 2%
  • Weights persist across server restarts in data/weights.json

See docs/WEIGHTS_VERSIONS.md for full version history.

Environment Variables

# Required (app fails to start without these)
BETFAIR_APP_KEY=your_app_key
BETFAIR_USERNAME=your_username
BETFAIR_PASSWORD=your_password
BETFAIR_CERTS_DIR=./certs    # Directory with client-2048.crt and client-2048.key
FLASK_SECRET_KEY=your_secret # Generate with: python -c "import secrets; print(secrets.token_hex(32))"

# Optional - Betting
TIMEFORM_API_KEY=            # Premium features
MIN_RUNNERS=7                # Minimum field size
MAX_RUNNERS=12               # Maximum field size
PAPER_TRADING_ENABLED=true   # Set false for live betting
AUTO_BETTING_ENABLED=false   # Enable automated betting
ACTIVE_SMART_MODE_VERSION=3  # Single version fallback (used when stacked is empty)
STACKED_SMART_MODE_VERSIONS=25,27,30,31,32,33,34 # Stacked mode: bet on ANY matching version

# Optional - Email (Mailgun recommended)
EMAIL_PROVIDER=mailgun       # "mailgun" or "smtp"
MAILGUN_API_KEY=             # Mailgun API key
MAILGUN_DOMAIN=              # e.g., mg.yourdomain.com
EMAIL_RECIPIENTS=            # Comma-separated list for daily email
EMAIL_SCHEDULE=06:00         # Daily email time (AEST)

Odds Tracking

ODDS_POLL_INTERVAL_NORMAL = 300   # 5 minutes
ODDS_POLL_INTERVAL_CLOSE = 60     # 1 minute (last 5 mins before race)

Database Schema

Prediction Tables

Table Purpose
race_predictions Race metadata, prediction timestamp, weights version, race type
runner_predictions Per-runner scores, prices, results, all factor scores, raw inputs
prediction_snapshots Multi-point tracking (initial, hourly, 60min down to 1min)
race_predictions (model) Per-race probability model log (p_model, edge, rule_bet, prob_bet)
snapshot_predictions Per-snapshot model predictions for timeline analysis (30min/5min/1min/bet_eval)
accuracy_log Daily accuracy metrics

Betting Tables (v0.9.65+)

Table Purpose
bet_records Bet history with status, P/L, Betfair bet ID

Historical Tables

Table Purpose
horses Horse master data
performances Historical race results
jockey_stats Jockey statistics
trainer_stats Trainer statistics

Data Files

File Purpose
data/weights.json Current weights, version history, all stored versions
data/users.json User credentials and roles
data/betting_state.json Daily limits and betting state

Automated Workflows

The application runs several background processes:

Process Frequency Action
Odds Tracking 5 min / 1 min Polls market prices
Prediction Saver 5 min Saves predictions for ALL upcoming races (immediate import)
Hourly Snapshots 5 min loop Captures hourly snapshots for races > 1 hour away
Results Fetcher 10 min Fetches settled race results
Final Hour Snapshots Per-race threads Captures precise snapshots at 60/45/30/20/15/10/5/3/2/1 min
Smart Mode Betting Continuous Places bets when criteria met (if enabled)

All processes start automatically when the application runs.

Snapshot Timeline

Each race has a complete prediction history:

Snapshot Timing Captured By
initial When first seen Background loop
12hr, 11hr, ... 2hr Hourly until 1 hour before Background loop
60min 60 minutes before RaceManager thread
45min, 30min, 20min, 15min, 10min Final hour RaceManager thread
5min, 3min, 2min, 1min Final minutes RaceManager thread
bet_eval At bet evaluation time RaceManager thread

Betting Modes

  • Paper Trading (PAPER_TRADING_ENABLED=true): Smart Mode automation bets are recorded but not placed on Betfair
  • Live Trading (PAPER_TRADING_ENABLED=false): Smart Mode automation bets are placed on Betfair
  • Probability Evaluation: Favourite Value and Runner Selection are forward-evaluated in parallel and compared on /probability; they do not currently execute separate automation bets

API Endpoints

Pages (require login)

  • GET / - Main race view
  • GET /stats - Stats dashboard
  • GET /smart-mode - Smart Mode analysis
  • GET /timing - Bet timing analysis
  • GET /todays-picks - Today's Smart Mode picks with email
  • GET /trends - P/L trends and version performance analysis
  • GET /automation - Betting automation (admin only)
  • GET /probability - Probability overview (admin only)
  • GET /probability/favourite-value - Favourite-value dashboard (admin only)
  • GET /probability/runner-selection - Runner-selection dashboard (admin only)
  • GET /login - Login page
  • GET /logout - Logout

Race Data

  • GET /api/status - Connection status
  • GET /api/races - Today's races with analysis
  • GET /api/race/<market_id> - Detailed race data

Tracking Control

  • POST /api/tracking/start - Start odds tracking
  • POST /api/tracking/stop - Stop odds tracking
  • POST /api/tracking/poll - Manual odds poll

Betting (admin only)

  • GET /api/betting/status - Betting service status
  • POST /api/betting/start - Start automated betting
  • POST /api/betting/stop - Stop automated betting
  • POST /api/betting/stake - Set stake amount
  • POST /api/betting/limits - Set daily limits
  • POST /api/betting/confirm/<market_id> - Confirm pending bet
  • POST /api/betting/reject/<market_id> - Reject pending bet
  • GET /api/betting/history - Bet history
  • GET /api/betting/export-csv - Export history to CSV

Stats Dashboard

  • GET /api/stats/accuracy - Accuracy metrics
  • GET /api/stats/factors - Factor analysis
  • GET /api/stats/suggestions - Weight suggestions (includes cap warnings)
  • GET /api/stats/predictions - Recent predictions
  • GET /api/stats/predictions-analysis - Detailed predictions breakdown (wins/losses)
  • GET /api/stats/weights-comparison - Backtested ROI comparison across weight versions
  • GET /api/stats/snapshots/<market_id> - Multi-point prediction snapshots
  • GET /api/stats/lookup?type=horse&query=name - Historical lookup (horse/jockey/trainer)
  • GET /api/stats/daily - Daily performance breakdown
  • GET /api/stats/picks/today - Today's Smart Mode picks (JSON/CSV)
  • POST /api/stats/fetch-results - Manual results fetch
  • POST /api/stats/apply-weights - Apply suggested weights (with no-change detection)
  • POST /api/stats/email/send - Send daily picks email (admin)
  • POST /api/stats/email/send-to-me - Send picks to user's email

Probability APIs (admin only)

  • GET /api/model/probability/predictions - Active shared probability strategy predictions
  • GET /api/model/probability/forward-log - Shared forward evaluation table across rules, Favourite Value, and Runner Selection
  • GET /api/model/probability/execution-plan - Current rules/probability control-plane execution summary
  • POST /api/model/favourite-value/train - Train/retrain Favourite Value model
  • GET /api/model/favourite-value/info - Favourite Value model metadata
  • GET /api/model/favourite-value/backtest - Favourite Value backtest at multiple edge thresholds
  • GET /api/model/favourite-value/predictions - Favourite Value predictions for upcoming races
  • GET /api/model/favourite-value/prediction-log - Favourite Value historical prediction log
  • GET /api/model/favourite-value/calibration - Favourite Value calibration curve data
  • GET /api/model/favourite-value/snapshot-predictions?race_id=X - Favourite Value snapshot timeline for a race
  • GET /api/model/runner-selection/info - Runner Selection model metadata
  • GET /api/model/runner-selection/backtest - Runner Selection backtest
  • GET /api/model/runner-selection/prediction-log - Runner Selection forward log
  • POST /api/model/runner-selection/review/jobs - Background candidate review with evidence capture
  • POST /api/model/probability/compare/jobs - Background same-window strategy comparison job

User Profile

  • GET /api/user/profile - Get user profile info
  • GET /api/user/email - Get user's saved email
  • POST /api/user/email - Save email preference

Deployment

See deploy/DEPLOY.md for full deployment guide.

Quick Deploy (AWS EC2)

# On Ubuntu 22.04 server
curl -O https://raw.githubusercontent.com/<repo>/main/deploy/setup-ubuntu.sh
chmod +x setup-ubuntu.sh
sudo ./setup-ubuntu.sh

HTTPS Setup

sudo certbot --nginx -d yourdomain.com

Authentication

The application uses Flask-Login (no nginx auth needed). Default users are created on first startup with random passwords - check the logs:

journalctl -u horse-racing | grep "DEFAULT USERS"

Data Sources

Tier Source Cost Data
Free Betfair Exchange API Free Form, ratings, weight, barrier, jockey, trainer
Premium Timeform API Subscription Sectionals, speed ratings, detailed history

Running Tests

pytest
pytest --cov=src  # With coverage

Current local pre-deploy gate:

  • compile checks over key runtime modules
  • full pytest
  • local smoke checks against /login, /probability, and /api/model/train-preflight

The gate is run via:

make test-local

Frontend Testing with Python

The app uses server-rendered Jinja2 templates with no separate JS build step. All frontend testing is done in Python:

  • Template smoke tests: The make test-local gate starts the app and hits key page routes (/login, /probability, /api/model/train-preflight) to catch Jinja2 render errors, missing template variables, and broken imports.
  • Flask test client: Use app.test_client() in pytest to verify page status codes, response content, and JSON endpoint shapes without a running server.
  • Production verification: Post-deploy, use the scp+python pattern (see deploy/DEPLOY.md) to exercise real code paths on the server with production data. This avoids CSRF/auth issues that block curl-based testing.
# Example: test a page renders in pytest
from src.web.app import create_app

def test_login_page_renders():
    app = create_app()
    app.config["TESTING"] = True
    with app.test_client() as client:
        resp = client.get("/login")
        assert resp.status_code == 200
        assert b"<form" in resp.data

When adding new pages or modifying templates, add the route to the smoke-check list in scripts/run_local_full_check.sh. See deploy/DEPLOY.md for the full frontend testing guide.

Local Pre-Deploy Workflow

Use your existing .env for full local app checks before deploying.

# Full gate: compile + pytest + local app smoke
make test-local

# Faster gate: compile + pytest only
make test-local-fast

# Run app locally in safe test mode (forces AUTO_BETTING=false, PAPER_TRADING=true)
make run-local

Notes:

  • The local gate script is scripts/run_local_full_check.sh.
  • It validates required env keys from .env, runs tests, then smoke-checks local endpoints.
  • It forces a local DB during the gate via DATABASE_URL=sqlite:///.../data/races.db (override with LOCAL_TEST_DATABASE_URL).
  • The smoke boot uses .venv/bin/python -m src.web.app with:
    • AUTO_BETTING_ENABLED=false
    • PAPER_TRADING_ENABLED=true
    • FLASK_DEBUG=true
  • Use release-prod to enforce test-before-deploy in one command:
make release-prod

Direct deploy command:

./deploy.sh

Current deploy behavior:

  • rsyncs code to /opt/horse-racing/app
  • excludes local data/, .env, virtualenvs, caches, and SQLite files
  • restarts horse-racing via systemd
  • smoke-checks remote /login and /probability

Important:

  • until Alembic is implemented, schema-changing releases may still require running init_database() on prod after deploy

Roadmap

Completed

  • Backtesting framework (v0.5.4)
  • Weights version tracking (v0.5.0)
  • Custom weight configuration (v0.4.0)
  • Multi-point prediction snapshots (v0.5.1)
  • Weights persistence (v0.5.3)
  • Auto-refresh stats dashboard (v0.5.2)
  • Smart Mode v1/v2/v3 with filtering (v0.6.0+)
  • State/track type filters (v0.6.1)
  • Calculation consistency audit (v0.9.55-v0.9.58)
  • Price fallback standardization (v0.9.55-v0.9.56)
  • Score consistency between class/dict methods (v0.9.58)
  • Raw input data storage for recalculation (v0.9.59)
  • Live Betfair betting integration (v0.9.65)
  • Paper trading mode (v0.9.63)
  • User authentication with roles (v0.9.67)
  • Daily betting limits (v0.9.66)
  • CSRF protection (v0.9.76)
  • Session timeout & secure cookies (v0.9.77)
  • Betfair API timeout (v0.9.78)
  • Credential validation at startup (v0.9.72-v0.9.73)
  • Data capture refactor - all races captured (v0.10.0)
  • Daily stats breakdown (v0.11.0)
  • Export today's picks JSON/CSV (v0.11.1)
  • Email alerts via Mailgun (v0.11.2-v0.11.4)
  • Today's Picks page with AEST timezone (v0.11.5-v0.11.6)
  • User email preferences (v0.11.5)
  • Early race import - immediate database save (v0.12.0)
  • Hourly snapshots for trend analysis (v0.12.0)

Planned

  • Timeform API integration (Phase 2)
  • Jockey/trainer historical statistics
  • Track condition preferences
  • Historical accuracy charts
  • Multi-bookmaker odds comparison (API doesn't support horse racing)

Future Enhancements (Data Backfill)

  • The Racing API Australian add-on (historical data backfill)
  • TAB Studio API integration (official AU data)
  • Historical raw data backfill (requires paid API)

See docs/CHANGELOG.md for version history.

License

Private use only.