Prodigy

EmberWatch — Fire Detection & Intelligence System

CONFIDENTIAL TECHNICAL OVERVIEW • MARCH 2026

Prepared for Stuart Mitchell — Defensible Space & Structure Hardening Specialist

Executive Summary

EmberWatch is Prodigy's real-time wildfire intelligence layer. It continuously ingests fire data from10 government and satellite sources across 3 countries, normalizes it into a unified schema, and delivers property-specific threat assessments to homeowners — typically within15–30 minutes of fire detection.

The system runs autonomously 24/7 — no human operator. A scheduled job fetches, deduplicates, and caches fire data every 5 minutes. Users never hit external APIs directly; they read from a pre-built cache that delivers sub-200ms response times regardless of how many people are watching the same fire.

1. Data Sources — Where the Fire Data Comes From

We pull from 10 live government feeds, organized into two parallel fetch batches to manage memory (each serverless function has a 128MB limit):

Batch 1 — North America + Eastern Australia

NIFC / WFIGSUnited States

National Interagency Fire Center — the authoritative US wildfire incident database. ArcGIS FeatureServer, up to 1,500 records. Includes IRWIN IDs, containment %, fire cause, jurisdictional agency, and incident type classification.

services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/WFIGS_Incident_Locations_Current

CWFISCanada (National)

Canadian Wildland Fire Information System — national feed via GeoServer WFS. Reports fire stage of control (Out of Control, Being Held, Under Control, Extinguished), hectares burned, and provincial agency.

cwfis.cfs.nrcan.gc.ca/geoserver/public/ows

BCWSCanada (BC)

BC Wildfire Service — ArcGIS endpoint for British Columbia specifically. Critical for Prodigy's core market. Includes fire cause, geographic description, and BC-specific fire number identifiers.

services6.arcgis.com/ubm4tcTYICKBpist/arcgis/rest/services/BCWS_ActiveFires_PublicView

NSW RFSAustralia (NSW)

New South Wales Rural Fire Service — major incidents JSON feed. Handles polygon geometries (centroid extraction for point-based mapping). Categorizes hazard reduction burns separately.

rfs.nsw.gov.au/feeds/majorIncidents.json

VIC EmergencyAustralia (Victoria)

Emergency Victoria — GeoJSON feed filtered for fire/bushfire categories. Maps alert levels (Safe → Watch and Act → Emergency Warning) to our normalized status enum.

emergency.vic.gov.au/public/events-geojson.json

Batch 2 — Remaining Australian States

QLD QFESAustralia (Queensland)

Queensland Fire and Emergency Services — S3-hosted JSON feed for bushfire current incidents.

publiccontent-gis-psba-qld-gov-au.s3.amazonaws.com

SA CFSAustralia (South Australia)

SA Country Fire Service — CRIIMSON incident feed. Filtered for fire/burn incident types.

data.eso.sa.gov.au/prod/cfs/criimson/cfs_current_incidents.json

NT PFESAustralia (Northern Territory)

NT Police, Fire & Emergency Services — GeoJSON incidents feed. Distinguishes planned burns from wildfires.

pfes.nt.gov.au/incidentmap/json/incidents.json

WA DFESAustralia (Western Australia)

WA Dept of Fire & Emergency Services — REST API with incident objects. Includes suburb-level locality data.

api.emergency.wa.gov.au/v1/incidents

TAS TFSAustralia (Tasmania)

Tasmania Fire Service — JSON incident feed filtered for fire events.

fire.tas.gov.au

Every source has an 8-second timeout. If a source is slow or down, it fails gracefully — the other 9 sources still update. Source health is tracked in a DataSourceHealth entity (latency, consecutive failures, last success time) so we can monitor reliability over time.

2. The Normalization Pipeline — Making Sense of 10 Different Formats

Every data source returns a different schema. The pipeline normalizes each fire into aunified record with consistent fields:

// Normalized fire record structure:
{
  fire_id:            "nifc_ABC123",        // Globally unique: source prefix + native ID
  region:             "US",                  // US | CA | AU
  source:             "NIFC",                // Which API provided this
  name:               "Oak Fire",            // Human-readable name
  latitude / longitude: 34.12, -119.45,     // WGS84 coordinates
  
  // Status normalization — every source maps to ONE of these:
  normalized_status:  "OUT_OF_CONTROL",      // OUT_OF_CONTROL | BEING_HELD | UNDER_CONTROL | UNKNOWN
  status_raw:         "OC",                  // Original value from source API
  
  // Classification
  is_prescribed:      false,                 // Prescribed/hazard reduction burns flagged separately
  is_controlled:      false,                 // 100% contained
  is_hotspot:         false,                 // Satellite thermal detection vs confirmed incident
  fire_type:          "WILDFIRE",            // WILDFIRE | PRESCRIBED | HOTSPOT
  containment_pct:    35,                    // 0-100 (null if unavailable)
  
  // Size & timing
  size_hectares:      5000,
  discovered_date:    "2026-03-15T08:00Z",
  
  // Metadata (varies by source)
  metadata: {
    agency: "USFS",                          // Jurisdictional agency
    cause:  "Lightning",                     // Fire cause if reported
    state:  "CA", county: "Ventura",         // US location detail
    province: null,                          // Canadian province
  }
}

