Restructures so one HF Space serves both the API and the frontend
on a single port β chosen over the spec's split (HF + Cloudflare)
because for a hackathon demo a single URL is what gets pasted into
Kaggle / the video, and the CDN advantage is meaningless for a few
judges.
Mechanics:
- index.html β backend/static/index.html (git mv, no copy)
- FastAPI mounts static/ and serves index.html at GET /. The
api/* routes are registered first so they always win.
- Dockerfile COPYs static/ alongside app/.
- Frontend API_URL resolver: same-origin by default; falls back to
http://127.0.0.1:8000 only when the page is served on a non-8000
localhost port (e.g. python -m http.server 5173). The window.
FLUTIQ_API and ?api=... overrides still work for split deploys.
Local dev simplified β single uvicorn now serves everything:
cd backend && .venv/bin/uvicorn app.main:app --reload --port 8000
open http://127.0.0.1:8000
Verified end-to-end: GET / returns 74KB of HTML (the frontend),
GETβ¦
Stands up the FastAPI backend with one agent fully wired through the
SSE pipe so the architecture is proven before we fan out the rest:
POST /api/assess β geocode β FEMA NFHL β Gemma 4 interpret
β agent_update events β complete{dossier}
Verified end-to-end against two cases:
- Houston Allen Pkwy (Zone AE) β is_sfha=true, requires_insurance=true
- Chicago S Drexel (Zone X) β gap_warning generated unprompted,
surfacing the urban-sewer-backup blind spot that is the whole pitch
Smoke-tested Gemma 4 on OpenRouter free tier (scripts/smoke_test.py):
basic completion, reasoning mode, and OpenAI-format tool calls all
pass on google/gemma-4-31b-it:free.
Spec bugs fixed while building:
- llm/client.extract_reasoning read message.reasoning_details[i].content;
the actual key is .text. Also falls back to top-level .reasoning string.
- tools/fema requested VERSION_ID, which does not exist on NFHL layer 28.
ArcGIS returned HTTP 200 with an {"error": ...} bβ¦