Draft Buddy uses Docker Compose as the primary local workflow for the refactored runtime boundaries:
web: FastAPI app and session managementrl: Gym environment, feature extraction, rewards, models, and trainingdata: player loading and projection generationsimulator: stateless season evaluationcore: shared draft state, controller, rules, bots, and entities
- Docker
- Docker Compose
Copy environment variables and set your active league:
cp .env.example .envLeague profiles live under config/leagues/ with per-season overlays in config/seasons/. Set these in .env:
DRAFT_BUDDY_LEAGUE=red_league_10 # ESPN Red League (10-team, full PPR)
DRAFT_BUDDY_SEASON=2026To switch to Redraft NBFL (12-team Sleeper, half PPR) later:
DRAFT_BUDDY_LEAGUE=redraft_nbfl_12Each league uses its own generated player CSV under data/leagues/{league_id}/generated/{year}/. Run docker compose run --rm data after switching leagues so projections match that league's scoring.
Build the image used by every service:
docker compose buildEach service bind-mounts the repository into /app and runs with PYTHONPATH=/app/src, so generated files are written back to your host checkout.
Common output locations on the host:
data/: generated player data and draft state filesdata/leagues/{league_id}/generated/{year}/pipeline_report.html: data-pipeline diagnostics reportlogs/: training metrics, dashboards, and run logsmodels/: checkpoints and trained model artifactscoverage.xml: XML coverage report fromtest-covhtmlcov/: HTML coverage report fromtest-cov
| Service | Purpose | Default command |
|---|---|---|
webapp |
Run the FastAPI web application | python scripts/run_webapp.py |
train |
Run RL training | python scripts/train.py |
test |
Run the test suite | python -m pytest tests/ |
test-cov |
Run tests with coverage outputs | python -m pytest tests/ --cov=src/draft_buddy ... |
data |
Generate player projections and merged draft data | python scripts/generate_projections.py --year 2026 |
insights-search |
Fetch web search snippets for top 150 ADP players (Valyu default) | python scripts/fetch_player_insight_search.py --year 2026 --top-n 150 --search-provider valyu |
insights-synthesize |
Synthesize Gemini Flash player insights from cached search | python scripts/synthesize_player_insights.py --year 2026 --top-n 150 |
position-guide |
Generate static RL position probability cheat sheet | python scripts/generate_position_guide.py --simulations 5000 |
Start the web application:
docker compose up webappIn the header, use Sim → Bot | Policy to choose the engine for Sim Pick and Auto Draft. Bot uses configured heuristic/ADP strategies; Policy uses the loaded RL checkpoint (MODEL_PATH_TO_LOAD).
Run training:
docker compose run --rm trainGenerate training plots from the latest CSV metrics without training:
docker compose run --rm train python scripts/train.py -pRun the test suite:
docker compose run --rm testRun tests with coverage:
docker compose run --rm test-covGenerate player projections with the default compose command:
docker compose run --rm dataVeteran weekly stats are downloaded from nflverse's current stats_player release as per-season files (stats_player_week_{year}.csv) into data/cache/nflverse/. The lookback window defaults to two completed seasons (Config.data.LEGACY_STATS_LOOKBACK_SEASONS); override with --lookback-seasons.
Override the data-generation command:
docker compose run --rm data python scripts/generate_projections.py --year 2024Rookie points are estimated by ADP neighbor interpolation among same-position veterans, with NFL draft-slot scaling as a fallback when ADP matching leaves gaps.
Projection generation merges FantasyPros consensus ADP from a saved HTML table (not the CSV export).
- Open FantasyPros PPR Overall ADP
- In DevTools inspector, select the ADP results
<table>whose classes includemcu-table/reports__table-inner(caption text starts withAverage Draft Position (ADP)) - Copy that table element's outer HTML only (not the full page, and not an ECR rankings table)
- Save it as
data/cache/adp/fantasypros-{year}-overall-adp-rankings.html(example:fantasypros-2026-overall-adp-rankings.html) - Run
docker compose run --rm data
Do not use the ECR / rankings page table. The ADP table must include an AVG column.
Offline player insight enrichment is a manual, two-step pipeline that prepares research-backed outlook data for the draft UI (see PLAYER_INSIGHTS_PART2_PLAN.md).
Prerequisites:
- Copy
.env.exampleto.envand set:VALYU_API_KEYfrom Valyu (default search provider)GEMINI_API_KEYfrom Google AI Studio and/orOPENROUTER_API_KEYfrom OpenRouter
- Optional: for Google CSE instead, set
INSIGHTS_SEARCH_PROVIDER=google,GOOGLE_CSE_API_KEY, andGOOGLE_CSE_ID(note: CSE is closed to new customers and sunsets Jan 2027).
Run order:
# 1. Generate player projections (if not already done)
docker compose run --rm data
# 2. Fetch and cache search snippets (Valyu default; date-filtered + relevance >= 0.7)
docker compose run --rm insights-search
# 3. Synthesize structured insights (Gemini or OpenRouter)
docker compose run --rm insights-synthesizeValyu search applies publication date windows (outlook/role: March 1 of the draft year; injury_recovery: last 90 days) and a 0.7 relevance threshold. Synthesis then re-ranks cached snippets by relevance and recency before calling the LLM.
After upgrading the insights pipeline, re-fetch search caches so results include relevance scores and date windows:
docker compose run --rm insights-search python scripts/fetch_player_insight_search.py --force
docker compose run --rm insights-synthesize python scripts/synthesize_player_insights.py --forceSynthesis model selection:
Defaults come from .env (INSIGHTS_LLM_PROVIDER, INSIGHTS_LLM_MODEL). Override per run:
docker compose run --rm insights-synthesize python scripts/synthesize_player_insights.py \
--provider openrouter --model deepseek/deepseek-v4-flashOutputs:
- Search cache:
data/cache/insights/search/{sleeper_id}/ - Synthesis cache:
data/cache/insights/synthesis/{sleeper_id}.json - Merged insights export:
data/insights/exports/player_insights_{year}_{timestamp}.json
Each synthesis run writes a new timestamped export file. The webapp loads the newest export by filename timestamp at startup. Legacy undated data/player_insights_{year}.json files are used as a fallback when no exports exist yet.
Partial re-runs:
docker compose run --rm insights-search python scripts/fetch_player_insight_search.py --max-players 20 --start-index 0
docker compose run --rm insights-search python scripts/fetch_player_insight_search.py --force
docker compose run --rm insights-search python scripts/fetch_player_insight_search.py --search-provider google --force
docker compose run --rm insights-synthesize python scripts/synthesize_player_insights.py --forceThe web UI includes an on-demand LLM draft assistant alongside the fast RL position chips. Set GEMINI_API_KEY and/or OPENROUTER_API_KEY in .env.
Supported models: Gemini 2.5 Flash, Gemini 2.5 Flash Lite, DeepSeek V4 Pro, DeepSeek V4 Flash (via OpenRouter).
In the header:
- Auto assistant — when on, fires once per snake turn when scope allows (skipped during clock overrides)
- Scope — My picks only (agent team from league config) or Every team (auto only)
- My model / Others — separate model pickers for your team vs other teams (defaults from
ADVISOR_AGENT_MODEL/ADVISOR_OTHER_TEAMS_MODEL) - Ask Assistant — always available during an active draft; uses the selected/on-clock team (including overrides)
The assistant builds per-position shortlists (top 7 by VORP/ADP for the RL model's top two positions, top 5 for the others) and returns a structured pick recommendation. Min GP Frac from the player table is sent with each request.
See PLAYER_INSIGHTS_PART2_PLAN.md for architecture details.
Offline Monte Carlo simulation produces a static position probability guide for your draft slot — useful as a fallback when the live dashboard is unavailable. Defaults (num-teams, slot, checkpoint-dir) come from the active league profile in .env.
Prerequisites:
- Generate player projections for the active league:
docker compose run --rm data - A trained policy checkpoint for that league size (paths are set in
config/seasons/{league}_{year}.json)
Run (uses active league from .env):
docker compose run --rm position-guide
open data/guides/exports/position_guide_*teams_slot*_*_*.htmlOverride league temporarily:
DRAFT_BUDDY_LEAGUE=redraft_nbfl_12 docker compose run --rm position-guideOutputs:
- JSON:
data/guides/exports/position_guide_{num_teams}teams_slot{slot}_{year}_{timestamp}.json - HTML: same basename with
.html(printable cheat sheet)
Each run writes a new timestamped export. Filenames include league size so 10-team and 12-team guides do not collide.
| League | Profile ID | Platform | Scoring | 2026 draft slot |
|---|---|---|---|---|
| Red League | red_league_10 |
ESPN | Full PPR | 2 |
| Redraft NBFL | redraft_nbfl_12 |
Sleeper | Half PPR | 5 |
Season rollover checklist (each August):
- Copy
config/seasons/{league}_2026.jsonto{league}_2027.json - Update
season,bye_weeks,draft.AGENT_START_POSITION, and checkpoint paths - Run
docker compose run --rm datafor each league you use - Train or point
training.MODEL_PATH_TO_LOADat the correctmodels/{N}_teams_*checkpoint
Player projections only include nflverse-trackable scoring rules. Bonuses without reliable stat columns (50+ yard TDs, D/ST details, IR slots) are omitted per league JSON.
Use docker compose up for long-running services that should stay attached to a port, such as webapp.
Use docker compose run --rm for one-off tasks such as training, tests, coverage, and data generation. The --rm flag removes the container when the command exits.
Because every service uses working_dir: /app, command overrides run from the repository root inside the container. That means overrides like:
docker compose run --rm test python -m pytest tests/test_config.pybehave consistently across services.
- Web UI: http://localhost:5001
- Coverage HTML report: htmlcov/index.html
- Training logs and dashboards:
logs/ - Model checkpoints:
models/
.
├── data/
├── frontend/
├── logs/
├── models/
├── scripts/
├── src/draft_buddy/
│ ├── core/
│ ├── data/
│ ├── rl/
│ ├── simulator/
│ └── web/
├── config/
│ ├── leagues/
│ └── seasons/
├── docker-compose.yml
├── Dockerfile
└── pyproject.toml
scripts/run_webapp.pyscripts/train.pyscripts/generate_projections.pyscripts/fetch_player_insight_search.pyscripts/synthesize_player_insights.pyscripts/generate_position_guide.py