- Python 93.3%
- HTML 3.9%
- Shell 1.9%
- JavaScript 0.7%
- CSS 0.1%
|
Some checks failed
CI / test (push) Has been cancelled
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>
|
||
|---|---|---|
| .github/workflows | ||
| alembic | ||
| data | ||
| deploy | ||
| docs | ||
| samples | ||
| scripts | ||
| src | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| alembic.ini | ||
| CLAUDE.md | ||
| deploy.sh | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
| requirements-scraping.txt | ||
| requirements.txt | ||
| review_thorough_claude_20260421.md | ||
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
- docs/ROADMAP.md: current direction and top priorities
- docs/WORKSTREAM_TRACKER.md: authoritative status table for major workstreams
- docs/ROADMAP_STATUS_MODEL.md: standard status meanings and expected workflow
- docs/CHANGELOG.md: release history
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_registrywith quantitative gates, review actions, and forward-evaluation lanes forlive,shadow, andapproved_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-selectionadmin 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=falsefor 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_evalfor 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
- URL: https://racing.thecrombies.com
- Stats: https://racing.thecrombies.com/stats
- Server: AWS EC2 (ap-southeast-2)
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 viewGET /stats- Stats dashboardGET /smart-mode- Smart Mode analysisGET /timing- Bet timing analysisGET /todays-picks- Today's Smart Mode picks with emailGET /trends- P/L trends and version performance analysisGET /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 pageGET /logout- Logout
Race Data
GET /api/status- Connection statusGET /api/races- Today's races with analysisGET /api/race/<market_id>- Detailed race data
Tracking Control
POST /api/tracking/start- Start odds trackingPOST /api/tracking/stop- Stop odds trackingPOST /api/tracking/poll- Manual odds poll
Betting (admin only)
GET /api/betting/status- Betting service statusPOST /api/betting/start- Start automated bettingPOST /api/betting/stop- Stop automated bettingPOST /api/betting/stake- Set stake amountPOST /api/betting/limits- Set daily limitsPOST /api/betting/confirm/<market_id>- Confirm pending betPOST /api/betting/reject/<market_id>- Reject pending betGET /api/betting/history- Bet historyGET /api/betting/export-csv- Export history to CSV
Stats Dashboard
GET /api/stats/accuracy- Accuracy metricsGET /api/stats/factors- Factor analysisGET /api/stats/suggestions- Weight suggestions (includes cap warnings)GET /api/stats/predictions- Recent predictionsGET /api/stats/predictions-analysis- Detailed predictions breakdown (wins/losses)GET /api/stats/weights-comparison- Backtested ROI comparison across weight versionsGET /api/stats/snapshots/<market_id>- Multi-point prediction snapshotsGET /api/stats/lookup?type=horse&query=name- Historical lookup (horse/jockey/trainer)GET /api/stats/daily- Daily performance breakdownGET /api/stats/picks/today- Today's Smart Mode picks (JSON/CSV)POST /api/stats/fetch-results- Manual results fetchPOST /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 predictionsGET /api/model/probability/forward-log- Shared forward evaluation table across rules, Favourite Value, and Runner SelectionGET /api/model/probability/execution-plan- Current rules/probability control-plane execution summaryPOST /api/model/favourite-value/train- Train/retrain Favourite Value modelGET /api/model/favourite-value/info- Favourite Value model metadataGET /api/model/favourite-value/backtest- Favourite Value backtest at multiple edge thresholdsGET /api/model/favourite-value/predictions- Favourite Value predictions for upcoming racesGET /api/model/favourite-value/prediction-log- Favourite Value historical prediction logGET /api/model/favourite-value/calibration- Favourite Value calibration curve dataGET /api/model/favourite-value/snapshot-predictions?race_id=X- Favourite Value snapshot timeline for a raceGET /api/model/runner-selection/info- Runner Selection model metadataGET /api/model/runner-selection/backtest- Runner Selection backtestGET /api/model/runner-selection/prediction-log- Runner Selection forward logPOST /api/model/runner-selection/review/jobs- Background candidate review with evidence capturePOST /api/model/probability/compare/jobs- Background same-window strategy comparison job
User Profile
GET /api/user/profile- Get user profile infoGET /api/user/email- Get user's saved emailPOST /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-localgate 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 withLOCAL_TEST_DATABASE_URL). - The smoke boot uses
.venv/bin/python -m src.web.appwith:AUTO_BETTING_ENABLED=falsePAPER_TRADING_ENABLED=trueFLASK_DEBUG=true
- Use
release-prodto 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-racingvia systemd - smoke-checks remote
/loginand/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.