AWS Builder Center

Beyond "AI Confidence": Architecting a Zero-Hallucination Flood Hazard Engine on AWS Serverless

When monsoon waters drown underpasses, uncalibrated AI scores can cost lives. Here is how we engineered an idempotent, dual-clock evidence state machine with DynamoDB and Amazon Bedrock for Delhi's flood navigation.

Author: Adhyan Saxena
Team: Kernel
Team Members: Utkarsh Singh, Pranav Pant, Granth Chhabra, Adhyan Saxena
Event: Bharat Builds Tour — Event 02: Environmental Hacks
Organizers: WeMakeDevs × AWS
Track: Track 02 — Heat and Water

The Siren Call of the "85% Flood Probability"

During a severe Delhi monsoon downpour, an underpass like Minto Bridge or Pul Prahladpur does not slowly deteriorate over hours. It floods in fifteen minutes.
When teams build disaster-response or flood navigation prototypes at hackathons, there is a recurring architectural trap. You have probably seen or even designed an arbitrary weighting formula like this:
Risk Score = (0.35 × Rain Forecast) + (0.30 × Camera Detection) + (0.20 × Traffic Speed) + (0.15 × LLM Confidence)
On a pitch slide, this looks like sophisticated data science. In production, it is dangerous:
  • Weather station data is coarse: A cloudburst radar reading cannot tell you whether a specific local stormwater drain is clogged with silt.
  • Traffic slowdown is ambiguous: Vehicles crawl in Delhi for countless reasons—stalled buses, red lights, stray cattle—not necessarily deep water.
  • LLMs parse grammar, not reality: A foundation model parsing Hinglish with "99% confidence" only understands syntax; it has zero knowledge of physical ground truth.
  • Arbitrary scalars create false security: Blending uncalibrated signals produces a fuzzy number that lures commuters into entering a submerged underpass because the screen showed an unverified "Moderate 60% Caution."
When our team built DilliJalNav Edge for the AWS Environmental Hacks hackathon, we discarded probabilistic guesswork.
When a commuter's vehicle and safety are on the line, you need a deterministic, evidence-aware state engine where every transition is auditable, duplicate-resistant, and fail-closed.
Here is how we built that backbone using AWS Lambda, Amazon DynamoDB atomic transactions, and Amazon Bedrock.



1. The Core Paradigm: Separation of Concerns

The primary design principle of DilliJalNav Edge is that observations, linguistic extractions, aggregate hazard states, and routing decisions must never share the same data model or confidence value.
Plain text
[Raw Commuter Report] ──► Observation (Untrusted Claim)
│
[Amazon Bedrock] ──► Extraction (Structured Interpretation)
│
[Deterministic Rules] ──► Hazard State (Monotonic Revision in DynamoDB)
│
[Road Graph Engine] ──► Route Decision (Fail-Closed Geometry Filter)
  1. Observation (Untrusted Claim): An immutable record of what an actor submitted ( WATER_OBSERVED ,  NO_WATER_OBSERVED , or  UNSURE ), carrying a client timestamp and a trusted server receipt timestamp.
  1. Extraction (Linguistic Interpretation): Amazon Bedrock converts messy human sentences into typed candidate fields, but possesses zero authority to alter road status.
  1. Hazard State (Deterministic Aggregation): A rule engine recalculates whether verified evidence satisfies temporal and multi-source corroboration criteria.
  1. Route Decision (Graph Exclusion): A graph validator tests road segments against active restrictions and either selects a safe detour or explicitly fails closed.



2. The Flaky 4G Problem: Distributed Idempotency

Delhi monsoons bring severe cellular connectivity drops. A commuter stopped before a flooded tunnel who taps "Report Waterlogging" frequently suffers dropped packets, causing background retries or impatient repeated button taps.
If an API simply increments a counter ( reports += 1 ), three retried TCP connections from one rider will falsely escalate an isolated sighting into an authoritative multi-person hazard alert.
To prevent this, our AWS Lambda intake pipeline enforces strict Actor-Event Idempotency before triggering state evaluation:
Plain text
import hashlib
import json