Status Normalization Examples

SourceRaw ValueNormalized
NIFCPercentContained: 100UNDER_CONTROL
CWFISstage_of_control: "OC"OUT_OF_CONTROL
BCWSFIRE_STATUS: "Being Held"BEING_HELD
NSW RFSstatus: "Out of control"OUT_OF_CONTROL
VIC Emergencystatus: "Watch and Act"BEING_HELD
QLD QFESStatus: "Controlled"UNDER_CONTROL

Prescribed Burn Detection

We flag prescribed/hazard reduction burns at ingestion time to prevent false alarms:

  • NIFC: IncidentTypeCategory === "RX" or name contains "PRESCRIBED"
  • NSW RFS: category contains "hazard reduction"
  • NT PFES: _category matches "planned burn" or "prescribed"
  • WA DFES: name matches "burn off" or "prescribed"

Prescribed burns are never promoted to critical alerts. They have a 7-day age limit vs 21–30 days for wildfires.

3. Deduplication & Age Filtering

With 10 sources, the same fire can appear multiple times (e.g., CWFIS and BCWS both reporting the same BC fire). We use fire_id as a composite key (source prefix + native ID) and merge into a HashMap — first-write wins.

Age Filter Rules

Stale fires are automatically removed based on type and age:

Prescribed burns:        ≤ 7 days old
Controlled (100%):       ≤ 10 days old
NIFC incidents:          ≤ 30 days old    // US fires have slower reporting cycles
All other wildfires:     ≤ 21 days old

// Special case: NIFC fires with no discovery date are kept
// if they have size > 1ha OR containment < 100%

Circuit Breaker

If the new fire count drops below 10% of the previous count (and previous was >50), the system keeps the old cache instead of overwriting with potentially bad data. This protects against upstream API outages delivering empty or partial datasets.

4. Multi-Layer Caching — How 1000 Users See the Same Fire Instantly

┌─────────────────────────────────────────────────────────────────┐
│  LAYER 1: FireCache Entity (Database)                           │
│  Updated every 5 min by refreshFireCache scheduled job          │
│  Contains ALL fires globally (typically 400-800 records)        │
│  Single row: cache_key = "global_fires"                         │
└──────────────────────────┬──────────────────────────────────────┘
                           │ DB read (once per 30s per isolate)
┌──────────────────────────▼──────────────────────────────────────┐
│  LAYER 2: In-Memory Cache (Deno Deploy Isolate)                 │
│  30-second TTL — shared across all concurrent requests          │
│  Request coalescing: 1000 simultaneous users → 1 DB read        │
│  Bounding-box pre-filter → O(relevant) not O(all)               │
└──────────────────────────┬──────────────────────────────────────┘
                           │ JSON response (~10KB per user)
┌──────────────────────────▼──────────────────────────────────────┐
│  LAYER 3: Client Session Cache (Browser RAM)                    │
│  Push-based reactive cache — updates on Layout prefetch          │
│  Instant restore on tab switch (0ms)                            │
└──────────────────────────┬──────────────────────────────────────┘
                           │ Fallback if offline
┌──────────────────────────▼──────────────────────────────────────┐
│  LAYER 4: localStorage Cache (5-min TTL)                        │
│  Enables offline mode — stale data better than no data          │
│  Auto-cleared when fresh data arrives                           │
└─────────────────────────────────────────────────────────────────┘
Self-healing: If getFireSnapshot errors, it silently serves the last-known-good in-memory cache. The user never sees an error — they get slightly stale data instead of nothing. This is critical for a safety-critical application.

5. Fire Movement Tracking — Is It Coming Toward You?

After the cache is written, the system runs a movement tracking phase. For every property with coordinates, it checks each nearby fire against previously recorded distances.

// Movement detection logic:
previousDistance = FireTracking.distance_km   // from 5 min ago
currentDistance  = haversine(property, fire)  // now
distanceChange   = current - previous

if (|distanceChange| < 0.5 km)  → "stable"
if (distanceChange < 0)         → "approaching"  // GETTING CLOSER
if (distanceChange > 0)         → "receding"     // MOVING AWAY

// If approaching:
approachRate = |distanceChange| / hoursSinceLastCheck   // km/h
estimatedArrival = currentDistance / approachRate        // hours

Threat Scoring

Each fire gets a 0–100 threat score based on distance + movement:

FactorPoints
Within 10 km+40
Within 25 km+30
Within 50 km+20
Approaching+20
Approach rate > 5 km/h+10

Alert Escalation

Alert levels drive notifications (push, email, SMS):

  • CRITICAL — ≤10 km or threat score ≥80
  • WARNING — ≤25 km or threat score ≥60
  • WATCH — ≤50 km or threat score ≥40

Alerts are only generated when status changes (e.g., a fire transitions from "stable" to "approaching", or from WARNING to CRITICAL). This prevents notification spam during ongoing situations.

6. Weather Integration & AI Risk Analysis

Weather Data

Real-time weather from Open-Meteo (free, no API key, global coverage). Per-location, 10-minute cache. Fields: temperature, humidity, wind speed, wind gusts, wind direction.

Risk Score Calculation (Deterministic + AI)

The risk score is a two-stage process:

Stage 1: Deterministic Score — hard-coded rules, no AI involved:

// Fire proximity (confirmed incidents only — hotspots excluded)
≤5 km  → base 85-100 (CRITICAL regardless of weather)
5-15km → base 50-85  (HIGH, scaled by distance)  
15-30km → base 25-40 (MODERATE, weather can increase)
30-50km → base 15    (LOW, weather-driven)

// Temperature modifiers
≤0°C:  cap score at 8 (or 70 if confirmed fire ≤10km)
≤10°C: cap score at 15 (or 60 if confirmed fire ≤15km)
≥38°C: +20 points
≥32°C: +12 points

// Humidity modifiers
≤15%: +15 points (critically dry)
≤25%: +10 points

// Wind modifiers
≥50 km/h: +20 points (extreme)
≥30 km/h: +10 points

Stage 2: AI Contextual Analysis — the deterministic score is passed to an LLM with instructions to use the exact score but provide contextual explanation, actionable advice, fire spread prediction based on wind direction, and sprinkler system recommendations. Results are cached for 6 hours per risk-score band.

7. Map Visualization — What the User Sees

The EmberWatch map renders multiple overlay layers on Leaflet + Stadia Maps dark tiles:

  • Fire Clusters — clustered markers at low zoom, individual fire icons at high zoom, color-coded by threat distance (red ≤5km, orange 5-15km, yellow 15km+, green = controlled)
  • Monitoring Radius — adjustable circle (7/10/25 km) with traffic-light coloring (green = clear, yellow = elevated, red = critical)
  • Fire Perimeters — real NIFC perimeter polygons when available, generated approximations from size for others
  • Wind Overlay — animated directional arrows showing current wind speed and direction
  • Fire Spread Prediction — projected spread zones based on wind + fire position
  • Ember Attack Zones — modeled ember transport distance based on wind speed, fire intensity (FRP), and distance
  • Evacuation Routes — AI-generated routes using OSRM road-following routing
  • Historical Fire Footprints — past major fire boundaries for context
  • Emergency Services — hospitals, fire stations, shelters from curated static database
  • Road Closures — active closures near the property

8. Alert & Notification Pipeline

Fire Movement Detected
        │
        ▼
  Alert Level Changed?  ─── No ──→  Skip (no spam)
        │
       Yes
        │
        ▼
  Is Prescribed Burn? ─── Yes ──→  Skip (never alert on Rx)
        │
       No
        │
        ▼
  Is Test Mode? ─── Yes ──→  Skip (demo safety)
        │
       No
        ├──→  In-App Notification (always)
        ├──→  Push Notification (native APNs / Firebase)
        ├──→  Email (Resend API — fallback or if enabled)
        └──→  SMS (Twilio — if phone verified & enabled)

9. System Architecture Summary

ComponentTechnologyPurpose
refreshFireCacheDeno serverless (scheduled)Fetch + normalize + dedupe + cache every 5 min
getFireSnapshotDeno serverless (on-demand)Read cache, filter by location, serve to users
fetchWeatherOpen-Meteo APIReal-time weather per property (10min cache)
analyzeHazardRiskDeterministic + LLMRisk scoring + contextual AI analysis
triggerEmberWatchAlertPush + Email + SMSMulti-channel alert delivery
FireCache entityNoSQL databaseGlobal fire cache (single row, all fires)
FireTracking entityNoSQL databasePer-property fire distance tracking over time
DataSourceHealth entityNoSQL databaseAPI health monitoring per source

10. Key Numbers

10
Data Sources
3
Countries
5 min
Refresh Interval
400-800
Typical Fire Count
< 200ms
Response Time
30 sec
Cache TTL (server)
Per-property
Movement Tracking
4
Alert Channels
1000+
Concurrent Users
99.9%
Uptime Target

© 2026 Prodigy Wildfire Solutions. This document is confidential and intended for Stuart Mitchell only.

Fire data sourced from public government feeds. Prodigy is not affiliated with any government entity.

Prodigy

Taking longer than usual to load. Check your connection and try again.