7 Commits
Author SHA1 Message Date
grabowski a610abcdb8 feat(dashboard): CMU's 2024 flood marks as a map layer
CI / Format & lint (push) Successful in 44s
CI / Test suite (push) Successful in 49s
Docs / Validate documentation (push) Successful in 18s
CMU Water Center marked the October 2024 high-water line at ~4,900 places in
Chiang Mai and Lamphun (depth above the road while P.1 stood at 5.30 m).

- src/static/flood-marks-2024.json (position and depth, 110 KB), built by
  scripts/build_flood_marks.py from CMU's GeoJSON.
- "Flood marks 2024" layer: canvas dots from zoom 12 in CMU's five depth
  classes, legend row, CMU credit; turning it on below zoom 12 flies to the
  city. A mark's popup gives the depth and its flood zone.
- Zone and marker popups name the nearest mark within 250 m (loaded with the
  zones).
- No depth estimate for other P.1 levels: the marks do not scale one-to-one
  with P.1 (marks in the 5.30 m zone stood a median 65 cm deep in 2024).
- The layer bar wraps on narrow maps; popups pan clear of it.
- Docs: DATA_SOURCES 5.1, DASHBOARD_REDESIGN, README.
2026-10-26 14:30:35 +07:00
grabowski 701bb87d34 feat(p1): city flood stages and zones from CMU Water Center's 2024 flood map
CI / Format & lint (push) Successful in 49s
CI / Test suite (push) Successful in 59s
Docs / Validate documentation (push) Successful in 27s
Security / Dependency vulnerabilities (push) Successful in 1m5s
Security / Static analysis (push) Successful in 17s
Security / License report (push) Successful in 45s
Rust / fmt, clippy, test, build (push) Successful in 3m42s
CMU Water Center's Chiang Mai flood hazard map (CMFlood) replaces the older
municipal map. It is built on the October 2024 flood and has the Ping leave
its banks above P.1 4.20 m, with five stages: 4.30, 4.50, 4.70, 5.00 and
5.30 m (the 2024 record). The old map's seven stages (3.70-4.60 m) said the
city floods from 3.70 m; CMU calls that map out of date.