# Deterministic canonical payload hash
payload_digest = hashlib.sha256(
json.dumps(
{
"hotspotId": body["hotspotId"],
"kind": body["kind"],
"observedAt": body["observedAt"],
"description": body.get("description", "").strip()
},
sort_keys=True
).encode("utf-8")
).hexdigest()
When an observation arrives, Lambda executes an atomic transaction in DynamoDB checking the actor's event partition:
  • Partition Key:  NS#pilot#ACTOR#<sourceKey> 
  • Sort Key:  EVENT#<eventId> 
  • Attributes:  payloadHash ,  statusCode ,  responseBody ,  createdAt 
  • Same eventId + matching hash: Lambda returns the previously cached 201 Created response immediately without incrementing evidence.
  • Same eventId + conflicting payload: Lambda returns 409 Conflict.
  • New eventId: The observation proceeds to atomic aggregation.



3. The DynamoDB TTL Trap & The "Dual-Clock" Freshness Engine

A frequent serverless design error is relying on DynamoDB's native Time-To-Live (TTL) feature to "auto-clear" flood alerts after 30 or 45 minutes.
The Reality: According to AWS documentation, DynamoDB TTL deletes expired items asynchronously in the background, which can take anywhere from minutes to 48 hours.
If a navigation backend relies on the physical presence of a record in DynamoDB to determine whether an underpass is flooded, an expired hazard might linger for two days.
Even more importantly: in disaster tech, stale does not mean clear, and an absence of recent reports does not prove a road is dry.
We addressed this with a Dual-Clock Architecture:
Plain text
Observation Accepted (T = 0)
├── [Logical Window] freshUntil = T + 30 mins (Query Freshness Boundary)
├── [State Evaluation] nextTransitionAt = T + 30m (State Re-evaluation Boundary)
└── [Storage Retention] cleanupAt = T + 7 days (DynamoDB TTL Epoch Seconds)
  • observedAt: Client observation time (validated against a 2-minute future clock skew).
  • receivedAt: Authoritative server receipt time (prevents artificial backdating).
  • freshUntil: Logical expiration. Once crossed, the report stops counting as active corroboration.
  • cleanupAt: Native DynamoDB TTL attribute used solely for physical storage purging and cost control.
Whenever Lambda evaluates a hotspot, it reads the active observation partition and re-computes freshness against the current UTC timestamp, irrespective of whether DynamoDB has physically reclaimed old rows.



4. Bounding Amazon Bedrock: Unstructured Hinglish to Typed Schemas

Delhi commuters rarely submit reports in clean English. They submit mixed Hinglish:
"Bhai Minto Bridge underpass me 3 foot paani bhara hai, car band ho gayi hai."
We utilized Amazon Bedrock (using lightweight foundation models via the Converse API) to parse unstructured natural language. But generative models can hallucinate or follow prompt injections.
We locked down Bedrock with three strict architectural boundaries:
  1. Zero Operational Authority: Bedrock cannot change road states, clear closures, or calculate detour paths.
  1. Untrusted Data Isolation: The system prompt explicitly treats user input as untrusted: __PRE_PLACEHOLDER_3__
  1. Explicit Claim Provenance: If someone mentions "3 foot paani," the schema normalizes it but flags it as an unverified user claim:
Plain text
{
"hazardMention": "WATER_OBSERVED",
"reportedDepth": {
"value": 3,
"unit": "ft",
"sourcePhrase": "3 foot paani",
"basis": "user_claim"
},
"evidenceSpans": ["Minto Bridge", "3 foot paani"]
}
The commuter reviews this extracted preview in the app before confirming submission, maintaining human-in-the-loop integrity.



5. The State Machine: flood-policy-v1

