Spaces:
Sleeping
Sleeping
File size: 6,919 Bytes
64d8003 c65b8bc 2a534fa c65b8bc 2a534fa a1f52bf 2a534fa a1f52bf 2a534fa a1f52bf 2a534fa a1f52bf 2dfa899 d4c9a3c c5672b8 2dfa899 c5672b8 2dfa899 c5672b8 2dfa899 c65b8bc 2a534fa c65b8bc a1f52bf 2a534fa a1f52bf 2a534fa c65b8bc 2a534fa c65b8bc 2a534fa 4488687 2a534fa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | # 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.
|