- P1_FLOOD_STAGES: the five CMU stages, discharge on RID's 2024 rating.
  /forecast's P.1 `stages` now has five entries (same format). Thresholds
  are unchanged: 4.20 m danger is the bank top, 3.70 m warning half a metre
  below (as on CMU's gauge page). flood_config.json and the Rust parity
  fixtures regenerated (only the stages changed).
- flood-zones-p1.geojson: CMU's KML (L1..L5) as five disjoint zones, zone k
  floods first at stage k; scripts/build_flood_zones.py rebuilds it.
- Dashboard: CMU's colours and credit (map attribution); the stage grid follows
  the number of stages; zone lookup for markers honours holes. The city
  verdict uses statusOf's classes: flooding (over the banks) from 4.20 m,
  high water from 3.70 m. Zones show by themselves when the river is over its
  banks or stage 1 is >= 50 % likely. Alert, replay and verdict texts no
  longer call 3.70 m city flooding (EN + TH).
- Docs: DATA_SOURCES 5.1 (CMFlood: the map, flood marks, poles, city gauges),
  FLOOD_FORECASTING, DASHBOARD_REDESIGN, NOTIFICATIONS, README credits.
2026-10-26 14:19:52 +07:00
grabowski 180d95c6bd fix(forecast): no nightly gaps in the issued model forecasts
CI / Format & lint (push) Successful in 28s
CI / Test suite (push) Successful in 1m4s
Docs / Validate documentation (push) Successful in 17s
Rust / fmt, clippy, test, build (push) Successful in 13m1s
The P.1 chart's "24 h peak, as issued" line had a gap every night and a
dip at 21:00. Two causes:

- A run forecasts from the newest reading only. RID publishes 22:00..06:00
  in one batch in the morning (and now and then one hour late), so those
  hours were never forecast from. After each run the collector now
  forecasts the hours of the last 48 h (two weeks on the first run after a
  start) that have a RID reading but no model forecast, each as a run at
  that hour would have: gauges, HII fill and dam state up to that hour.
  Rain is fetched once, so a late hour's rain_fc24 partly holds rain that
  has since fallen (as in training). Only missing (hour, station) pairs are
  written. `ping-collector catch-up-forecasts --hours N` goes further back.
- Overnight each hourly run re-issues the 21:00 reading, after 6 h as the
  heuristic, which overwrote the 21:00 model row. A heuristic row no longer
  replaces a model row in forecast_history.

The dashboard draws only model rows on the issued line.

Checked on a local copy of 18 days of RID data: the catch-up of an hour
matches `ping-collector forecast --now <hour>`; 288 hours took 20 s
(debug build). Tests for both database rules.
2026-10-20 15:21:48 +07:00
grabowski cf10f2e4c8 feat(dashboard): console phase 4 - 2024 replay, simulation, replaces the old page
CI / Format & lint (push) Successful in 26s
CI / Test suite (push) Successful in 1m15s
Docs / Validate documentation (push) Successful in 12s
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.
2026-10-20 13:47:15 +07:00
grabowski ffd3030352 feat(dashboard): console phase 3 - history, forecast accuracy, data statistics
CI / Format & lint (push) Successful in 25s
CI / Test suite (push) Successful in 58s
Docs / Validate documentation (push) Successful in 11s
- History in the station panel (inline SVG): 3 d / 7 d / 30 d / 90 d / 1 y /
  all or a date range; level and discharge, the 24 h peaks as the model
  issued them, warning/danger bands, hover/touch values. Up to 7 d the chart
  ends in the current forecast. Above 2,500 readings it shows the daily mean
  with the daily low-high band, so a flood peak is not averaged away; gaps
  (RID's nightly pause) break the line instead of being bridged. Days are
  Bangkok days computed arithmetically: Intl per reading took a minute for
  "All" (67,000 rows), now 2 s.
- "How accurate is the forecast?" for P.1 (/api/forecast/skill), loaded when
  opened because a cold skill query takes up to 20 s.
- Data statistics (/api/stats) behind "2.7M readings since 2018" in the
  status bar.

fix(dashboard): "All time" history ended in October 2024. The history route
returns the oldest rows first and stops at 50,000 by default; P.1 has about
67,000 hours. Both dashboards now ask for limit=100000 (the route's maximum).
2026-10-19 17:12:39 +07:00
grabowski 71cc1b0ca7 feat(dashboard): console phase 2 - self-hosted basemap, basin stations, markers
CI / Format & lint (push) Successful in 24s
CI / Test suite (push) Successful in 1m9s
Docs / Validate documentation (push) Successful in 14s
- Basemap: src/static/basemap/ping-basin.pmtiles, a Protomaps extract of the
  Ping basin (zoom 0-12, 15 MB, OpenStreetMap data), drawn by
  protomaps-leaflet 5.1.0 (unpkg, SRI) in its light/dark style with labels in
  the page language. It replaces the dimmed OSM raster tiles, whose baked-in
  labels were the noisiest part of the screen; OSM stays as the fallback.
  The browser reads the file with range requests (ServeDir answers 206;
  dev_proxy.py now does too). The map stays within the basin.
- Basin stations: a second list next to the RID gauges and a map layer of
  diamonds, from /api/hii/waterlevel/latest (falling back to
  /sensors/thaiwater), coloured by ThaiWater's bank-height classes (over the
  bank = danger, 70-100 % = watch). Gauges RID already lists appear once.
  The panel shows level (m MSL), % of bank height, distance to and height of
  the bank top, gauge reading and agency; selection in the URL (#hii-<id>).
- custom-markers.json as pins from zoom 12, with the flood zone they are in.

Refresh instructions for the basemap in src/static/basemap/README.md.
2026-10-19 16:58:21 +07:00
grabowski 301a629b42 feat(dashboard): the console, phase 1 of the redesign (preview at /static/console.html)
CI / Format & lint (push) Successful in 24s
CI / Test suite (push) Successful in 1m31s
Docs / Validate documentation (push) Successful in 10s
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