Instead of probabilistic percentages, our backend executes an explicit finite-state machine:
  • UNKNOWN: Default state. Essential rule: An unassessed corridor is shown with coverage warnings, never marked green or "Safe."
  • REPORTED: One qualifying fresh positive report. Two-wheeler routes immediately bypass the segment, and the UI displays  1 report • awaiting corroboration .
  • CORROBORATED: At least two distinct contributor sessions ( sourceKey ) report matching conditions within the 30-minute freshness window.
  • CONFLICTED: Fresh positive reports coexist with newer negative claims. Avoidance is preserved and flagged for community review.
  • STALE: When 30 minutes pass with no new data, the road is not cleared. It transitions to  STALE . Navigation continues to route around the hazard, warning the driver: "Water reported 45m ago. Clearance unconfirmed."
  • RECENTLY_CLEAR_REPORTED: Clearance requires an explicit multi-negative consensus (two independent reports of clear road conditions and zero subsequent flood sightings).



6. Atomic Optimistic Locking in DynamoDB

During a sudden storm, multiple riders submit reports at the same time. If two Lambda invocations process simultaneous updates, race conditions could cause lost writes or stale state regressions.
We implemented a single-table design with optimistic concurrency locking via a monotonic  revisionId :
Plain text
def update_hazard_state(hotspot_id, new_observation, expected_revision):
# Fetch fresh observations within the active 30-minute window
fresh_obs = fetch_fresh_observations(hotspot_id, window_minutes=30)
next_state = evaluate_state_machine(fresh_obs + [new_observation])
table.transact_write_items(
TransactItems=[
# 1. Store immutable observation record
{
"Put": {
"Item": serialize_obs(new_observation),
"TableName": TABLE_NAME
}
},
# 2. Write state aggregate guarded by optimistic revision lock
{
"Put": {
"Item": {
"PK": f"NS#pilot#HOTSPOT#{hotspot_id}",
"SK": "STATE",
"state": next_state.name,
"revisionId": expected_revision + 1,
"updatedAt": current_utc_iso(),
"freshUntil": compute_fresh_boundary(fresh_obs)
},
"TableName": TABLE_NAME,
"ConditionExpression": "attribute_not_exists(revisionId) OR revisionId = :expected",
"ExpressionAttributeValues": {":expected": expected_revision}
}
}
]
)
If another invocation updates the state in parallel, DynamoDB throws a  TransactionCanceledException . Our handler catches this, retrieves the latest revision, and retries with exponential backoff and jitter.



7. Fail-Closed Routing: Why We Refuse to Draw a Line

Standard navigation tools optimize for the fastest route. In flash floods, conventional tools often continue directing motorists into submerged underpasses because traffic hasn't stalled far enough back yet, or because 2D maps confuse dry elevated flyovers with flooded subterranean roads below.
Our routing Lambda verifies candidate route polylines against active excluded road segments.
What happens if both the primary route and the detour underpass are flooded?
Most navigation apps will still draw a path across whichever road looks least congested.
DilliJalNav Edge adopts a Fail-Closed Approach:
Plain text
{
"status": "NO_ADMISSIBLE_ROUTE",
"explanation": "All reviewed corridors through this sector are obstructed by corroborated flood reports. Travel is not advised.",
"activeHazards": ["MINTO_BRIDGE_UNDERPASS", "CONNAUGHT_OUTER_CIRCLE"]
}
No blue line. No misleading arrival estimate. An unmistakable, honest warning.
In standard consumer apps, failing gracefully means keeping the user moving. In climate resilience engineering, failing gracefully means stopping someone from driving into danger.



Key Takeaways for AWS Builders

  1. Replace Probabilistic Confidence with Deterministic State: Safety-critical applications require auditable, rule-based state machines that can justify every alert.
  1. Never Confuse DynamoDB TTL with Business Freshness: Use TTL for cost and storage management; use explicit application timestamps for temporal freshness.
  1. Idempotency is Non-Negotiable at the Edge: Expect flaky connectivity in extreme weather. Use transactional request hashing to prevent over-counting.
  1. Confine Generative AI to Extraction: Use Amazon Bedrock to parse unstructured human languages, but keep core decision logic in deterministic code.
  1. Treat "Unknown" as a First-Class State: An application that honestly flags unverified areas is far safer than one that paints an unconfirmed road green.
Built by Team Kernel (Utkarsh Singh, Pranav Pant, Granth Chhabra, Adhyan Saxena) during the Bharat Builds Tour — Event 02: Environmental Hacks by WeMakeDevs × AWS (Track 02: Heat & Water).
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