The main working of the project is that give the real value of complete clean drainages and how they will help full in controlling the flood water that oftenly gather in the elite class colonies of the Lahore.

Inspiration

Lahore does not lack data. It lacks the last mile.

WASA tracks 55 "sore points" — named streets that flood after every intense spell, 110 documented incidents between 2012 and 2017. The rain gauges, the air monitors, the waste studies: all of it already exists, published, sitting in reports. What did not exist was anything that turned it into a decision for one person, on one street, in the next six hours.

I kept coming back to the same gap. On 25 June 2025 Lahore got 276 mm in 24 hours — the wettest June day on record. On 5 August 2025, ~355 mm, the heaviest single day since 1895. On 1 November 2025 the city woke up to an AQI of 2,026, the most polluted air on Earth that morning. Three different hazards, the same failure: people found out by walking into it. A shopkeeper on a flooded road did not need a city-wide rainfall figure. They needed to know about their street, and they needed to know four hours earlier.

Every app I looked at stopped at the number. "12 mm" — then what? Does Shahdara flood? Should I move the car? Is it safe to send my father to the market? A dashboard that ends in a number has handed the hard part back to the person least able to do it.

The other half of it was the crews. WASA and LWMC respond after the water arrives, because the queue is discovered rather than computed. But the rain forecast is public. The waste load is published. The service intervals are known. The order in which those drains need clearing before the rain is something you can work out in advance, and nobody was doing it. Two-sided felt obvious: the same engine that warns a resident can rank a crew's morning.

What it does

Nigran is a hyperlocal, multi-hazard decision layer for Lahore. Four hazards — flood, air, heat, and waste/drainage — across four surfaces, in one URL.

Citizen view. Pick your zone, or use your location, and you get that zone's own live temperature, humidity, rain and AQI — distinct from every other zone in the city, with no city-wide averaging to hide the local extreme. Alongside it: a flood-risk map, a plain-language "what to do now" list, a six-hour rain timeline, and a nearest-relief finder that ranks filtration plants, relief camps and hospital heat units by walking distance from your own zone. Every score opens a "Why?" drawer showing which weighted components produced it and where each one came from. No black box, nothing to take on faith.

Field Ops view. A dispatch queue for drain crews that re-ranks itself off the live rain forecast rather than sitting static. Every task carries the reason it sits where it does — fill level, hours unserved, the population behind it, and the urgency the forecast is putting on it. Crews can be assigned, and each crew's open load is tracked against its per-shift capacity. A simulation clock runs the drain model at 1440× — one real minute, one modelled day — so a nine-day refill plays out while you watch instead of over a week and a half. No rate changes between the two modes; only the clock feeding them moves. Rain and air stay on the live feed, are never accelerated, and the header says so on every view while the lapse is on.

City Overview. The operator's screen: hazard layers toggleable on the zone map, city impact counters — zones at risk, blocked drains, relief capacity — a 24-hour rain and AQI outlook, and a zone table that drills straight into the citizen view.

Complaints. A citizen board: nine everyday categories, up to three photographs, a short clip or a link to a longer video, and an optional pin rounded to about 110 m. Reports carry a real lifecycle — filed → acknowledged → in progress → resolved — where a resident claims observation ("it's fixed", "still happening") and a crew claims work, kept as an append-only event log rather than a mutable status field, so the record of how a report moved survives the move. A resident deliberately cannot acknowledge their own report: that would put a citizen's word in the shape of a municipal response. Anyone can back a neighbour's report with one tap, and a device can remove what it filed. Nothing on the board is verified, nothing on it feeds a risk score, and it says both on its face.

The thread through all four: every number is either live from a feed, cited to a published source, or explicitly labelled as this project's own modelling — and when a feed goes down, the engine renormalizes and flags the gap rather than scoring a zone safe. A missing number is honest. A fabricated one is not.

How I built it

Pure client-side React 19 + Vite + Tailwind, deployed static on Vercel. No backend required, no database required, no API keys, no cost. One URL for anyone to open.

The risk engine is deterministic, not a model. Weighted overlays over published parameters — which is the point. A score a judge can audit is worth more here than one nobody can explain.

flood    = 0.45·rain(6h) + 0.25·drain-blockage + 0.15·flood-history + 0.15·vulnerability
drainage = 0.45·worst-blockage + 0.25·capacity-deficit + 0.15·service-interval + 0.15·waste-load
priority = 0.40·blockage + 0.30·rain-urgency + 0.20·exposed-population + 0.10·staleness

Every coefficient traces to a source, and they all live in one auditable file rather than scattered through the codebase.

The data comes in three tiers, and the UI says which is which. Live and real: Open-Meteo weather and air quality, keyless, per-zone, refreshed every ten minutes, behind a status pill that always shows whether what you are looking at is live, stale, or offline. Paper-calibrated: WASA's sore-point incident record; Batool & Ch 2009 on Lahore's waste (0.84 kg per person per day, ~60% collected, ~67% of it organic — the fraction that clogs drains); Ahmad et al. 2024 for design-storm rain bands; Metson et al. 2021 for the crew-assignment formulation; Sosunova & Porras 2022 for the telemetry schema. Simulated and labelled: there is no open sensor feed for Lahore's drains and no published crew roster, so the drain fill model and the crew roster are modelled — and marked as modelled everywhere they appear, not buried in a footnote.

