# ==========================================
# File: README.md
# Role: Project entry point and operator guide.
# Input Data: N/A
# Output Data: Setup instructions, deploy targets, and architecture overview.
# Dependencies: N/A
# Notes: Keep this aligned with real deploy targets and current setup steps.
# ==========================================
Full-stack NFL forecasting workspace with a FastAPI backend, a React/Vite frontend, a dataset build pipeline, and a model training pipeline.
- Active dataset target:
backend/data/datasets/game_features_20260531_clean.csv - Active schedule artifact:
backend/data/Nfl_schedule_2026.csv - Active model bundle:
backend/modelsversion20260531T124903Z-prod-2026 - Active dataset hash:
94bd8ca5e7e47ac5db5d4d583daaa93265313be24a20bf909848db68a18f188b - Dataset seasons: 2018-2026
- Future-game coverage: 272 leak-safe 2026 regular-season rows
- Future-row rule: scheduled games may include market/schedule context, but final scores and target columns stay null until completed
- Model readiness rule:
/predictshould only serve when/health/pipelinereports no blockers and the model bundle dataset hash matcheslatest_dataset.json
- GitHub source branch:
master - Frontend: Vercel project
nfl-ml-predictions - Production frontend alias:
https://new-nfl-predict.vercel.app - Backend: Heroku app
nfl-predict - Canonical backend origin:
https://nfl-predict-ecf5a5bd34fe.herokuapp.com
Deploy intent:
- GitHub Actions deploys from
master - Vercel should build from
frontend/ - Heroku should serve the FastAPI backend with the buildpack +
Procfileflow - Production CORS should allow the canonical frontend origin plus
.vercel.apppreviews
- Serves NFL schedule, health, status, prediction, and history endpoints through
backend/routes/api.py, with route-facing workflows inbackend/services/api_runtime.pyand app bootstrap inbackend/main.py. - Stores user-scoped prediction history in SQLite first, with JSON files as a fallback.
- Builds cleaned training datasets into
backend/data/datasets/. - Trains score and win-probability models and promotes bundles for serving.
- Ships a React app with a protected dashboard, history view, and status page.
python -m pip install -r requirements.txt
cd frontend
npm install
cd ..python -m backend.services.schedule_ingestion --season 2026 --season-types 2,3 ^
--out-csv backend/data/Nfl_schedule_2026.csv ^
--out-parquet backend/data/schedules/nfl_schedule_2026.parquet ^
--raw-dir backend/data/raw/espn/scoreboardsThe schedule ingestion layer keeps future games leak-safe by leaving scores null for non-completed games.
python backend/builddataset.py --start 2018 --end 2026 --out-dir backend/data/datasets --encode onehot --no-calibration-rowsWhat this writes:
- A dated run folder in
backend/data/datasets/runs/<timestamp>/ - A promoted clean CSV in
backend/data/datasets/ - Completed and future partitions in
backend/data/datasets/ backend/data/datasets/latest_dataset.json- Schema, missingness, duplicate, and training-readiness reports in the run folder
python backend/train_models.py --data backend/data/datasets/game_features_20260531_clean.csv --out backend/models --productionScore prediction now defaults to --score-model ensemble, which blends the existing gradient-boosted regressor with an MLPRegressor neural network. Use --score-model hgb for a gradient-boosting-only comparison run, or tune the blend with --nn-weight 0.35.
What this writes by default:
- Promoted artifacts in
backend/models/ - A staging bundle in
backend/models/staging/<run_id>/ metadata.json,training_report.json, andrun_summary.json- Regression component metrics for the boosted score model, neural score model, and selected ensemble
- A dated mirror in
backend/YYYYMMDD/models/when training uses the default output directory
Important runtime note:
- Training still writes to
backend/models/by default. - Serving prefers
MODELS_DIRwhen set, thenbackend/data/models/current, thenbackend/data/models, then packaged fallbacks, and finallybackend/models. - That split is intentional so deployments can serve a promoted bundle while local training experiments stay isolated.
uvicorn backend.main:app --reload --host 127.0.0.1 --port 8000cd frontend
npm run devOpen http://localhost:3000.
The backend now boots even if models are missing or incompatible.
/health,/status/models,/schedule, and/historystill come up./predictreturns503with structured blockers when the active bundle is not ready.- This makes deployments diagnosable instead of failing hard during startup.
- Model hot-reload: the backend starts a lightweight background
model-watcherthread that monitors the active models directory and reloads promoted bundles without requiring a full process restart. This improves promotion workflows and reduces downtime. - In-process LRU cache: prediction responses are cached in-memory with TTL and max-items controlled by
PREDICT_CACHE_TTL_SECandPREDICT_CACHE_MAX_ITEMS(seebackend/services/api_runtime.py) to reduce repeated identical inference cost during heavy UI refreshes. - Model-quality pass (June 2026): the dataset builder now adds recent-form margin trend features, and the win-model training path uses balanced sample weighting to reduce imbalance sensitivity on sparse inference rows.
The backend prediction path was iterated in three passes:
- Weakpoint discovery pass: identified duplicate in-memory history growth on cache hits and non-fail-fast team-code validation during
/predict. - Mitigation pass: added bounded+deduplicated in-memory history recording to prevent repeated identical cache returns from crowding out useful recent history.
- Validation pass: added runtime-backed team code validation (dataset + team map) so invalid abbreviations fail fast with actionable error hints.
GET /schedule?season=<year>&week=<week>returns a specific slate.GET /schedule/next-weekremains the compatibility route for "next slate".- When future postseason games exist, the backend keeps showing the next playoff slate.
- During true offseason, if the next season schedule is bundled or available through
nflreadpy, the backend shows the upcoming season's earliest week instead of a stale archived slate. - If no current or future season schedule exists anywhere, the backend falls back to the latest available archived slate rather than returning an empty schedule.
The frontend sends X-User-Id, and the backend uses that to isolate prediction history.
- Primary store: SQLite-backed history and summary metrics
- Fallback: JSON ledgers under
backend/Predictions/users/<user-storage-key>/ - The current session is local-device convenience state, not real server-side authentication
frontend/src/App.jsxcreates the auth session and shared prediction state once.frontend/src/hooks/usePredictionState.jsowns schedule, health, history, summary, logos, and prediction maps.frontend/src/components/DashBoard/Dashboard.jsxconsumes that shared state instead of shadowing it locally.frontend/src/api/client.jsis the supported transport and compatibility layer for the active app shell.
GET /healthGET /health/pipelineGET /status/overviewGET /status/modelsGET /status/runtimeGET /metadata/datasetGET /metadata/model-bundle
GET /scheduleGET /schedule/next-weekGET /api/predict/next-weekGET /teams/logosPOST /predictPOST /debug/predict-input
GET /history?limit=NGET /history/summary
When ENABLE_ADMIN=true:
POST /admin/retrainPOST /admin/promote/{job_id}
repo root/
README.md Operator guide
REPO-INFO.md Durable repo map and risk notes
requirements.txt Heroku/backend production dependency surface
Procfile, Dockerfile Backend deployment entrypoints
pyproject.toml, pytest.ini Python tooling and tests
backend/
main.py FastAPI app and runtime orchestration
builddataset.py Canonical dataset build entrypoint
train_models.py Canonical training entrypoint
prediction_store.py User-scoped history persistence
sqlite_store.py SQLite-backed prediction history
app/core/settings.py Environment settings and path resolution
scripts/ Backend operations and audit scripts
data/
datasets/
latest_dataset.json
legacy/ Ignored older generated feature CSVs
runs/<timestamp>/
models/
current/
frontend/
src/
App.jsx
api/client.js
hooks/usePredictionState.js
components/DashBoard/Dashboard.jsx
components/HistoryPage.jsx
pages/StatsPage.jsx
public/
nfl_ham2.png
nfl_pic.png
docs/
DATAFLOW.md
ENVIRONMENT.md
FRONTEND_PREDICTION_FLOW.md
NFL_SCHEDULE_SCHEMAS.md
PREDICTION_INTEGRATION_PATCH.md
Two weak points were prioritized and improved over three implementation iterations:
-
CORS regex resilience and safety
- Iteration 1: detected that malformed
ALLOW_ORIGIN_REGEXvalues could override safe defaults. - Iteration 2: normalized slash-delimited env regexes and rejected known-bad overmatching patterns.
- Iteration 3: added backend tests to guarantee fallback to the canonical Vercel-origin regex in production.
- Iteration 1: detected that malformed
-
Frontend API reliability under transient failures
- Iteration 1: identified fetch calls as single-shot requests (no timeout, no retry).
- Iteration 2: added request timeout + bounded retry logic for network/transient server errors.
- Iteration 3: added client tests proving transient
503retries recover successfully.
- Environment configuration
- Frontend prediction flow
- Dataflow map
- Schedule and dataset schemas
- Prediction integration patch notes
Recommended checks after backend or frontend changes:
.venv\Scripts\python.exe -m pytest backend/tests -q
cd frontend && npm test -- --run && npm run build
python scripts/verify_api_cors.py --backend-url https://nfl-predict-ecf5a5bd34fe.herokuapp.comRuntime smoke checks:
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/health/pipeline
curl http://127.0.0.1:8000/status/overview -H "X-User-Id: analyst@example.com"
curl -X POST http://127.0.0.1:8000/predict ^
-H "Content-Type: application/json" ^
-d "{\"home_team\":\"LAC\",\"away_team\":\"ARI\",\"season\":2026,\"week\":1}"- Check
/status/modelsfor readiness blockers. - Check
/health/pipelinefor dataset hash, stale dataset, and feature-contract blockers. - Confirm
MODELS_DIRpoints at a complete bundle. - If
latest_dataset.jsonchanged after a dataset rebuild, retrain and promote a new model bundle before serving predictions. - If the bundle was trained under a different scikit-learn version, retrain or align the runtime environment.
- Confirm Vercel
VITE_API_BASE_URLpoints at the canonical Heroku backend URL. - In local dev, prefer
VITE_API_DEV=http://127.0.0.1:8000. - Older deployments may not expose
/history/summaryor queryable/schedule; the frontend now falls back, but a backend redeploy is still the clean fix.
- Inspect
backend/data/datasets/latest_dataset.json. - Override explicitly when needed:
python backend/train_models.py --data backend/data/datasets/<your_clean_dataset>.csv- Make sure
backend/data/Nfl_schedule_<upcoming-year>.csvexists once the upcoming schedule is published. SCHEDULE_PATHcan point to a preferred CSV, but the backend also scans sibling schedule CSVs so a stale explicit file does not hide a newer packaged season.- The frontend also ships fallback CSVs under
frontend/public/schedules/for compatibility with older backends.
- Curated production dataset CSVs under
backend/data/datasets/game_features_*.csvare intentionally allowed through.gitignore. - Runtime databases and local prediction history remain ignored and should not be committed.
- If a newly generated schedule CSV is production-critical, add an explicit unignore rule such as
!backend/data/Nfl_schedule_2026.csv.