Overview
Version 2 · Trimmed detailed architecture/DB/API content into dedicated architecture, database, and api-reference pages; overview now summarizes and links out, plus links to new user-guide and faq pages
Stats (Meeting Stats API)
Single-file FastAPI service (trainer_stats_api.py, app title "Meeting Stats API", currently
v2.0.0) that computes trainer and jockey performance statistics for horse-racing meetings —
win/place rates, impact values by SP price band, meeting averages, top horses — and optionally
persists/serves those stats. It also serves a small static dashboard, login page, and
interactive API docs/test pages from static/.
There is no separate module structure; the entire backend lives in one file.
Where to look
- architecture — data flow, external API bypass, endpoint naming split, middleware
- database — connections, schema, query patterns, a known SQL quirk
- api-reference — full endpoint table, auth, request/response examples
- user-guide — setup, running, testing, troubleshooting
- faq — recurring questions with durable answers
- changelog — delivered changes over time
Summary
build_entity_payload() is the key architectural seam: pure Python, no DB dependency, drives both
trainer and jockey stats from a flat list of race+run row dicts. Rows are fetched from a
source DB (read-only: race, run, courses) and results can be returned directly or
persisted via POST /save-stats into a target DB (meeting_stats_payloads). An optional
external-API bypass lets /trainerStats, /jockeyStats, /meetingStats fetch already-computed
stats from a third party instead of the local DB when config.json → external_api.enabled is
true.
Configuration
Copy config.example.json → config.json before running (git-ignored). Path can be overridden
with DB_CONFIG_PATH; every DB field can also be overridden via env var: DB_HOST, DB_PORT,
DB_USER, DB_PASSWORD, DB_SOURCE, DB_TARGET. external_api block is optional — omit or set
enabled: false to always use the local DB path. Details: database.
Auth
Single shared static bearer token (tds-stats-secure-token-2026), checked by the verify_token
dependency. Accepted via Authorization: Bearer, ?token=, or ?sso_token= query param.
POST /login with admin/admin123 (hardcoded) simply returns this same static token — there is
no per-user session, expiry, or revocation mechanism. This is a shared API key, not real
authentication.
The only server-side state is USER_CONTEXT — an in-memory dict keyed by client IP, set on
/trainerStats, /jockeyStats, /meetingStats calls, expiring after 5 minutes, read via
GET /api/user-context. Purely a UI convenience cache (lets the UI restore "last meeting
queried"); not persisted across restarts and not an auth mechanism.
GET /login redirects to an external Appsmith SSO login when the request looks
Appsmith-embedded (referer contains appsmith, no ?direct=). POST /api/logout proxies to an
external auth service by sessionId.
Frontend
Static files served from static/ (mounted at /static), no build step:
index.html→/— main dashboard (searchable trainer/jockey grids, filters, detail panel)login.html→/loginfallback — local login formapi_docs.html→/api-docs— hand-written interactive endpoint reference with "Try it"api_test.html→/api-test— manual API testerquery_executor.html— ad-hoc query tool, not routed
Running
pip install fastapi uvicorn pymysql pydantic python-multipart
copy config.example.json config.json # then fill in real credentials
uvicorn trainer_stats_api:app --host 0.0.0.0 --port 8006 --reload
# or: python trainer_stats_api.py
Access: http://127.0.0.1:8006/ (dashboard), /docs (Swagger), /redoc (ReDoc), /api-docs
(custom reference). Full setup/operating steps: user-guide.
As a persistent Windows service:
.\.venv\Scripts\python.exe run_service.py # or .\run_service.bat
Install/uninstall as Administrator: install_service.bat / uninstall_service.bat.
Security notes
- Single shared static token: anyone with it has full access to every endpoint; no per-user scoping, expiry, or revocation short of changing the constant and redeploying.
- Hardcoded login credentials (
admin/admin123) are effectively decorative —/loginreturns the same static token regardless of who logs in. config.example.jsonmust never contain real credentials — it is the template only.config.jsonshould stay out of version control; prefer env vars in production.- DB queries use parameterized queries (PyMySQL
%s/%(name)splaceholders) — no string-interpolated SQL found; keep this pattern for new queries. - CORS is currently wide open (
allow_origins=["*"],allow_credentials=True) — acceptable for an internal token-gated API, worth tightening before wider exposure.
Further reading in this repository
FULL_PROJECT_DOCUMENTATION.md in the repo root has a more exhaustive single-file reference
covering similar ground to this KB — useful as source evidence but not itself kept in sync with
the KB.