The discipline I held to throughout: a missing number must never read as safe. When the rain feed drops, the flood engine drops that term from both the numerator and the denominator, renormalizes over what remains, and flags the gap — it does not quietly score the zone low. A zone with no mapped drain drops its blockage terms the same way. With no live feed and no usable cache, values stay null and the UI says so. There is no synthetic snapshot. A fabricated number is worse than an absent one.

Challenges I ran into

The drain lifecycle is not a checkbox. Clearing a drain is not the end of the task — it refills, becomes work again on its own, escalates, and eventually seizes. Modelling that without a latch was the interesting part: fill is a pure function of elapsed time, so a neglected drain simply arrives at 95.00% and stops there, frozen until someone clears it, while every other drain keeps climbing on the same clock. That needed no state machine, just a ceiling. And a drain that has never been serviced is always open work whatever its fill — its calibrated seed is its documented current condition, so there is nothing for it to wait for.

Time-lapse nearly corrupted the record. Running the model at 1440× makes the app demo-able, but it also means one afternoon of clicking can fabricate what looks like years of maintenance history. Services recorded under the accelerated clock are kept and shown but flagged simulated and excluded from every count, so the numbers on screen are real services only.

Photographs leak addresses. Someone photographing a flooded street outside their own home should not be publishing their GPS coordinates with it, and they will never think to check. Photos are re-encoded in the browser — downscaled to 1400 px WebP — and because the pixels are redrawn from scratch, the EXIF block goes with them. Pins are opt-in, never automatic, snapped to three decimals (~110 m), and the server re-rounds whatever it is sent, because it cannot know the client did.

Local storage was the wrong shape for complaints. A complaint carries photo blobs and can carry a clip; localStorage holds strings in a ~5 MB origin-wide quota that one photograph would fill. Complaints went to IndexedDB; the 200-byte service records stayed in localStorage. A write that cannot be shared is kept and retried, never dropped — the report genuinely exists, it is just not shared yet.

A whole layer went untested without anyone noticing. The suite covered pure functions and source-text assertions, so a syntax error in the database module survived for weeks while everything stayed green — the app is client-first and works with no API URL configured, so a broken backend fails silently by design. Nothing in the repo surfaced the gap. That one still shapes how I think about what a green suite actually proves.

Accomplishments that I'm proud of

Every number on screen can be defended. Not one figure is invented. Each is either live from a feed, cited to a published paper, or explicitly labelled as this project's own modelling choice — and the labelling is in the UI, not just the README. The 40/80/95% service thresholds are stated as project policy precisely because no paper sets them.

The whole app works with nothing configured. No API key, no account, no cost, no infrastructure. With no backend and no live feed it degrades to a labelled snapshot mode rather than breaking or lying. That is a much harder thing to build than a happy path.

380 unit and component tests across 18 files, and 14 browser tests, all green — including the four-band service lifecycle from both sides of every threshold, the 95% freeze, the missing-data null contracts, the photo pipeline, the video-host allowlist (that javascript: and data: are refused, and that a host merely containing an allowed name does not pass), the "me too" count a double-tap cannot inflate, and the phone layout at eight widths from 320 to 1440.

The complaints board is honest about itself. It is the only place a human supplies the observation, so it is kept structurally apart from the engine — its own file, its own storage, its own rules — and it tells the user plainly that it is unverified and that it moves no score. A prototype is allowed to have a weak feature; it is not allowed to dress one up as a municipal record.

Two-sided. Almost everything in this space is a dashboard for citizens. The crew queue, the capacity-aware assignment, and the fact that the same forecast drives both views is the part I think is actually new here.

What I learned

Limits are a feature if you label them. The instinct is to hide the modelled parts. The opposite turned out to be stronger: saying "this layer is simulated, here is why, here is the schema it follows, and here is the feed it would swap to" is more credible than a number with no provenance at all.

Explainability beats accuracy when nobody can verify either. A deterministic weighted overlay with a "Why?" drawer is auditable by a stranger in thirty seconds. A model that is 4% more accurate and unexplainable is worth nothing to a judge — or to a resident deciding whether to move their car.

Designing the failure path is most of the work. Handling the API being down, the cache being stale, the drain having no telemetry, the photo being too large, the clip being the wrong format, the resident trying to acknowledge their own report — that is where the product actually got good, and none of it is visible when everything works.

Don't trust a green suite to tell you what ran. Pure functions passing says nothing about a database module that has never been loaded. Coverage of the parts that are easy to test is not coverage.

What's next for Nigran Lahore

Verify the shared service against a real database. The optional Express + Postgres layer that makes the drain log and the complaints board shared rather than per-browser is written and unit-tested at the decision-logic level, but it has never been run against a live Postgres. Until it has, I am not claiming it works.

Swap the modelled layer for real telemetry. The drain model and the crew roster are the two simulated pieces. The engine is already architected to accept live feeds in their place — when WASA or LWMC publish drain sensors, the model becomes the fallback rather than the source.

Push alerts that reach people without smartphones. SMS and WhatsApp when a zone hits severe. The app is only useful to someone who opens it, and the people most exposed to a Lahore flood are the least likely to be holding a phone with a browser open.

Hindcast validation. Run the engine against the July–August 2026 archive and score it against what actually flooded. Right now the model is defensible; it is not yet measured.

Urdu, and scale. Urdu localisation first, then expansion from 8 zones toward Lahore's ~250 union councils.

Built With

Share this project:

Updates

Submission history