# 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:** , 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 . 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.