wavereader / AGENTS.md
lucashudsn's picture
deploy: auto-deploy main to the Space via Actions; script ships any branch
d4c9a3c
|
Raw History Blame Contribute Delete
6.92 kB

A newer version of the Gradio SDK is available: 6.31.0

Upgrade

AGENTS.md

Guidance for AI coding agents working in this repo. The active surface is app.py + ui/ + wavereader/ + data/ (the v2 rebuild). legacy/ holds the frozen v1 front end (main.py + app/) β€” don't extend it; it survives only as the fallback layer in ui/_compat.py.

What is live now

  • app.py β€” v2 entrypoint: build() + gr.api tools + demo.launch(css=APP_CSS, mcp_server=True). Run uv run python app.py.
  • ui/ β€” the single-page front end (no tabs): app.py (layout + wiring, ≀3 gr.State; Bells Beach auto-selected on load so there are no empty states), panels/{lens,story,intel,agent}.py (map-lens column | forecast story | intel rail | full-width agent bar), charts/{strip,score,seafloor,climate,map,_style}.py, contracts.py (SELECT_KEYS/FETCH_KEYS/AGENT_KEYS + fill() β€” handlers return via fill(KEYS, …) so output ordering can't drift), _compat.py (backend access: v2 core β†’ legacy.app.* β†’ stubs; also wetsuit hints, engine chips, surf cams), theme.py (APP_CSS, IBM Plex Mono lo-fi identity β€” don't restyle without asking).
  • wavereader/ β€” pure core, typed + tested, no Gradio imports: breaks.py (catalogue, memoized load_breaks), openmeteo.py (versioned/atomic disk cache, seaward grid-snap), scoring.py (deterministic 0–10 engine β€” the LLM never owns numbers), seafloor.py (GEBCO world model), climate.py (ERA5 5-yr profiles + audit; break_climate reads the profile embedded on each break record, per-slug file fallback), tools.py (the typed tool functions shared by agent + MCP), agent.py (smolagents ToolCallingAgent, native tool calls, budget: 6 steps / 2 score_week / 700 tokens), narrate.py + llm.py (one InferenceClient factory: Nemotron 3 Ultra via deepinfra, WR_* env overrides).
  • scripts/warm_caches.py β€” pre-fill forecast + seafloor caches (demo hot start). scripts/build_climate.py β€” climatology builder (re-embeds built profiles into the catalogue after each run, backup first).
  • scripts/check_coords.py + scripts/check_coast.py β€” break-coordinate maintenance: check_coords validates/fixes state/region/duplicate placement against OSM; check_coast audits GEBCO elevation at every break point and (--fix) snaps non-coastal ones to the nearest 0 m shoreline crossing (both write a timestamped backup first).
  • data/ β€” australia-surf-breaks-enriched.json (238 breaks; each record embeds its ERA5 profile under climate β€” 236 of 238, the two duplicate Gnaraloo rows have no profile), surf-break-schema.json (contract), climate/*.json (per-slug ERA5 profiles β€” build_climate.py output and break_climate's fallback; also index.json manifest), surf-cams.json (camera links keyed "name | state | region"), surf-break-example-bells.json (prompt worked example).

Deploying to HF Spaces

  • The Space lucashudsn/wavereader (public) is this repo β€” remote space. Every push to main auto-deploys via .github/workflows/deploy-space.yml (needs the GitHub repo secret HF_TOKEN) β€” merging to main ships it; no manual step. For any other branch, ship manually with scripts/deploy_space.sh [ref] (builds a throwaway commit that prepends space-config.yaml to README.md and force-pushes it to space:main β€” Spaces need YAML frontmatter, GitHub doesn't have to see it; the ref defaults to your current branch), then git push origin <branch> to keep GitHub in sync. The Space builds from the pushed commit; watch Logs β†’ Build on the Space page or poll HfApi().get_space_runtime("lucashudsn/wavereader") until RUNNING.
  • space-config.yaml is the Space config. Keep sdk_version in lockstep with the gradio pin in requirements.txt (regenerate that file only via uv export --no-hashes --format requirements-txt --no-dev -o requirements.txt β€” other invocations can leave stray progress lines that break pip), and python_version in lockstep with pyproject.toml's requires-python. If you change Space settings via the web UI, HF rewrites the Space's README.md β€” mirror anything you care about back into the yaml.
  • HF_TOKEN is a Space secret (set once via Settings β†’ Variables and secrets or HfApi().add_space_secret); WR_* overrides can be added the same way. .cache/ is ephemeral on the Space and rebuilds lazily β€” no persistent storage, no warm step.

Conventions that matter

  • One spot drives everything: selected (gr.State) is the single source of truth β€” picking a spot re-aims the map, badges, forecast strip, world model and climatology together. Keep it that way.
  • Staged spin-ups: ui/panels/story.py::fetch_forecast is a generator that yields stage 1 (charts, ~ms) then stage 2 (seafloor world model from a parallel thread). Status lines name the engine + latency. Keep that pattern for anything slow.
  • Charts: theme through ui/charts/_style.py::style_fig; weekend shading via add_weekend_shading. The week strip lives in charts/strip.py (score/swell/wind rows); window slicing (weekend/mornings/arvos) re-charts from state via filter_hours β€” no refetch. Seafloor display is bicubically smoothed (scipy) and its wave animation is period-driven β€” smoothing and animation are display-only; stats stay on the raw grid.
  • MCP surface = exactly score_week / rank_region_week / explain_score (gr.api in app.py). All event listeners are registered with api_name=False β€” keep it that way.
  • The LLM never owns numbers. Scores/forecasts/analysis stay deterministic; the model narrates and cites tools.

Data contracts

  • Required break fields: name, state, region, location, skillLevel, breakType, peakType, idealSwell, idealWind, idealTide. Optional: description, id, bestSeason, hazards, crowdFactor. Never invent enum values β€” check data/surf-break-schema.json first.
  • Canonical state/region always come from the input list, never the model. location.country is "Australia".
  • New prompts must load schema + example from data/ (don't paste copies).

Rules for edits

  1. Everything is tracked in this repo (no nested git β€” don't re-init one).
  2. Never commit .env, .venv/, __pycache__/, .DS_Store, .cache/ (all ignored).
  3. Never read .env or print secrets/tokens. Use .env.example for var names; check presence with printenv HF_TOKEN | wc -c or [ -n "$HF_TOKEN" ] style checks that never echo the value.
  4. Keep prompt files in data/; keep app.py free of embedded schema text.
  5. Test cheaply: uv run pytest -q (offline fixtures) + uv run python -c "import app" for import health. Live LLM calls cost inference β€” dry-run the agent via the injected fake model (tests/test_agent_dryrun.py) before any live turn.