Skip to content
Research

The models are good. The map is not.

Global flood platforms are excellent where the data is thick: dense gauge networks, LIDAR terrain, decades of insurance claims. Most of the people who drown live where none of that exists. That is the whole company.

01 · The empty quadrant

Nobody is building here.

That is the whole company. Tomorrow.io, Jupiter Intelligence, 7Analytics and Vassar Labs all win on assets we do not have and are not trying to acquire. We do not compete with them on their ground - we operate in the quadrant they skip: real-time decisions, made where the inputs are thin.

The quadrant is empty for a reason. It is unglamorous, and you cannot enter it from a desk in another country.

Positioning schematic. Placement is our own reading of public product scope, not a benchmark.
02 · What data-sparse actually means

We measured it. It is worse than the brochures suggest.

These are not estimates. Each one is a number we hit while building, on real Odisha feeds, and each one is a reason a platform designed for Rotterdam does not transfer.

0 / 12

CWC river stations that were decision-grade on day one

Not one of them could be trusted to move a score on the day we started ingesting.

0.0%

of readings at Kishan Nagar outside physical range

A platform that trusts the feed wholesale would have alerted on noise.

0

conflicting danger-level tables for the same gauge

DoWR's real danger level for Akhuapada is 18.33 m; INDOFLOODS lists 17.83, which is actually the warning level.

0 mm

what the IMD daily grid can read on a peak event day

While OpenWeather reports localised Odisha rain falling hard. No single feed is safe alone.

Three of these four look like data.

Raw gauge feed · before validation
FLATLINE

sensor stuck, months of identical values

STALL

feed frozen 84 days, last good value repeats

IMPOSSIBLE

spike to 1133 m, and minus 834 m at Jenapur

A RIVER RISINGcounts

Akhuapada 18.45 m against a danger level of 18.33

A flatline, a stall and an impossible spike all arrive as valid rows with valid timestamps. Only the fourth is a river rising.

That is why river level is gated: a station only reaches the risk path when it is marked status == live. Otherwise the briefing says the read is rainfall-only, out loud. The moat is not a model - it is the accumulated knowledge of which feed lies, and when.

03 · Decision engine, not predictor

A forecast is not a decision.

Forecasts answer what will happen. A district officer at three in the morning needs an answer to what do I do, and needs to defend that answer at nine. Those are different products.

What a forecast gives you

62%

probability of flooding, next 24 hours

  • Do I evacuate?
  • Which wards?
  • How do I justify it tomorrow?

The officer improvises. The number takes no responsibility.

What a decision engine gives you
LiveHIGHrule_score 0.71 · Cuttack · 04:12 IST

Rain 6 h

78 mm

p95 baseline

41 mm

Antecedent

3 wet d

Forecast 24 h

46 mm

Why this score
78 mm in 6 h against a p95 of 41 mm, on soil already saturated by three wet days. Forecast adds 46 mm in the next 24 h.
Closest past event
Nearest match in 653 labelled historical events, and what happened in the 48 hours after it. Officers reason by precedent, not probability.
Suggested action
Pre-position at Naraj. Alert wards 4 to 9. One line, not a plan.
What we could not see
No live gauge on this reach, so this read is rainfall-only and the score is capped accordingly. The absence is stated, not smoothed over.

Every field is something the engine already computed. None of it needs a bigger model.

04 · One engine, many hazards

The hazard changes. The shape does not.

Flood is the wedge because it is the most expensive and the most tractable. Underneath, the engine is hazard-agnostic: a baseline, an observation, a rule, a decision. Heat already runs on it, in shadow, with no way to alert.

Now · liveLive

Flood, five cities

Mature the alerting set. North Odisha scored in shadow. Close the recession gap with river gauges on the risk path.

Next · shadowShadow

Heat, same engine

Eleven locations scored against ERA5 normals, five surfaced. Stays in shadow until it has an audit a buyer can check.

DiscoveryOpen

One industry vertical

Mining first, and discovery before any build. Ten to fifteen conversations decide whether there is pull, before a line of product code.

Not in the next ninety days: cyclone, drought, ports, extra industries, or a sixth alerting city. Deliberately.

That is the argument. Everything below is the evidence for it, including the parts that went against us.

The evidence

Everything we know, including what broke.

A district cannot audit what it cannot see. Every figure here carries a label saying what kind of number it is, and the failures are given the same weight as the wins.

01 · what we published

One hundred and forty-three validated positives.

Every one of them counted toward a 99.3% detection rate, and that number led every deck we had.

02 · how they were labelled

Each one snapped to the nearest pilot city within 120 km.

In Odisha that radius crosses whole basins. Watch what it swept in.

03 · where they actually were

They belonged to other rivers.

Baitarani 79. Rushikulya 34. Brahmani 29. The engine had been scored on water we were not claiming to watch.

04 · what was left

One of 143 was Mahanadi.

We retired the number, published the audit, and put the precision problem it exposed on the public site.

Schematic. Basin courses are indicative; the event counts and distances are from the relabel audit.

05 · The audit that cost us our best number
Backtest

We audited our own 99.3% and it did not survive.

Our event labeller snapped each INDOFLOODS gauge to the nearest pilot city within 120 km. In Odisha that distance crosses whole basins. The number was mislabelled, not fabricated - but it was wrong, and we retired it from the site.

We could have quietly fixed it. Instead we published the audit, the replacement number, and the precision problem it exposed underneath.

