Files
Northern-Thailand-Ping-Rive…/docs/DASHBOARD_REDESIGN.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

152 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dashboard redesign: the console
Status: **phase 1 done** (2026-10-19). The new dashboard is built next to the current
one and replaces it in phase 4.
- **Preview:** <https://water.buildfor.life/static/console.html>, after `git pull` on the
server (static files are read per request, no restart). It is not indexed
(`noindex`) and links back to the current dashboard.
- **Locally:** `uv run python scripts/dev_proxy.py 8765`, then
<http://localhost:8765/static/console.html>. The API is proxied to production.
- **Rules:** the same as for `dashboard.html`, plus no emoji (`tests/test_console.py`):
- every path it fetches is a Rust route;
- every string exists in EN and TH, and Thai is the default;
- timestamps are Bangkok wall-clock time.
## Why: the current page
Observed on 2026-10-19 at 1440 px and on a 390 px phone.
- **No clear first answer.** "Is Chiang Mai flooding?" is a thin green banner above
four KPI cards of equal weight. Two of them answer nothing for a resident: "Most
stressed gauge 16%" and "Chiang Mai river flow 53.9" (no unit in the number). At the
bottom there are five more (2,700,706 datapoints, 3,002 days, …).
- **The colours answer the wrong question.** Markers and river lines are coloured by
discharge class (m³/s), not by distance to the warning level, and the station list
shows discharge.
- **The map is noisy.** OSM terrain with Thai labels, numbered markers and animated
dashes. The legend covers about a third of the map, and more than half of the first
phone screen.
- **The header is busy.** It has six buttons with emoji (↻ 🌙 🔔 ▶); on a phone they
wrap into three rows.
- **Internal detail on the main page.** The model-skill table lists seven model hashes;
the history card is an empty 360 px box until a station is clicked.
- **Inconsistent styling.** A gradient background, several card and pill styles, three
greens.
## Decision
Two directions were sketched on 2026-10-19 against live data (throwaway HTML, not kept):
- **A:** a public information page.
- **B:** an operations console.
The user chose **B**.
## Layout
One screen, no page scroll on a desktop:
| Area | Content |
|---|---|
| Top bar | brand · **Chiang Mai verdict** (P.1 level + 24 h forecast, always visible, click selects P.1) · gauges reporting · highest 24 h flood chance · data age · EN/ไทย · light/dark · Get alerts |
| Left | gauge list: status bar, code, name, margin to the warning level, 72 h sparkline, level and 6 h change; filter; sort by river order, closest to the warning level, rising; status legend |
| Centre | map: Ping mainstem and tributaries, gauges coloured by status with code + level labels, layers Rain 24 h and Flood zones, one legend |
| Right | selected gauge: level, 6 h change, discharge, warning/danger level with margins, capacity; level scale with now and 24 h peak; 72 h chart with the forecast peaks; forecast table (peak, chance of warning/danger at 6/12/24 h, model, issue time, disclaimer); for P.1 the seven inundation stages and the zones toggle; data links |
| Status bar | data credits, API, source code |
**Narrower screens:**
| Width | Layout |
|---|---|
| < 1280 px | The panel moves below list and map, in columns |
| < 860 px | One column: verdict, map, list, panel |
**Behaviour:**
- Selection is in the URL (`#P.84`).
- Arrow keys move through the list.
- Data refreshes every 5 minutes.
## Where every current element goes
| Current (`dashboard.html` id) | Console | Phase |
|---|---|---|
| `flood-verdict`, `verdict-*` | Verdict in the top bar | 1 |
| `station-count`, `last-updated`, `data-age`, `live-pill` | Top-bar KPIs (reporting, data age) | 1 |
| `river-flow`, `peak-station` (P.1 flow, most stressed gauge) | Discharge and capacity in the panel; "Closest to warning level" sort | 1 |
| `station-map`, `map-legend`, `legend-toggle`, `rain-toggle`, `zones-toggle` | Map, layer buttons, one legend | 1 |
| side list, `station-search` | Gauge list with filter and sorts | 1 |
| `forecast-card`, `p1-outlook`, `p1-peak`, `p1-stages`, `forecast-grid` | Panel: forecast table for every gauge, stages for P.1 | 1 |
| `alerts-panel`, `alerts-*` | Alerts drawer | 1 |
| `refresh-button` | Removed: refresh every 5 min, age in the top bar | 1 |
| `lang-toggle`, `theme-toggle` | EN / ไทย segmented control, icon button | 1 |
| `error`, `loading` | Map overlay with retry; "Loading" in the list | 1 |
| `thaiwater-sensors`, `sensors-*` | List tab + map layer | 2 |
| custom markers | Map layer | 2 |
| `history-card`, `history-*` | Panel history with ranges | 3 |
| `skill-panel`, `skill-*` | Panel section for P.1 | 3 |
| `db-stats`, `db-*` | Status-bar popover | 3 |
| `replay-2024`, `replay-*`, demo hooks | Replay/simulation mode with banner and mode pill | 4 |
## Design rules
- **Type:** Inter (Latin) and Noto Sans Thai, self-hosted in `src/static/fonts/` (75 KB, OFL). Numbers use tabular figures.
- **Colours:** CSS variables for light and dark, with dark the default; a saved choice wins (`ping-river-theme`, shared with the current dashboard).
- **Accent:** `#1d6fd6` light, `#4c9bf0` dark.
- **Status:** colours are used only for status, each with an `-ink` variant for text (WCAG AA on white).
- **One status rule** (`statusOf`) for the map, list and panel. It is display-only; the notifier keeps its own transitions.
| Status | Rule |
|---|---|
| Normal | none of the rules below |
| Watch | within 0.5 m of the warning level, or 24 h warning chance ≥ 25 % |
| Warning | level ≥ `threshold_warning` |
| Danger | level ≥ `threshold_danger` |
| No data | no reading for 24 h |
- **City verdict:** the current dashboard's rule — flooding from P.1 ≥ 3.70 m; "likely" at danger ≥ 50 % or warning ≥ 70 %; watch at warning ≥ 20 % or level ≥ 3.20 m.
- **Data age:** "feed stale" after 3 h, except during RID's overnight pause (21:00 to 09:00 with an evening reading). The pause shows as "overnight pause", the same rule as the notifier's `FEED_NIGHT_UNTIL_HOUR`.
- **Discharge:** comes with RID's report. When the newest hour is a level-only reading, the panel shows the last RID discharge with its time ("at 14:00").
- **Rain layer:** off by default unless any station has heavy rain (> 35 mm / 24 h); an explicit choice is remembered.
- **Flood zones:** shown automatically while P.1 is at or above a stage, unless hidden by the user.
## Phases
Each phase is one commit, checked with screenshots: 1440, 1100 and 390 px; light and dark; EN and TH.
1. **Done — Console shell with live data.**
- Layout, design tokens, fonts, EN/TH.
- Verdict and KPIs, gauge list with sparklines and sorts.
- Map with status markers, rain and flood-zone layers.
- Station panel (readings, scale, 72 h chart, forecast table, P.1 stages).
- Alerts drawer (ntfy topics); URL selection; 5-minute refresh.
- `tests/test_console.py`; `dev_proxy.py` serves `src/static` with proper content types.
2. **Map and basin.**
- **Basemap:** a self-hosted Protomaps extract of the Ping basin (PMTiles in `src/static/`, labels in Thai or English following the page language, light/dark themes). This replaces the dimmed OSM raster tiles, whose baked-in labels are the noisiest part of the screen.
- **Server:** check that ServeDir answers HTTP range requests (PMTiles needs them); add range support to `dev_proxy.py`.
- **ThaiWater/HII stations:**
- A tab in the list.
- A map layer: % of bank height, `/api/hii/waterlevel/latest`, falling back to `/sensors/thaiwater`.
- **Custom markers:** `custom-markers.json`, with their flood-zone popup.
3. **Station depth.**
- **History in the panel:** range 24 h / 7 d / 30 d / 90 d / all / custom dates, level and discharge, issued 24 h forecasts overlaid, flood bands; Chart.js or SVG.
- **P.1 forecast skill** ("Is the model getting better?") as a panel section, from `/api/forecast/skill`.
- **Data statistics:** a status-bar popover, from `/api/stats`.
4. **Modes and the swap.**
- **October 2024 replay:** banner, clock, markers and verdict follow the replay; zones auto-show.
- **Demo hooks:** `?demo_level=`, `?demo_rise=`. A mode pill (LIVE / REPLAY / SIMULATION) never relabels a replay or simulation as live.
- **The swap:** `console.html` becomes `dashboard.html`; the old file and its tests go. `tests/test_console.py` absorbs the remaining checks of `tests/test_dashboard.py` (one P.1 level, SEO tags; the robots `noindex` is removed).
- **Docs:** README screenshot and the CLAUDE.md rule.
5. **Polish.**
- Phone layout pass.
- Native-speaker review of the Thai strings.
- Keyboard and screen reader pass.
- Lighthouse ≥ 90 (performance, accessibility).
- No console errors.
## Data notes from building phase 1
- **Negative levels.** P.4A (−1.28 m) and P.67 (−0.59 m) report negative levels: gauge datums, not errors. The panel's scale starts at the lowest of 0 and the station's 72 h readings.
- **P.1 discharge disagrees between endpoints.** At 15:00 on 2026-10-19, `/measurements/latest` had level 1.30 m with no discharge, while `/measurements/history` had 26.0 m³/s with no capacity. RID's 14:00 report said 53.9 m³/s. The console shows the last RID discharge. Worth checking where the 26.0 comes from.
- **P.84 spike.** A one-hour spike at 13:00 the same day: 0.73 → 1.05 → 0.78 m.