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.
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.
- 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).
- 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.
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).