Where our 143 validated positives actually came from

Baitarani79

Anandapur, 86.8 km from Cuttack

Rushikulya34

Purushottampur, 104.6 km, in Ganjam

Brahmani29

Jenapur, 49.4 km

Mahanadi1

Naraj - one event, out of 143

94.4%

north Odisha onsets, 102 of 108

95.1%

across all 143 events

97.5%

Baitarani

86.2%

Brahmani

What survived the relabel. Rain-only, scored against each gauge’s own ERA5 p95, negatives sampled at the same gauge. No leaked river term.

Miss

The finding nobody publishes: our precision does not exist.

The old “0% false positive rate” was measured on dry days - the one case a rainfall rule separates by construction. On heavy monsoon days at or above the gauge’s own p95 with no recorded flood, the engine fires on every single one: 647 of 647. Sweeping 0.5 to 8 times p95, precision never exceeds 24.2% against a 20% base rate.

And the reason is interesting. North Odisha flood days have a median of 1.94 times p95. Non-flood heavy days have 2.03 - the days without floods had more rain. Either rainfall genuinely cannot discriminate and we need river level as a feature, or the flood record is not exhaustive and we need river level as a label. Both answers point the same way.

What we have not solved, as of August 2026

  • The trust gate is not passed

    Fourteen consecutive dry days with zero false HIGH. Minimum streak is currently zero, blocked on Sambalpur and Rourkela. And the gate as written tests only dry days - the one case the engine never fails. It needs rewriting.

    Open
  • Precision is unmeasurable until rivers land

    We cannot separate "the engine over-fires" from "the flood record is incomplete" without river level as a feature or a label. Real telemetry is now in production, so this becomes answerable this season.

    Open
  • The ML model has never driven an alert

    XGBoost v2 runs on every cycle and is logged. Offline it reads 91% leave-one-region-out and 96% event holdout. It stays in shadow until monsoon validation - offline metrics are not permission to go live.

    Shadow
  • Heat has a dataset, not a model

    About 82k leakage-audited rows under ml/heatwave/, of which roughly 23.7k are forecast-backed. No model has been trained. HeatBench v0 exists; the IMD grid cross-check sits at roughly 62% within 1.5 degrees of ERA5.

    Shadow

How to read every number on this site

Live

In production, driving user-facing alerts.

Backtest

Measured on history. Never quoted as live accuracy.

Shadow

Computed and published, structurally unable to alert.

Miss

The engine was wrong, and here is the write-up.

Open

A known gap we have not closed.

If we cannot label it, we do not make the claim.

Rainfall, river stage, and what the engine saidBacktest · forecast replayed as issued
06 · Fixing it: real river telemetry

We deleted the constants and went to the source.

The engine used to carry hardcoded river levels. They are gone. CWC hourly telemetry now arrives from the NWDP open API (46 gauges across Mahanadi, Brahmani and Baitarani), and the daily DoWR flood bulletin is parsed for 28 more.

What the feed actually looked like

  • Out-of-range at Kishan Nagar, including a spike to 1133 m and minus 834 m at Jenapur34.6%
  • Sensors flatlined: Akhuapada, Champua, Pamposh, Nimapara, Seorinarayan5 stuck
  • Brahmani and Baitarani feed, stalled since 3 June84 days
  • Real but stale: Sambalpur, Hirakud, Basantpur, Khairmal38–79 h

So the ingest ships with a QC layer it turned out to need badly: physical-range filter, stuck-sensor detection over a 500-reading window, and a datum check that strips a published danger level when it does not share the station’s datum. A level counts toward a score only when the gauge is genuinely live.

Backtest

And it would have worked

10 of 12 location-days in the 19–21 August recession window that rainfall scored LOW would have been rescued by the river hold. On 19 August, Akhuapada was literally above its danger level, 18.45 m against 18.33, while the rainfall engine said 0.20 LOW.

Live

Live in production

Sambalpur now reads 181.68 m from Jamadarpali. Cuttack 131.22 from Kishan Nagar, Puri 3.22 from Nimapara, Rourkela 171.4 from Pamposh, all correctly marked unavailable and contributing nothing. Bhubaneswar has no CWC station at all, so it now shows no river level rather than a frozen constant.

07 · What the engine is built from

Public and academic data, named, and filtered before it counts.

Nothing reaches a score straight from a feed. Every reading passes four gates, and a station that fails any of them contributes nothing rather than contributing noise.

INDOFLOODS

653 labelled historical flood events. Kuntla & Saharia, BAMS 2025.

IFI

India Flood Inventory. Saharia et al.

ERA5

1991–2020 climatology. Each location's own wet-day p95, and the heat baseline.

IMD

Daily gridded rainfall and Odisha district normals.

Odisha AWS

State automatic weather stations, ingested twice daily.

CWC / NWDP

Hourly river telemetry, 46 gauges, open-licensed.

DoWR Odisha

Daily flood bulletin: 28 gauges with danger, warning and historic high, plus reservoir gates and discharge.

NRSC · OpenWeather

Satellite baselines and live forecast. Derived risk product; no raw data resale.

Derived risk product; no raw data resale. Advisory only.

The honest position, August 2026

Every claim above is labelled. If we cannot label it, we do not make it.

The flood engine alerts on five cities. The ML runs in shadow and cannot alter a user-facing score. Heat has no live mode in the code, not a switch we are choosing not to flip. There is no signed MoU and no paying customer.