AWS Builder Center

I Built Pramaan: Engineering a Pollution Complaint into Evidence

I built the product and technical foundation behind Pramaan, a pollution evidence workflow that tests sources, refuses unsupported blame, and turns uncertainty into a next measurement instead of a dead end.




I Built Pramaan: Engineering a Pollution Complaint into Evidence

When I started building Pramaan, I did not want to make another air-quality dashboard.
Dashboards are useful when the question is:
“What is the air-quality level right now?”
But a pollution complaint creates a different question:
“What should an authority do next?”
A resident may report smoke, dust, or a strong smell and name the nearest factory. The report may be genuine, but proximity is not proof. Wind may be moving in another direction. The timing may not match. The sensor may have a clock fault. Several stations may be rising together because the event is regional. Or there may simply not be enough evidence to distinguish the possible sources.
That is the problem Pramaan is designed around.

The idea: proof before blame

Pramaan (प्रमाण) is a pollution complaint evidence workflow. It tests a named source against wind, timing, sensor quality, spatial patterns, regional background, and alternative explanations. It can support a bounded association, contradict an obvious suspect, refuse attribution, recommend where the next sensor should go, and detect changes to a sealed evidence record.
The most important product decision is also the easiest one to misunderstand:
Pramaan is allowed to say “we cannot tell.”
That is not a failure state. If the evidence cannot distinguish two possible sources, the responsible output is to collect the measurement that would separate them—not to pick the nearest site.

Building around four decisions

I structured the product around four decisions an inspector, ward officer, or campus administrator might actually need to make:
  1. Which complaint or candidate should be inspected first?
  1. Which candidate can be cleared for this incident window?
  1. Where should the next sensor be placed?
  1. Can another reviewer verify that the record was not silently changed?
This framing helped keep the product practical. Every screen has to end in an action: inspect, measure, escalate, clear for this episode, or collect more evidence.

The first engineering boundary: a complaint is not a measurement

One of the first things I built was a local complaint-intake workflow. A user can record:
  • Incident type: smoke, dust, odour, or other
  • Incident date and time
  • Location description
  • Notes about what they observed
The complaint is saved locally in the browser and can be reopened, deleted, or exported as JSON.
But the product deliberately does not turn that complaint into a sensor reading or a source verdict. A new report is shown as:
text
PENDING EVIDENCE

It also shows a checklist for what should be collected next: an independent calibrated monitor, an upwind/background reference, contemporaneous wind, clock and calibration checks, and alternative-source context.
This boundary matters. A polished interface can make an unsupported conclusion look official. The system has to protect the user from that illusion.

A populated operations console without fake live data

The first versions of the prototype felt too empty. There were technically correct panels, but not enough context for an inspector to understand what to do.
I turned the interface into a local environmental operations console with an explicitly seeded  DEMO / SYNTHETIC  workspace. It contains records for:
  • A local odour episode
  • A pending dust complaint
  • A regional haze review
  • A monitor clock-drift exception
  • A sensor maintenance window
  • A report-ready packet
  • A single-sensor anomaly follow-up
  • A school-perimeter smoke report
Each record has illustrative metadata:
  • Record ID
  • Region
  • Owner role
  • Workflow stage
  • Status
  • SLA-style window
  • Evidence completeness
  • Timestamp
  • Next action
  • Review note
These records are not pretending to be live operations. They exist so a reviewer can see how a real workflow would behave when records are queued, blocked, in review, or ready for a report.
The local workflow supports assignment and status changes, persists them in browser storage, and writes best-effort audit entries. Those operational changes cannot modify the scientific evidence, grade, claim ledger, or source attribution.

The evidence engine and three contrasting cases

The prototype contains three synthetic PM2.5 cases.

Case 1: coherent local plume

Two independent downwind monitors rise in sequence while an upwind reference remains near baseline. Wind and transport timing are compatible with the candidate. The result is a bounded association rather than a causal finding.

Case 2: single-sensor anomaly

