Smoke Scout
smog apps show a scary red number and stop there. We built two tools that answer “can I open my window?” and “where did this smoke come from?”, on AWS serverless. We traced smoke back across Punjab with wind math and NASA satellite data, then deliberately refused to blame a single farmer. Here’s how it was built and what broke.
Smoke Scout: One Cursed Folder, and the Question “Can I Open My Window?”
Build log from the AWS × WeMakeDevs Environment Hackathon. Written at 2 AM. The coffee has gone cold and I’m choosing to ignore that.
1. Breathing the Problem
If you’ve spent an October in Delhi or anywhere in North India, you know the smell before you know the number. It’s burning straw, sweet and acrid at the same time, and it gets under the doorframe like it pays rent. You wake up at 6 AM and the air has a texture. The sun is a dull orange coin behind the haze, and your phone says AQI 487 in a red so aggressive it feels personal.
And then the app leaves you there. That’s the whole product. A scary number and a colour.
Nobody in my house asks “what is the AQI?” The questions are:
Can I open the window right now?
Is it worse in the morning or the afternoon?
Where is this even coming from?
Every AQI app I tried answered none of them. They tell you the air is bad, which we had already worked out by coughing.
So Smoke Scout became two tools for two very different moods.
2. What We Built
Journey A: “When can I open my windows?”
This one is deliberately calm. No dashboards, no seventeen gauges. You open it and it answers one question.
The idea rests on something Delhi families half-know from experience: the 6 AM air is often the worst of the day. At night and in the early morning, a temperature inversion caps the boundary layer. Pollutants get trapped in a shallow, cold lid close to the ground. By midday the sun heats the surface, the boundary layer deepens, and the same pollution mass gets diluted through a much taller column of air. It’s still bad, but comparatively less bad.
So the app uses diurnal boundary-layer behaviour to say, in plain language: “Midday is your best window today. Keep things shut until about 12, then ventilate for a couple of hours.”
That’s a decision, which is a different thing from a number.
Journey B: “Trace the Smoke”
This is the nerdy one. You click a sensor spike on the map and we ask: where did this air come from?
The pipeline:
- Take the spike’s timestamp and location.
- Pull wind speed and direction history.
- Run a 2.5-hour backward kinematic drift, walking the air parcel back along the wind field.
- Project an upwind search corridor across Punjab.
- Check that corridor against NASA FIRMS VIIRS satellite thermal passes for fire detections in the window.
The output is a map of a corridor, with satellite thermal hits plotted inside it.
The decision I’m proudest of
We did not build an accusation machine.
It would have been easy, and honestly more demo-friendly, to make a big red pin that says “FARM FIRE FROM VILLAGE X CAUSED THIS.” Judges love a dramatic reveal.
It would also be scientifically garbage and ethically ugly. A 2.5-hour kinematic estimate with coarse wind data does not prove that a particular farmer burned a particular field. Crop-residue burning is a systemic problem tangled up with policy, machinery costs, and crop cycles. Pointing a smoke algorithm at individual villages is how you get harassment, not solutions.
So the UI says it plainly, and it says it on the results screen where nobody can miss it:
This traces an area, not a person, village, or field.
The corridor is deliberately fuzzy and the language is deliberately hedged. That friction is intentional.
Day Zero (a confession)
The hackathon started on thursday. We didn’t. We had mid-sems stacked back to back all week, they didn’t end until the 9th, and somewhere in the middle I got properly sick. The DTU on-site event, which I’d been looking forward to, was out too: between the chaos around town, a body that wanted to stay horizontal, and a healthy dose of anxiety, I just couldn’t be there.
So I started a day late, from my own room, with a blocked nose and a laptop. Everything below was built in the time that was left.
Smoke Scout was built by two people. My teammate Pratham Rawat wrote the entire AWS backend: Lambda, API Gateway, the S3 and DynamoDB plumbing. I led the project, designed the product, and built the frontend. I was running a fever, then a cough, and I still can’t fully breathe as I write this, which is an absurd condition to build an air quality app in. Without him this doesn’t ship.
3. The Architecture
The shape is boring on purpose. After so many hours, boring is a feature.
Browser (React 18 + Vite + TS + Leaflet)
│
AWS Amplify (build + hosting)
│
Amazon API Gateway
│
AWS Lambda (Python: drift math + ingestion)
│ │ │
S3 DynamoDB External APIs
(Punjab (incident (Open-Meteo,
GeoJSON) + event NASA FIRMS)
IDs)
Frontend: React 18, Vite, TypeScript, Leaflet. We refused the default “dark glassmorphism AI dashboard” look that every hackathon project ships. The design tokens are Midnight Green and Ivory: deep, quiet, a bit like a field notebook. When the topic is air you can’t breathe, a calm UI is part of the product.
Backend: All serverless.
- Lambda (Python) does the maths and the data ingestion. Python because the geo and numeric code is faster to write and verify there.
- S3 holds the 23-district Punjab GeoJSON boundaries. Lambda reads them for the corridor-to-district logic, so there’s no GIS server to babysit.
- DynamoDB persists incidents and event IDs, so a trace can be reopened and shared by ID.
- API Gateway is the front door.
- Amplify handles cloud builds and hosting.
Why serverless: nobody on a hackathon team wants to be the person SSH-ing into a box at hour 30. Lambda scales to zero, costs approximately nothing when idle, and doesn’t fall over if a few hundred judges click the demo link at once. I wanted to spend my sleep-deprived brain on wind vectors, not on systemctl restart.
Clean on a diagram. Four things then tried to kill us.
4. What Fought Back
Scar 1: The CORS Preflight Trap
The API worked. I tested it with curl and with a Python script. Perfect JSON, 200s everywhere.
Then I called it from the browser:
TypeError: Failed to fetch
That’s it. No status code, no body, no hint. Browsers give you a generic network error for CORS failures because security says you’re not allowed to know why.
We lost hours here. We checked headers, redeployed the Lambda, rewrote the CORS config, and doubted my own existence.
The actual culprit: API Gateway had “API Key Required” ticked on the automatic OPTIONS preflight route.
Here’s why that’s lethal. Before a browser sends a real POST with custom headers, it fires an automatic OPTIONS preflight to ask permission. That request cannot carry your API key, because the browser sends it on its own, before your code gets a say. If the OPTIONS route demands a key, the preflight gets a 403, the browser concludes the server hasn’t granted permission, and your real request is never sent.
curl doesn’t do preflights, which is why every test passed. The bug only existed in the one environment that mattered.
The fix was unticking one checkbox:
OPTIONS /trace → API Key Required: false
The lesson I’m tattooing somewhere: if curl works and the browser doesn’t, it’s almost never your backend logic. It’s the preflight. Open the Network tab, find the red OPTIONS, and start there.
Scar 2: The Ghost Folder Cloud Build
8:30 PM. Local build is green. Amplify pipeline is running. I’m feeling smug and eating a pateesa.
Then:
error TS18003: No inputs were found in config file 'tsconfig.json'.
Specified 'include' paths were '["src"]' and 'exclude' paths were '[]'.
No inputs found. In src. The folder with all my code in it.
I stared at it. I checked the config. The include path was right. The folder was right. The code was obviously there, on my laptop, compiling happily.
Here’s what it was: Git doesn’t track empty directories. Because of how I’d reorganised things, the folder structure Amplify cloned didn’t contain the files I assumed it did. The cloud build was looking at a tree that, for TypeScript’s purposes, had nothing in it. It wasn’t a build config issue at all. It was “works on my machine” in its purest form: what was on my disk and what was in the repo had quietly diverged.
The debugging that actually cracked it was unglamorous:
bash
git ls-files src | head
git status --ignored
git ls-files shows what Git is actually tracking, not what you think it’s tracking. The gap between those two things was the whole bug. Once the files were committed and pushed, the build went green.
The lesson: when the cloud build disagrees with your laptop, trust the cloud build. It’s the only one seeing what’s in the repo.
Scar #3: The Wind Direction Gotcha (the nasty one)
This is the one that scared me, because it didn’t throw an error. It produced a plausible wrong answer.
I’d written the backward-drift math. Take wind direction, compute the reverse bearing, walk the parcel back, project the corridor. Ran it on a test spike. Got a nice corridor, a clean polygon, and it looked right.
It pointed the wrong way.
The root cause is a meteorological convention that bites everyone once: wind direction is reported as the direction the wind comes from, not the direction it’s blowing to. Open-Meteo says wind_direction_10m = 270 and that means a westerly, wind coming from the west, blowing toward the east.
My code treated 270 as “heading west” and then reversed it, which meant I was marching the search corridor downwind, projecting where the smoke was going, not where it came from. For a tool whose entire job is looking upwind, that’s a fatal inversion.
The sanity check that caught it was dumb and physical. I picked a known case, a westerly wind, and asked: the smoke came from the west, so the corridor should extend west. Does it? It extended east.
The fix is one line, but you have to understand why:
python
# Open-Meteo: direction wind is coming FROM (meteorological convention).
# Source of the air is *already* that bearing. No extra reversal needed.
source_bearing = wind_from_deg % 360
# The air is travelling TOWARD (wind_from_deg + 180) % 360
The generalised lesson: in geo and physics code, the dangerous bugs are the ones that return a believable number. A crash is a gift. A wrong-but-plausible corridor on a map would have shipped, and nobody would have caught it, because who checks which way a wind arrow points at 3 AM. Write a test with a case so simple you can verify it with your own two eyes.
Scar #4: The Geo-Ethics Dilemma
This one wasn’t a bug, but it cost the most time.
Mid-build, the temptation arrived. We had a corridor. We had satellite fire detections inside it. The obvious next feature was to name the district, name the nearest village, and let the app say who did it.
Every instinct for a good demo said yes. Every instinct for honesty said no.
Here’s the technical truth that settled it:
- The drift is a 2.5-hour estimate from coarse reanalysis wind data. Real boundary-layer winds shift, shear, and recirculate.
- VIIRS thermal detections are satellite overpass snapshots, not continuous monitoring. A fire that started and ended between passes is invisible, and a hot spot that isn’t agricultural (a brick kiln, say) can show up as a thermal anomaly too.
- A corridor is hundreds of square kilometres. Plenty of fires, plenty of villages, and no way to link one smoke parcel to one field.
Claiming attribution would be overclaiming, and the downside lands on real people who farm under real economic pressure. So we built the restraint into the product: the language is hedged, the corridor stays an area, and the disclaimer sits on the result itself rather than hiding in a footer.
That became a design feature. A tool that knows what it can’t prove is more trustworthy than one that confidently points a finger.
5. Post-Mortem: What I’d Tell Another Builder
Every one of these bugs was boring. A checkbox. A folder. A convention. A line of trig. None of them was a clever algorithm problem. Hackathon war stories are usually plumbing stories wearing a cape.
The bigger takeaway is about what to build in the first place.
It’s very easy to build a hackathon project that looks impressive at a 3-minute demo: a glowing globe, a futuristic gradient, a chatbot bolted onto something. Judges have seen forty of those today.
What nobody has built is the thing a family in Dwarka actually wants at 7 AM: tell me when to open the window. That’s a smaller idea. It’s also the one that will still be useful in November when the hackathon is long forgotten and the smog isn’t.
We built Smoke Scout for ourselves, for the people breathing this every year. That kept every decision honest, especially the one about not pointing the finger.
If your project makes someone’s actual Tuesday easier, you don’t need the glowing globe.
Now if you’ll excuse me, I’m going to go open a window. Per my own app, it’s still too early. Damn.
Stack recap: React 18 · Vite · TypeScript · Leaflet · AWS Amplify · API Gateway · Lambda (Python) · S3 · DynamoDB · Open-Meteo · NASA FIRMS VIIRS
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article