Files
Northern-Thailand-Ping-Rive…/CLAUDE.md
T
grabowski 301a629b42
CI / Format & lint (push) Successful in 24s
CI / Test suite (push) Successful in 1m31s
Docs / Validate documentation (push) Successful in 10s
feat(dashboard): the console, phase 1 of the redesign (preview at /static/console.html)
The user chose direction B of the redesign: a full-screen operations console.
It is built next to the current dashboard, served by ServeDir at
/static/console.html (noindex, links back to /), and replaces dashboard.html
once it covers every feature (docs/DASHBOARD_REDESIGN.md, phase 4).

Phase 1:
- top bar: the Chiang Mai verdict (P.1 level + 24 h forecast, the current
  dashboard's rule), gauges reporting, highest 24 h flood chance, data age
  ("overnight pause" instead of "stale" during RID's nightly gap, as in the
  notifier), EN/TH, light/dark (dark default), Get alerts
- gauge list: one status rule for list, map and panel (normal, watch within
  0.5 m or >= 25 % chance, warning, danger, no data), margin to the warning
  level, 72 h sparkline, 6 h change; filter; sort by river order, closest to
  the warning level, rising; arrow keys
- map: Ping mainstem and tributaries, status markers with code + level,
  layers Rain 24 h (on by default only with heavy rain) and Flood zones
  (auto-shown while P.1 is at a stage), one legend
- station panel: level, change, discharge (last RID value when the newest
  hour is level-only), warning/danger levels with margins, capacity, level
  scale with now and 24 h peak, 72 h chart with the forecast peaks, forecast
  table with the model and issue time, the P.1 inundation stages, data links
- alerts drawer with the ntfy topics; selection in the URL (#P.84); refresh
  every 5 minutes
- Inter and Noto Sans Thai self-hosted (src/static/fonts, 75 KB, OFL), no
  emoji, Thai first, Bangkok time

tests/test_console.py: routed fetch paths, EN/TH parity, translated aria
labels, no emoji, fonts and licences, noindex. dev_proxy.py serves
src/static with real content types (it sent HTML as octet-stream).
2026-10-19 16:31:09 +07:00

4.7 KiB

CLAUDE.md

Guidance for AI coding agents working in this repository.

What this is

Flood monitoring and forecasting for the Ping River, Chiang Mai. Public dashboard and API at https://water.buildfor.life/ (never publish the server's private/mesh IP). Production: one systemd unit, ping-collector.service (the Rust binary in rust/), on a small VPS in /opt/thailand-water-monitor (a git checkout), user water-monitor; plus the monthly water-monitor-retrain.timer (Python, .venv, uv-managed). Updated by git pull, cargo build --release, sudo bash scripts/install.sh.

Rules

  • Python 3.11 only. uv sync --python 3.11; run everything as uv run ....
  • make format (black 88 / isort black profile, config in pyproject.toml; cargo fmt) before committing; CI fails on formatting. make test must stay green. Tests use synthetic data, never the network; database tests (both languages) run against a throwaway PostgreSQL schema from TEST_DATABASE_URL and skip without it.
  • PostgreSQL only. No other database backends.
  • Timestamps everywhere are Asia/Bangkok wall-clock with no offset. The dashboard parses them with parseTs() and renders with timeZone: TZ; keep it that way.
  • Model changes go through the rolling-origin harness (scripts/evaluate_variants.py) and are judged on first-alert LEAD and false alarms, not MAE. Record results, positive or negative, in docs/FLOOD_FORECASTING.md section 5. Do not change what is deployed (rise_rain / hgb-v3) without a harness result that beats it on lead.
  • train_all() must never silently produce a gauge-only (v2) model; the guard that raises RainUnavailableError stays.
  • No git add -A: zero-byte shell-accident files (#, $(wc, ...) have been committed before. Stage files by name.
  • Do not add Co-Authored-By trailers.
  • The dashboard is a single file, src/static/dashboard.html, EN + TH via the t() table: every user-visible string needs both languages. Every path it fetches must be a route in rust/collector/src/web/mod.rs (tests/test_dashboard.py checks). Its replacement, the console (src/static/console.html, preview at /static/console.html, plan in docs/DASHBOARD_REDESIGN.md), follows the same rules plus no emoji (tests/test_console.py); preview it with uv run python scripts/dev_proxy.py.
  • Rust (rust/, see rust/README.md): cargo fmt --all, cargo clippy --all-targets -- -D warnings, cargo test; minimum version 1.88 (CARGO_TARGET_DIR=target/msrv cargo +1.88 check --all-targets --locked). Read .env only through rust/collector/src/envfile.rs (python-dotenv semantics, never dotenvy: the Python tools read the same file).
  • The forecast is served by Rust (rust/forecast) from models trained in Python; src/ml/predict.py is the reference it is tested against. A change to src/ml/features.py, predict.py, the HII fill or the dam frame must be ported in the same change: regenerate rust/forecast/testdata/gen_forecast_fixtures.py and keep cargo test -p ping-forecast green. Changing thresholds, stages, leads or the notifier settings (src/ml/export.py): uv run python -m src.ml.export --config (a test fails otherwise) and rebuild the collector.
  • Notifier texts are pinned by rust/collector/testdata/notify_python_cases.json.gz; a deliberate wording change updates that fixture in the same commit.
  • API routes live in rust/collector/src/web. A route's parameters are declared once (web/params.rs); that list validates and documents (/docs). Keep response formats stable: external clients use them.
  • The Python backfill stores (src/hii_collector.py, src/rid_reservoir.py, src/ml/rain.py) write the same tables as rust/collector/src/db.rs: a table or upsert change goes into both.

Where things are

  • rust/collector (ping-collector): collection (rid.rs, hii.rs, dams.rs, rain.rs), forecast run (forecasting.rs), ntfy/Matrix (notify.rs), website/API (web/), tables (db.rs). rust/forecast: inference.
  • src/ml/: features/train/evaluate/predict/export, rain.py (Open-Meteo), dam.py, hii_rain.py. src/hii_backfill.py, src/hii_collector.py, src/rid_reservoir.py: archive backfills (scripts/backfill_*.py).
  • scripts/install.sh: install/update on the server. scripts/retrain.sh + water-monitor-retrain.timer: monthly retrain with staged promote. scripts/forecast_parity.py: Rust vs Python forecast on a live database. scripts/dev_proxy.py: serve the working-copy dashboard against the live API.
  • docs/FLOOD_FORECASTING.md is the authoritative model write-up; docs/DATA_SOURCES.md the source catalog; docs/NOTIFICATIONS.md the alerts; docs/RUST_REWRITE.md the history of the Python-to-Rust move.