A dramatic reading comes from a monitor with an invalid calibration or clock condition. Peer monitors remain stable. Pramaan excludes the suspect reading from attribution, refuses to name a source, and recommends a better measurement.

Case 3: regional smoke front

Upwind, downwind, and regional references rise together. The local-source hypothesis is contradicted by the broader pattern. The case points toward regional investigation instead of local blame.
The tests are deliberately readable:
  • Upwind/downwind contrast
  • Wind sector and transport timing
  • Spatial coherence
  • Regional background
  • Other local sources
  • Instrument validity
Each outcome is one of  supports ,  contradicts ,  inconclusive , or  unavailable .

Building the Run Lab

To make the reasoning visible in a demo, I added a local five-stage Run Lab:
text
Intake
→ QA gate
→ Falsification
→ Cross-examination
→ Seal

The operator can start, pause, resume, reset, and re-run a synthetic case. Each stage displays outputs derived from the selected fixture. Completed runs receive a deterministic run ID and are stored in browser-local history.
The Run Lab also provides:
  • A comparison view for two cases
  • Grade and coverage differences
  • Counts of supportive, contrary, inconclusive, and unavailable tests
  • A guided judge walkthrough
  • A downloadable evidence packet
The packet includes inputs, stage outputs, test outcomes, citations, grade boundaries, an integrity result, and limitations. It is explicitly labelled:
text
DEMO / SYNTHETIC · local fixture only


Making language safer than the raw data

One technical feature I care about is the citation-enforced claim checker.
The system does not simply generate a paragraph and hope that it is accurate. A claim has:
  • A claim ID
  • A claim type
  • Evidence IDs
  • A grade ceiling
  • The text shown to the reviewer
The checker rejects:
  • Unknown evidence IDs
  • Missing citations
  • Unsupported causal language
  • Legal claims such as “guilty” or “violated”
  • Unsupported health or safety claims
  • Numbers that disagree with the fixture
  • Association claims that omit contrary evidence
The demo includes an “inject unsupported claim” action. The claim is rejected and the reason is visible.
My view is that any future language-model integration should sit behind this same deterministic gate. A model can help explain structured findings, but it should not be allowed to add facts.

Building the integrity demonstration

The browser prototype canonicalizes three local artifacts:
  •  observations.json 
  •  assessment.json 
  •  config.json 
Each artifact is hashed using SHA-256 through the Web Crypto API. The sorted artifact hashes are then combined into a manifest-style root.
The demo allows one held copy of the observations to be modified. Verification then reports that the artifact changed and the root no longer matches.
This feature forced me to be precise about the claim. A hash demonstrates byte integrity after sealing. It does not prove that a sensor was truthful, correctly calibrated, or lawfully collected. The application displays that limitation next to the verification result.

The AWS architecture I prepared

The current frontend runs locally, but I also prepared an AWS Serverless Application Model foundation for the next deployment step.
The AWS SAM template defines a path for:
text
Complaint / case input
↓
API Gateway
↓
Lambda validation and handlers
↓
DynamoDB case state
↓
S3 evidence artifacts
↓
KMS manifest signing

The stack also defines Cognito for authentication and a parameterized S3 Object Lock option for evidence retention.

AWS SAM

I used AWS SAM to keep the infrastructure reproducible and to build the Lambda-compatible backend locally. The backend includes routes for case creation, case listing, case retrieval, complaint intake, and verification metadata. It also has a local mock mode so the API shape can be exercised without credentials.

API Gateway and Lambda

The planned API uses API Gateway routes backed by a Python Lambda handler. The handler validates JSON, supports deterministic idempotency using  Idempotency-Key , and separates complaint intake from evidence-backed case analysis.

DynamoDB

DynamoDB is the intended store for case metadata and workflow state. The SAM design uses pay-per-request billing, encryption, and point-in-time recovery for a small pilot.

S3 and Object Lock

S3 is intended for versioned, private evidence snapshots. Object Lock is parameterized but defaults to off. It must be selected when a new bucket is created, after the retention and break-glass policy has been reviewed.

