BACKEND ARCHITECTURE
main.pyFastAPI app, CORS middleware, mounts both routers
cli.py · the ldt commandsetup_logging(), then dispatch; unknown command exits 1
api/
routes.pyGET /api/championsPOST /api/recommendplain def handlers(blocking psycopg)500 if scoring raises
schemas.pyPydantic models:DraftStateRequest,ChampionInfo,RecommendationResponse
pipeline/
update_ladder.pyupdate-challengers: replace the Challenger player list
ingest_and_process_matches.pyrun-match-sync: per player, fetch → aggregate → flush in one transaction
patch_maintenance.pydaily-maintenance: wipe data on a new patch, refresh champions
services/draft_service.pyrecommend_champions():candidates ≥ 20% role share,score = mean pair win rate(else role win rate)
domain/draft_state.pyDraftState (position,banned, allies, enemies)Position enum
db/
queries.pyevery read, upsert and delete
schema.pyCREATE TABLE IF NOT EXISTS × 6
connection.py
db_connection(): pooled psycopg (1–4 conns), commit or roll back
external/riot_api.pyRiot API: 20 req/s + 100 req/120 s limiter,429 → Retry-After or backoff, up to 3 triesData Dragon: plain requests, no key, no limiter
Neon Postgresmetadata · champions · challenger_playersmatches · champion_role_statschampion_relationships
Riot APILEAGUE-V4 + MATCH-V5
Data Dragon CDNversions + champion.json
core/config.py: .env → RIOT_API_KEY, DATABASE_URL, FRONTEND_ORIGINS; na1 / americas, queue 420 · logging_config.py · imported by main, cli, connection, riot_api
request path · Cloud Run
batch path · GitHub Actions
include_router
dispatch subcommand
init-db
recommend_champions()
champion_dictionary_from_db()
candidates, win rates, relationships
queries.py (all three)
db_connection() (ingest, patch)
all three
HTTPS
HTTPS