The console is now the dashboard at / (src/static/dashboard.html); the old page and console.html are gone. - Replay of the October 2024 flood (flood-2024-all.json): the page's clock, verdict, list, markers, flood zones and panel follow the replay; the panel charts the season on fixed axes with the 2024 model's 24 h peak. Hourly while the model alerts or P.1 is above 3.50 m, faster through quiet days; pause and drag. Rain, basin stations and the 5-minute refresh are off during the replay (today's data). - ?demo_level= and ?demo_rise= simulate P.1. A violet REPLAY / amber SIMULATION bar, the top bar's KPI and the P.1 panel say so; the mode is drawn from state.mode alone, so a refresh or language switch cannot relabel either as live. - History: rows without discharge_percent are ThaiWater's copy of the gauge (night hours RID publishes late), with a different rating (P.1: 26 against RID's 54 m3/s); their discharge is not drawn for gauges with RID discharge. The discharge strip has headroom so its label stays clear of the line. - Swap: the old page's title, description, Open Graph and JSON-LD tags; no noindex; tests/test_dashboard.py = console tests + the old checks that still apply (one P.1 level, SEO files) + a replay/simulation labelling test. - Docs: README screenshot (docs/img/dashboard.jpg, replacing two unused images), CLAUDE.md, CONTRIBUTING.md, FLOOD_FORECASTING.md (where the model shows), DASHBOARD_REDESIGN.md (phase 4 done, mode rules). Deploy needs a restart: the server reads dashboard.html at start.
4.7 KiB
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 asuv run .... make format(black 88 / isort black profile, config in pyproject.toml; cargo fmt) before committing; CI fails on formatting.make testmust stay green. Tests use synthetic data, never the network; database tests (both languages) run against a throwaway PostgreSQL schema fromTEST_DATABASE_URLand 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 withtimeZone: 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, indocs/FLOOD_FORECASTING.mdsection 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 raisesRainUnavailableErrorstays.- 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(design and rules in docs/DASHBOARD_REDESIGN.md), EN + TH via thet()table: every user-visible string needs both languages, Thai is the default, no emoji. Every path it fetches must be a route inrust/collector/src/web/mod.rs(tests/test_dashboard.py checks). A replay or a simulation must never be shown as live data. Preview it against production withuv run python scripts/dev_proxy.py; the server reads it at start (restart). - 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.envonly throughrust/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.pyis the reference it is tested against. A change tosrc/ml/features.py,predict.py, the HII fill or the dam frame must be ported in the same change: regeneraterust/forecast/testdata/gen_forecast_fixtures.pyand keepcargo test -p ping-forecastgreen. 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 asrust/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.mdis the authoritative model write-up;docs/DATA_SOURCES.mdthe source catalog;docs/NOTIFICATIONS.mdthe alerts;docs/RUST_REWRITE.mdthe history of the Python-to-Rust move.