KMS

The infrastructure defines an asymmetric KMS key for a future signed evidence-manifest flow. The browser prototype currently uses local Web Crypto hashing; it does not claim to have a deployed KMS signature yet.

Cognito

Cognito is included as the future multi-user authentication boundary. The frontend can construct a hosted sign-in link when configured, but it does not collect passwords or store credentials.
The SAM project was validated/built locally. The AWS stack has not yet been deployed, so I am intentionally not presenting the services as live infrastructure.

The hardest bug was a trust bug

The toughest issue was not a CSS problem or a build error. It was a data-boundary bug.
At one point, selecting a normal complaint could cause the investigation view to fall back to a default synthetic case. That meant a user report could accidentally appear next to a synthetic grade or source result.
I diagnosed it by tracing the selected-record state through the Case Desk, investigation routing, and the fallback that chose a synthetic case when no matching fixture ID existed.
The fix was to make the record types explicit and route complaints to a dedicated pending-evidence view. Complaints can now be opened, assigned operational metadata, exported, and audited, but they cannot enter the synthetic Run Lab or receive a synthetic grade.
I verified the correction through the TypeScript/Vite production build and by reviewing the complaint routing and local persistence paths. A browser automation suite is still a future improvement.
This bug changed how I think about trust in environmental software: a wrong fallback can be more dangerous than a visible error.

What I learned while building it

A product feels real when it handles uncertainty

Real workflows contain blocked records, missing data, QA holds, pending reviews, and requests for more information. Adding those states made the product feel more realistic than adding another decorative visualization.

Empty states should explain the next action

An empty queue should say what is missing and how to recover. A pending complaint should say what evidence to collect. A missing coordinate should not show a fake marker.

Local-first can be a feature during a hackathon

Keeping the browser prototype usable without credentials made it possible to demonstrate the investigation, integrity, and complaint workflow reliably. The AWS boundary is prepared without making the demo dependent on an unverified cloud connection.

“Verified” needs a scope

The application uses words such as “backend response verified” and “integrity verified” only for bounded meanings. A successful HTTP response is not proof that an environmental reading is correct. A matching hash is not proof of causation.

What is built and what is next

Built locally

  • React/Vite operations console
  • Three synthetic cases
  • Complaint intake and browser persistence
  • Local queue, assignment, status, and audit workflow
  • Operations analytics and reports
  • Five-stage Run Lab
  • Evidence packet generation
  • Case comparison
  • Guided judge mode
  • Claim checker and hallucination rejection
  • SHA-256 tamper demonstration
  • AWS SAM backend and infrastructure scaffold
  • Local Python mock API checks

Not yet deployed or field-validated

  • Public application URL
  • Cognito user pool deployment
  • API Gateway deployment
  • DynamoDB production persistence
  • S3 Object Lock bucket
  • KMS signing flow
  • Live OpenAQ or Open-Meteo readings
  • Field calibration
  • Regulatory authority integration
  • Validated atmospheric dispersion solver
  • Inspector or resident pilot

My next milestone

My next milestone is a thin AWS vertical slice:
  1. Deploy the SAM stack in  us-east-1  after reviewing permissions and retention policy.
  1. Configure Cognito authentication.
  1. Create and retrieve one case through API Gateway and Lambda.
  1. Store case metadata in DynamoDB.
  1. Store a canonical evidence snapshot in S3.
  1. Sign and verify one manifest with KMS.
  1. Connect the frontend to the deployed API.
  1. Replace one synthetic fixture with a cited historical event.
  1. Run a transparent validation scorecard before making real-world claims.

Closing

The most important feature in Pramaan is not the grade. It is the ability to produce a responsible next step when the grade is not enough.
The system should be able to say:
  • Inspect this case.
  • Clear this candidate for this episode.
  • Escalate this as regional.
  • Place the next sensor here.
  • Or: we do not know yet.
That is the product I wanted to build.
Pramaan does not promise certainty. It makes uncertainty actionable—and makes unsupported blame harder.

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article