FastAPI app, PDF internals, API routes, and dev server. Load whenever the task involves the web layer — exploring routes, planning API changes, reading PDF handling code, implementing endpoints, or setting up the environment.
See docs/web.md for production deployment, container usage, and API endpoint reference.
Start as a background task with sandbox disabled (file watcher and loopback networking are sandbox-blocked):
# Start (run_in_background: true, dangerouslyDisableSandbox: true)
uv run fastapi dev src/ojhunt/web/app.py --port 8080
curl to localhost also requires dangerouslyDisableSandbox: truelsof -ti :8080 | xargs kill -9 (no sandbox bypass needed)extract_data(pdf_bytes) returns — has and only.
It does have a ; the snapshot is never embedded in the PDF. Build one
manually from history if needed.PdfEmbeddedDatasettingshistorysnapshotapplication/pdf on success and HTML on error: return explicit
Response(content=..., media_type="application/pdf") or HTMLResponse(...) — do not use
response_class=HTMLResponse on the decorator.llms.txt.| Variable | Required | Description |
|---|---|---|
LOGIN_USERNAME__<CRAWLER> | For shared-account crawlers | Auth username (uppercase crawler name) |
LOGIN_PASSWORD__<CRAWLER> | For shared-account crawlers | Auth password (uppercase crawler name) |
BUILD_TIME | No | Build timestamp (Unix epoch or ISO), shown on About page |
GIT_COMMIT_SHA | No | Git commit hash, used for source code link on About page |
Credentials go in .env (gitignored) — loaded automatically by load_dotenv() in
src/ojhunt/web/app.py. No need to source .env manually.
Keep JavaScript minimal. Business logic belongs in Python; JS handles only browser-specific concerns.
JS is appropriate for: reading local files, computing timezone name via
Intl.DateTimeFormat().resolvedOptions().timeZone, triggering downloads, reactive UI state.
When StaticFiles is mounted at "/", FastAPI's redirect_slashes is suppressed.
Mount("/") matches every path first and returns 404 for paths that aren't real files —
the redirect never fires.
fetch() URLs in app.js must exactly match the route path in api.py (no trailing
slashes unless the route has one)href attributes are harmless (browsers follow 307 redirects), but fetch() calls
can silently fail because StaticFiles returns a non-JSON 404 body that breaks response.json()When writing UI copy that refers to "the old site":
github.com/Liu233w/acm-statistics deployment (also known as ACM Statistics,
OJ Analyzer, OJHunt)legacy.db preserves history up to 2025-10-22; web + CLI export available