feat: Mae Ngat dam features — built, evaluated, defaulted OFF
CI/CD Pipeline - Northern Thailand Ping River Monitor / Code Quality (push) Successful in 15s
Documentation / Validate Documentation (push) Failing after 8s
Documentation / Generate API Documentation (push) Successful in 9s
Documentation / Build Sphinx Documentation (push) Successful in 15s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Cleanup (push) Successful in 1s
Documentation / Documentation Summary (push) Successful in 2s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Test Suite (3.11) (push) Failing after 27s
CI/CD Pipeline - Northern Thailand Ping River Monitor / Build Docker Image (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Integration Test with Services (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Staging (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Deploy to Production (push) Skipped
CI/CD Pipeline - Northern Thailand Ping River Monitor / Performance Test (push) Skipped

src/ml/dam.py loads rid_reservoir_daily into a leakage-safe hourly frame
(daily row visible from 07:00 its own date, ffill capped at 48 h) and is
plumbed through features/train/predict/evaluate exactly like rain, gated
to the six mainstem stations below the Mae Ngat confluence.

The experiment concludes as a documented NEGATIVE result: on the 2024
record-flood backtest every dam-feature subset costs 1-3 h of first-alert
lead (13h -> 10-12h) for <=3 cm of peak-error gain, because the daily RID
report lags up to 31 h and describes yesterday's benign absorbing
reservoir during fast onset. Features therefore default OFF (--dam
opt-in on the training and backtest CLIs; rise_rain_dam/rise_dam harness
variants, excluded from the default variant set). The ablation also
isolated the HII gap-fill as lead-neutral: the acceptance gate holds at
13 h with fill enabled, and docs/img charts are regenerated with the
shipping configuration. Full table in docs/FLOOD_FORECASTING.md §5.

Review-swarm fixes: evaluate.py skips variants whose feature family is
absent instead of crashing the run; --dam forwards --db-url and warns
loudly when no dam history loads; an empty DB result can no longer wipe
a good dam cache; run-level metrics version claims v4 only when a dam
station is actually in the set.
This commit is contained in:
2026-08-13 20:42:21 +07:00
parent 6af6fbe02c
commit 28b62e5a36
12 changed files with 464 additions and 25 deletions
+43 -2
View File
@@ -451,8 +451,9 @@ acts before any gauge rises, and `rain_fc24` — a weather *forecast* — acts
before the rain itself falls. **Remaining honest limits:** marginal
just-over-threshold crests (2025: +2 h) are intrinsically short-notice; the
rain series only exists from 2021-03, so older training rows are rain-blind;
forecast-rain quality bounds what the feature can add; and Mae Ngat/Mae Kuang
dam releases remain uningested (see `docs/DATA_SOURCES.md`).
forecast-rain quality bounds what the feature can add; and Mae Ngat reservoir
state, though now ingested daily (see `docs/DATA_SOURCES.md`), measurably
*hurts* alert lead as a model feature — see the 2026-08-13 experiment below.
**Danger-level skill at P.1 is unproven.** P.1 never crossed 4.5 m in the
2025-01-01 → 2026-08-10 test span (`base_rate_danger` is 0.0, so every danger
@@ -475,6 +476,46 @@ P.1 additionally reports `stages`: exceedance probability for each of the seven
official inundation stages (3.704.60 m), computed from the regression head and
its calibration sigma, so they need no retrain and no per-stage classifiers.
### 2026-08-13: Mae Ngat dam features — a documented negative result
With `rid_reservoir_daily` backfilled to 2018 (daily Mae Ngat storage/inflow/
outflow, `src/ml/dam.py`), the obvious v4 experiment was to feed reservoir
state to the mainstem models: during the Oct 2024 flood the dam hit 113% of
usable capacity with 1922 MCM/day inflow spikes on the crossing days.
**It fails the acceptance gate.** On the 2024 record-flood backtest (train
< 1 Sep 2024, belt-and-braces alerting, identical to the deployed pipeline):
| dam features | first-alert lead | record-peak err (24 h ahead) |
|----------------------------|------------------|------------------------------|
| none (deployed v3 config) | **+13 h** (PASS) | +0.24 m |
| all four | +10 h (FAIL) | +0.22 m |
| storage % + 3-day delta | +12 h | +0.21…+0.27 m |
| inflow + outflow | +10 h (FAIL) | +0.35 m |
| outflow only | +12 h | +0.20 m |
Every subset costs 13 h of warning for at most a ~3 cm peak-error gain. The
mechanism is the publication lag: RID posts the daily report on the morning of
its own date (features apply it from 07:00, `dam.py`'s leakage rule), so at the
04:00 first-alert hour of 24 Sep 2024 the freshest dam row still described
23 Sep — a benign reservoir quietly absorbing inflow (outflow 0.13 MCM/day).
The columns therefore argue *against* imminent flooding exactly when the rain
features are (correctly) raising the alarm. The rolling-origin harness agrees:
`rise_rain_dam` matches `rise_rain` on leads and false alarms, only nudging
event-peak amplitude (0.11 → 0.03 m on the Sep 2024 event), and `rise_dam`
(dam without rain) is strictly worse with alarm-latch artifacts.
**Disposition:** dam features are OFF by default (`train_all(use_dam=False)`;
opt-in via `--dam` on the training CLI, `scripts/backtest_render.py --dam`,
and the `rise_rain_dam` / `rise_dam` harness variants). The collector keeps
accruing daily rows; revisit post-monsoon when the 2026 season adds dam-era
flood events — an intraday scrape (the lsim.rid.go.th source, reachable only
from Thai networks) would remove the publication-lag objection entirely.
**Shipped from the same work:** the HII gap-fill merge in the data loader
(`fill_from_hii`, +9,341 h at P.81, +682 h at P.92, +810 h at P.20) is
lead-neutral — the gate holds at 13 h with fill on — and ships enabled.
## 6. Deployment
### API
Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 130 KiB