# Project export: ContainOS

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Physics-Grounded Multi-Agent Copilot for Rapid Wildfire Containment.
- Devpost: https://devpost.com/software/containos
- GitHub: https://github.com/maanitg/containment
- Video: https://www.youtube.com/embed/EZNfc-7HeEc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([OpenAI] Artificial Intelligence Track ([1st] Lunch with OpenAI engineers at the office + 1 year of ChatGPT Pro [2nd] 1 year of ChatGPT Pro [3rd] OpenAI swag))
- Team: 3 GitHub contributor(s) — KTianshi (8 commits), Nathan Zhou (6 commits), Claude Opus 4.6 (1 commits)

## Devpost submission (written by the team)

### Inspiration

The first three hours of a wildfire, the initial attack, are the most critical in determining whether the wildfire can be rapidly contained with minimal damage or whether a massive, multi-day operation is needed. Incident commanders on the ground must rapidly assess a fire, decide where to deploy crews and aircraft, and issue evacuation warnings as conditions evolve. Despite unprecedented access to satellite imagery, low earth orbit data, drone feeds, and AI-enabled cameras, responders still lack a unified system that synthesizes this information into actionable, real-time guidance. We built ContainOS to provide real-time, physics-grounded decision support for firefighters en route, reducing cognitive friction during high-pressure moments.

### What it does

ContainOS transforms fragmented wildfire data into a unified decision-support platform for containment operations. It ingests wind, terrain, infrastructure, topography, and population data to generate structured insights and prioritized alerts about present and impending risks, enabling rapid triage decisions by incident commanders. Historical fire data is incorporated to surface relevant precedents and escalation patterns from past incidents. Beyond presenting base data through a streamlined, low-friction interface optimized for tablet use in the field, ContainOS generates prioritized alerts tied directly to the fire’s interaction with its surroundings. The system supports both online and offline operation and integrates proactive text-based alerting for first responders in low-connectivity environments, flagging immediate hazards such as downed powerlines or significant wind shifts.

### How we built it

ContainOS combines deterministic wildfire physics with a feedback-aware multi-agent AI system. The system uses four coordinated GPT-4o reasoning agents that operate as a structured agent graph for fire behavior, risk assessment, notifications, and recommendations, alongside a Gemini-powered historical context component. All outputs pass through a deterministic validation layer. Before agent reasoning, the backend computes a deterministic physics state, conceptually forming a physics graph over the fire perimeter and nearby communities, from time-indexed environmental snapshots (wind, terrain, vegetation, infrastructure, fire perimeter, and population data). This includes: Baseline spread velocity Slope and fuel multipliers A deterministic threat level Community exposure using distance thresholds The frontend visualizes these dynamics per segment, rendering fast, moderate, and slow spread zones. On top of this physics baseline, the GPT-4o agents generate: Fire behavior analysis Infrastructure risk assessment A small, prioritized set of tactical alerts (1–5) A top containment recommendation with a 0–100 confidence score All outputs are validated against deterministic constraints. If an agent contradicts the physics baseline, the system injects structured feedback and re-runs the affected agents. The system allows up to three total attempts (initial generation plus two retries). If consistency cannot be achieved, it safely falls back with a confidence score of 0. Together, the physics state and agent reasoning form a constrained two-layer architecture, where probabilistic inference is bounded deterministically. This architecture prevents generative reasoning from overriding physical reality, converting AI into a bounded, constraint-validated system for high-stakes decision support. Challenges A core challenge was architecting a system where a deterministic physics pipeline and a multi-agent reasoning graph operate in lockstep. The physics layer establishes a non-negotiable baseline, while probabilistic agents generate structured assessments on top of it. Ensuring these layers remained aligned required explicit validation, constraint enforcement, and bounded retries. Rather than functioning as a linear AI pipeline, the system continuously reconciles agent outputs with computed physical state to preserve reliability under rapidly changing conditions. Another challenge was deploying AI into real operational workflows without introducing misplaced trust. In wildfire containment, recommendations affect evacuation timing, crew deployment, and infrastructure protection. We had to design the system so that AI assistance augments situational awareness without projecting unwarranted certainty, remaining transparent, bounded, and clearly subordinate to human judgment. Accomplishments We iterated directly with former CalFire leadership to refine alert prioritization, evacuation logic, and interface clarity based on real operational workflows. We built a feedback-aware, physics-constrained multi-agent system that enforces physically consistent, self-correcting reasoning in a high-stakes environment. By validating probabilistic agent outputs against a deterministic physics baseline and enforcing bounded retries with safe fallback behavior, the system prevents generative outputs from contradicting physical reality. This ensures recommendations remain consistent, explainable, and grounded in computed state. Most importantly, we demonstrated a practical framework for responsible AI in life-critical domains: generative reasoning explicitly constrained by deterministic physical models, structured validation loops, visible uncertainty through confidence scoring, and clear preservation of human authority in decision-making. This approach shows how AI systems can augment rather than replace expert judgment in safety-critical contexts.

### What we learned

We learned that high-stakes AI systems must be explicitly bounded by deterministic constraints; generative outputs cannot be trusted without formal validation against physical reality. This is especially true in wildfire containment, where lives are at stake and operational decisions must remain grounded in computed state. We also learned the importance of iteratively reviewing our product with real users, for not only initial insights on user pain points but continuous alignment and redirection as needed. This project reinforced how modern AI systems can responsibly support public sector decision-making when deliberately constrained and purpose-built.

### What's next

Ensuring ContainOS is useful in the field will require direct feedback from incident commanders. We hope to work with CalFIRE to extensively test and update this tool for eventual rollout across California and beyond, for real application in containing wildfires right when they start.

## README (from the GitHub repository)

# ContainOS

**AI-Powered Wildfire Intelligence & Command System**

A real-time multi-agent decision support system for wildfire incident command, combining deterministic physics calculations with coordinated AI agents to provide tactical recommendations, threat assessments, and automated alerts.

---

## Quick Start

```bash
# 1. Setup environment
cp .env.example .env
# Edit .env and add your OPENAI_API_KEY and GEMINI_API_KEY

# 2. Start backend (Terminal 1)
cd backend
python -m pip install -r requirements.txt
python main.py

# 3. Start frontend (Terminal 2)
cd frontend
npm install
npm run dev

# 4. Open http://localhost:5173
```

### Prerequisites

- **Node.js** 18+ and npm
- **Python** 3.10+
- **OpenAI API Key** - [Get one here](https://platform.openai.com/api-keys)
- **Google Gemini API Key** - [Get one here](https://aistudio.google.com/app/apikey)

---

## Overview

Command-and-control platform for wildfire incident commanders combining deterministic physics with coordinated AI agents.

**Core Capabilities:**
- **Physics-grounded AI** - Deterministic calculations validate all AI outputs; violations trigger automatic replanning
- **Geographic historical memory** - Gemini analyzes past fires from the same region to inform tactics
- **Multi-agent orchestration** - 7 coordinated components (physics + GPT-4o + Gemini + validator) analyze risk in real-time
- **Closed-loop validation** - Failed physics checks force agent replanning (max 2 retries)
- **Interactive mapping** - Leaflet visualization with fire perimeters, terrain, infrastructure
- **Offline-first** - Works without connectivity using cached data and IndexedDB

---

## Architecture

### Multi-Agent Pipeline

```
                            Live Fire Data
                                  |
                                  v
                      Graph Physics Engine
                    (deterministic calculations)
                                  |
                  +---------------+---------------+
                  |                               |
                  v                               v
        Historical Memory                    Physics Data
         (Gemini 1.5 Pro)                  (spread, threat)
        Finds regional fires                     |
                  |                               |
                  +---------------+---------------+
                                  |
                                  v
                  +---------------+---------------+
                  |                               |
                  v                               v
          Fire Behavior Agent             Risk Analysis Agent
               (GPT-4o)                        (GPT-4o)
            Physics + History               Physics + History
                  |                               |
                  +---------------+---------------+
                                  |
                                  v
                  +---------------+---------------+
                  |                               |
                  v                               v
          Notification Agent             Recommendation Agent
               (GPT-4o)                        (GPT-4o)
         1-5 factual alerts                 1 tactical action
                  |                               |
                  +---------------+---------------+
                                  |
                                  v
                              Validator
            (physics constraint check + multi-turn retry loop)
                                  |
                                  v
                           Frontend Output
```

**Agent Roles:**

1. **Graph Physics Engine** - Computes spread velocity, threat levels using deterministic formulas
2. **Historical Memory** (Gemini) - Finds past fires in same region, provides learned tactics
3. **Fire Behavior** (GPT-4o) - Analyzes spread patterns using physics + historical context
4. **Risk Analysis** (GPT-4o) - Identifies threatened infrastructure using physics + history
5. **Notification** (GPT-4o) - Generates 1-5 concise alerts (≤10 words each)
6. **Recommendation** (GPT-4o) - Provides 1 tactical action (≤12 words), rationale, and confidence
7. **Validator** - Enforces physics constraints; triggers replanning if violated (max 2 retries)

---

## API Reference

**Main Endpoint**
- `POST /api/process-live-data/{time_index}` - Process timestamped fire data (index: 1-5)
  - Returns: notifications + recommendation + computed physics + history summary

**Data Endpoints**
- `GET /api/data/all` - Static map data (fire perimeter, terrain, infrastructure)
- `GET /api/data/live/{time_index}` - Timestamped live data (index: 1-5)
- `GET /api/notifications?limit=20&offset=0` - Agent-generated notifications
- `GET /api/recommendations/latest` - Most recent recommendation
- `GET /api/recommendations/all` - All recommendations
- `GET /api/status` - Notification/recommendation system status
- `POST /api/reset-notifications` - Clear notifications/recommendations in memory
- `POST /api/analyze` - Direct full analysis endpoint (request body required)
- `GET /health` - Health check
- `WebSocket ws://localhost:8000/ws` - Real-time agent status streaming

**Full docs:** http://localhost:8000/docs

---

## How It Works

### Historical Context Integration

**Geographic-first matching:**
1. System loads past fires from `historical_fires.json` (behavior, tactics, resources)
2. Gemini prioritizes **same region** (e.g., Northern California), then matches wind/slope/vegetation
3. Generates 3-sentence summary of how those regional fires behaved
4. Context provided to Fire Behavior & Risk Analysis agents for informed predictions

**Example output:**
> "The 2022 Canyon Creek Fire in Northern California exhibited rapid uphill spread through chaparral under 25mph NE winds on 30° slopes, requiring defensive positioning ahead of the fire front. Resources escalated 5x when fire reached chaparral belt, with dozer lines on ridgetops proving most effective."

### Closed-Loop Validation

Physics engine establishes ground truth; AI outputs must comply or trigger replanning:

```python
# Deterministic baseline
if slope > 20: threat = "CRITICAL"
if town_distance < 5km: threat = "CRITICAL"

# AI outputs "ELEVATED" → Validator rejects

# System forces replan:
# "Physics violation: deterministic calculates CRITICAL but you output ELEVATED. You MUST escalate."

# AI retries → outputs "CRITICAL" → Approved
```

**Result:** No AI hallucinations. All recommendations grounded in fire physics.

---

## 📁 Project Structure

```
containment/
├── backend/
│   ├── agents/
│   │   ├── orchestrator.py          # Multi-agent coordination + physics engine
│   │   └── historical_memory.py     # Gemini geographic matching
│   ├── data/                         # Fire perimeter, terrain, historical fires (JSON)
│   ├── main.py                       # FastAPI endpoints + WebSocket
│   └── notification_manager.py       # Stores agent outputs
├── frontend/
│   ├── src/
│   │   ├── components/               # FireMap (Leaflet), LayerControls
│   │   ├── services/                 # API client
│   │   ├── offline/                  # IndexedDB + Service Worker
│   │   └── App.jsx                   # Main UI
│   └── package.json
└── README.md
```

---

## Environment Variables

Create `.env` in the project root for backend keys:

```bash
# Required
OPENAI_API_KEY=your_key_here     # Multi-agent system (GPT-4o)
GEMINI_API_KEY=your_key_here     # Historical memory (Gemini 1.5 Pro)

```

Optional frontend override in `frontend/.env.local`:

```bash
VITE_API_URL=http://localhost:8000
```

---

## Tech Stack

| Layer | Technology | Purpose |
|-------|-----------|---------|
| Frontend | React 19 + Vite + Leaflet | UI + interactive maps |
| Backend | FastAPI + Uvicorn | API server |
| AI Agents | GPT-4o + Gemini 1.5 Pro | Multi-agent reasoning + historical memory |
| Validation | Pydan

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 33 recognized source files, 190 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (51 of 51)

```
.env.example
.gitignore
api/health.js
backend/agents/historical_memory.py
backend/agents/orchestrator.py
backend/data/fire_perimeter.json
backend/data/frontend_historical_fires.json
backend/data/historical_fires.json
backend/data/infrastructure.json
backend/data/live_data_t1.json
backend/data/live_data_t2.json
backend/data/live_data_t3.json
backend/data/live_data_t4.json
backend/data/live_data_t5.json
backend/data/terrain.json
backend/main.py
backend/notification_manager.py
backend/requirements.txt
backend/test_frontend.html
docs/DATA_CONSOLIDATION_README.md
docs/PROJECT_STRUCTURE.md
docs/QUICK_START.md
docs/SETUP.md
frontend/.env.example
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/public/manifest.json
frontend/public/sw.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/FireMap.jsx
frontend/src/components/InsightsPanel.jsx
frontend/src/components/LayerControls.jsx
frontend/src/hooks/useFireData.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/offline/connectivity.js
frontend/src/offline/idb-cache.js
frontend/src/offline/offline.css
frontend/src/offline/OfflineBanner.jsx
frontend/src/offline/OfflineProvider.jsx
frontend/src/offline/register-sw.js
frontend/src/offline/useCachedFetch.js
frontend/src/offline/write-queue.js
frontend/src/services/dataService.js
frontend/vite.config.js
public/manifest.json
public/sw.js
README.md
vercel.json
```

### Dependencies

- backend/requirements.txt: fastapi@==0.115.6, google-generativeai@==0.8.3, openai@==1.59.8, pydantic@==2.10.6, python-dotenv@==1.0.1, uvicorn[standard]@==0.34.0
- frontend/package.json: @anthropic-ai/sdk@^0.74.0, @eslint/js@^9.39.1, @types/react@^19.2.7, @types/react-dom@^19.2.3, @vitejs/plugin-react@^5.1.1, eslint@^9.39.1, eslint-plugin-react-hooks@^7.0.1, eslint-plugin-react-refresh@^0.4.24, globals@^16.5.0, leaflet@^1.9.4, react@^19.2.0, react-dom@^19.2.0, react-leaflet@^5.0.0, vite@^7.3.1

### Recent commits (newest first)

- Update README.md
- Add multi-turn retry note to README pipeline diagram
- Rename project
- Fix README multi-agent pipeline diagram formatting
- Fix agent coordination
- Merge branch 'feature/agents'
- Fix notifications
- Merge pull request #6 from maanitg/feature/agents
- Fix agents
- Merge branch 'offline-vercel' into main
- updates
- Fix frontend and backend alignment
- Update README
- Merge pull request #4 from maanitg/feature/agent-reasoning-graph
- Connect backend and frontend
- Merge pull request #3 from maanitg/feature/agent-reasoning-engine
- Fix structure
- Merge pull request #2 from maanitg/kyle-new-changes
- ui edits and stuff
- Add current agent setup

## Key source files (fetched from GitHub, selected and truncated for size)

### docs/QUICK_START.md

```markdown
# Quick Start Guide

Get WildfireOS running in 5 minutes.

## Prerequisites

- Python 3.9+ installed
- Node.js 18+ and npm installed
- API keys for Gemini and OpenAI

## Step 1: Clone and Setup

```bash
cd /path/to/containment

# Copy environment template
cp .env.example .env

# Edit .env and add your API keys
# GEMINI_API_KEY=your_key_here
# OPENAI_API_KEY=your_key_here
```

## Step 2: Backend Setup

```bash
cd backend

# Install Python dependencies
pip install -r requirements.txt

# Start the backend server
python main.py
```

The backend will start on **http://localhost:8000**

You should see:
```
🔥 WildfireOS Backend Starting...
📍 API: http://localhost:8000
📍 Docs: http://localhost:8000/docs
🔌 WebSocket: ws://localhost:8000/ws
```

## Step 3: Frontend Setup

Open a new terminal:

```bash
cd frontend

# Install Node dependencies
npm install

# Start the development server
npm run dev
```

The frontend will start on **http://localhost:5173**

## Step 4: Open in Browser

Open **http://localhost:5173** in your browser.

You should see:
1. Loading screen while data fetches
2. Fire map with active fire perimeter
3. Active alerts in the sidebar
4. Map legend at bottom

## Verify It's Working

### Backend Health Check
```bash
curl http://localhost:8000/health
# Should return: {"status":"healthy"}
```

### Data Endpoints
```bash
# Get all data
curl http://localhost:8000/api/data/all

# Get fire data only
curl http://localhost:8000/api/data/fire-perimeter
```

### Frontend
- Map should display with fire perimeter
- Alerts should appear in right sidebar
- Toggle map layers using legend controls
- Click on map features for popups

## Common Issues

### Backend won't start

**Error: "GEMINI_API_KEY not found"**
- Solution: Add API keys to `.env` file

**Error: "Port 8000 already in use"**
- Solution: Stop other services on port 8000 or change port in `main.py`

**Error: "Module not found"**
- Solution: Install dependencies: `pip install -r requirements.txt`

### Frontend won't start

**Error: "Cannot find module"**
- Solution: Run `npm install`

**Error: "EADDRINUSE: Port 5173 in use"**
- Solution: Stop other Vite servers or Vite will auto-increment port

### Frontend shows error

**"Error Loading Data"**
- Solution: Make sure backend is running on port 8000
- Check: `curl http://localhost:8000/health`

**Map not rendering**
- Solution: Check browser console for errors
- Ensure Leaflet CSS is loading

## Next Steps

1. **Explore the API**: Visit http://localhost:8000/docs for interactive API documentation
2. **Customize data**: Edit JSON files in `backend/data/`
3. **Read documentation**: See `docs/` folder for detailed guides
4. **Review architecture**: Check `docs/PROJECT_STRUCTURE.md`

## Development Workflow

```bash
# Terminal 1: Backend
cd backend
python main.py

# Terminal 2: Frontend
cd frontend
npm run dev

# Terminal 3: Testing/Development
# Make your changes here
```

## Stop the Servers

- Backend: Press `Ctrl+C` in backend terminal
- Fron
[truncated — 486 more characters]
```

### docs/DATA_CONSOLIDATION_README.md

```markdown
# Data Consolidation Summary

## What Was Done

### 1. Data Consolidation ✅
All data files have been consolidated into a single location: `/containment/backend/data/`

**Data Files Created:**
- `fire_perimeter.json` - Current fire perimeter, wind data, and wind forecast
- `infrastructure.json` - Communities, firebreaks, and water resources
- `terrain.json` - Fuel types, elevation points, power lines, and ridge lines
- `frontend_historical_fires.json` - Historical fires with perimeters (for map visualization)
- `historical_fires.json` - Historical fires data (used by AI agents)

### 2. Backend Updates ✅
Updated `/containment/backend/main.py`:
- Added data loading function `load_json_file()`
- Created new API endpoints:
  - `GET /api/data/fire-perimeter` - Fire and wind data
  - `GET /api/data/infrastructure` - Communities and infrastructure
  - `GET /api/data/terrain` - Terrain and vegetation data
  - `GET /api/data/historical-fires` - Historical fire records
  - `GET /api/data/all` - All data in one request (recommended)
- Created `requirements.txt` with all dependencies

### 3. Frontend Updates ✅
Updated frontend to fetch data from backend API:

**New Files:**
- `/frontend/src/services/dataService.js` - API service for fetching data
- `/frontend/src/hooks/useFireData.js` - React hook for data fetching

**Updated Files:**
- `/frontend/src/App.jsx` - Now fetches data and passes to FireMap
- `/frontend/src/components/FireMap.jsx` - Accepts data as props, removed local imports

**Backup Files Created:**
- `App.jsx.backup`
- `FireMap.jsx.backup`

### 4. Architecture Changes
**Before:**
- Frontend: Imported data directly from local JS files
- Backend: Only used one historical fires JSON file
- Data scattered across frontend and backend

**After:**
- Frontend: Fetches all data from backend API on mount
- Backend: Serves all data through REST API endpoints
- All data consolidated in backend/data folder
- Single source of truth for all fire data

## How to Run

### Backend
```bash
cd /Users/nathan/Downloads/TreeHacks/project/containment/backend

# Install dependencies (if not already installed)
pip install -r requirements.txt

# Make sure .env file has required API keys:
# GEMINI_API_KEY=your_key_here
# OPENAI_API_KEY=your_key_here

# Run the backend
python main.py
```

Backend will run on `http://localhost:8000`

### Frontend
```bash
cd /Users/nathan/Downloads/TreeHacks/project/containment/frontend

# Install dependencies (if not already installed)
npm install

# Run the frontend
npm run dev
```

Frontend will run on `http://localhost:5173`

## Testing the Integration

1. Start the backend first (it must be running on port 8000)
2. Start the frontend (it will connect to the backend automatically)
3. The frontend will show a loading screen while fetching data
4. If the backend is not running, you'll see an error message with instructions

## API Endpoints

Base URL: `http://localhost:8000`

- `GET /` - Service status
- `GET /health` - Health check
- `GET
[truncated — 1664 more characters]
```

### backend/requirements.txt

```
fastapi==0.115.6
uvicorn[standard]==0.34.0
python-dotenv==1.0.1
google-generativeai==0.8.3
openai==1.59.8
pydantic==2.10.6

```

### frontend/package.json

```
{
  "name": "wildfire-intel",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.74.0",
    "leaflet": "^1.9.4",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "react-leaflet": "^5.0.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@types/react": "^19.2.7",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.1.1",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "vite": "^7.3.1"
  }
}

```

### backend/main.py

```python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Any
import asyncio
import json
import os

from agents.orchestrator import execute_agent_graph
from agents.historical_memory import HistoricalMemory
from notification_manager import notification_manager

app = FastAPI(title="WildfireOS Backend")

# Configure CORS for frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173", "http://localhost:3000"],  # Vite default ports
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize historical memory agent
historical_memory = HistoricalMemory()

# Data directory path
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")

# Load data files
def load_json_file(filename: str) -> dict | list:
    """Load a JSON file from the data directory"""
    file_path = os.path.join(DATA_DIR, filename)
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"WARNING: {filename} not found in data directory")
        return {} if filename.endswith('.json') else []
    except json.JSONDecodeError as e:
        print(f"ERROR: Failed to parse {filename}: {e}")
        return {} if filename.endswith('.json') else []

# Load fire metadata once at startup
def get_fire_metadata() -> dict:
    """Get current fire name and location"""
    try:
        fire_perimeter = load_json_file("fire_perimeter.json")
        if fire_perimeter and "currentFire" in fire_perimeter:
            current_fire = fire_perimeter["currentFire"]
            center = current_fire.get("center", [39.52, -121.05])
            # Determine region from coordinates (simplified - lat, lon)
            lat, lon = center[0], center[1]
            if 39.0 <= lat <= 40.5 and -122.0 <= lon <= -120.0:
                region = "Northern California"
            elif 38.0 <= lat <= 39.0:
                region = "Central California"
            else:
                region = "California"
            return {
                "name": current_fire.get("name", "Unknown Fire"),
                "location": region,
                "coordinates": center
            }
    except Exception as e:
        print(f"WARNING: Could not load fire metadata: {e}")

    # Default fallback
    return {
        "name": "Cedar Ridge Fire",
        "location": "Northern California",
        "coordinates": [39.52, -121.05]
    }

FIRE_METADATA = get_fire_metadata()

# --- Request/Response Models ---

class FireAnalysisRequest(BaseModel):
    live_graph: dict[str, Any]
    wind_data: dict[str, Any]
    environment_data: dict[str, Any]
    infrastructure_data: dict[str, Any]
    previous_recommendation: dict[str, Any] | None = None

class FireAnalysisResponse(BaseModel):
    notifications: list[dict[str, Any]]
    recommendation: dict[str, Any]
    computed_physics: dict[str, Any]
    history_summary: str

# --- REST Endpoints ---

@app.get("/")
async def root():
    return {
        "service": "WildfireOS Backend",
        "status": "online",
        "version": "1.0.0"
    }

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

@app.get("/api/data/fire-perimeter")
async def get_fire_perimeter():
    """Get current fire perimeter, wind data, and wind forecast"""
    return load_json_file("fire_perimeter.json")

@app.get("/api/data/infrastructure")
async def get_infrastructure():
    """Get communities, firebreaks, and water resources"""
    return load_json_file("infrastructure.json")

@app.get("/api/data/terrain")
async def get_terrain():
    """Get fuel types, elevation points, power lines, and ridge lines"""
    return load_json_file("terrain.json")

@app.get("/api/data/historical-fires")
async def get_historical_fires():
    """Get historical fires data (frontend version with perimeters)"""
    return load_json_file("frontend_historical_fires.json")

@app.get("/api/data/all")
async def get_all_data():
    """Get all data in a single request"""
    return {
        "firePerimeter": load_json_file("fire_perimeter.json"),
        "infrastructure": load_json_file("infrastructure.json"),
        "terrain": load_json_file("terrain.json"),
        "historicalFires": load_json_file("frontend_historical_fires.json")
    }

@app.get("/api/data/live/{time_index}")
async def get_live_data(time_index: int):
    """Get timestamped live data file (1-5)"""
    if time_index < 1 or time_index > 5:
        return {"error": "time_index must be between 1 and 5"}
    return load_json_file(f"live_data_t{time_index}.json")

@app.post("/api/process-live-data/{time_index}")
async def process_live_data(time_index: int):
    """Process a timestamped data file through agents and generate notifications"""
    if time_index < 1 or time_index > 5:
        return {"error": "time_index must be between 1 and 5"}

    data = load_json_file(f"live_data_t{time_index}.json")
    if not data:
        return {"error": f"Could not load live_data_t{time_index}.json"}

    result = await notification_manager.process_timestamped_data(data)
    return result

@app.get("/api/notifications")
async def get_notifications(limit: int = 20, offset: int = 0):
    """Get agent-generated notifications (newest first)"""
    notifications = notification_manager.get_notifications(limit=limit, offset=offset)
    print(f"[API] Returning {len(notifications)} notifications (total stored: {len(notification_manager.notifications)})")
    return {
        "notifications": notifications,
        "total": len(notification_manager.notifications),
        "limit": limit,
        "offset": offset
    }

@app.get("/api/status")
async def get_status():
    """Get current notification system status"""
    return {
        "total_notifications": len(notification_manager.notifications),
        "total_recommendations": len(notification_manager.recommendations),

[truncated — 6643 more characters]
```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import 'leaflet/dist/leaflet.css'
import './index.css'
import './offline/offline.css'
import App from './App.jsx'
import OfflineProvider from './offline/OfflineProvider.jsx'
import { registerServiceWorker } from './offline/register-sw.js'

registerServiceWorker()

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <OfflineProvider>
      <App />
    </OfflineProvider>
  </StrictMode>,
)

```

### frontend/src/App.jsx

```javascript
import { useState, useCallback, useRef, useEffect } from "react";
import FireMap from "./components/FireMap";
import { useFireData } from "./hooks/useFireData";
import { dataService } from "./services/dataService";
import OfflineBanner from "./offline/OfflineBanner";
import "./App.css";

export default function App() {
  const { data, loading, error } = useFireData();
  const [aiInsights, setAiInsights] = useState([]);
  const [notifications, setNotifications] = useState([]);
  const [showAllNotifications, setShowAllNotifications] = useState(false);
  const [processingData, setProcessingData] = useState(false);
  const [recommendation, setRecommendation] = useState(null);
  const [dismissedRecommendation, setDismissedRecommendation] = useState(false);

  const [layers, setLayers] = useState({
    fuel: true,
    firebreaks: true,
    communities: true,
    water: true,
    terrain: true,
    powerlines: true,
    historical: true,
  });

  const [acknowledged, setAcknowledged] = useState({});
  const [showHistory, setShowHistory] = useState(false);
  const mapRef = useRef(null);
  const hasProcessed = useRef(false); // Track if we've already processed data

  // Process timestamped data through agents on mount
  useEffect(() => {
    async function processAllTimestampedData() {
      if (processingData || hasProcessed.current) return;
      hasProcessed.current = true;

      setProcessingData(true);
      console.log("🔥 Processing timestamped data through agents...");

      try {
        // Process timestamped files (5 files = first 4 minutes of fire)
        const numFilesToProcess = 5; // Full dataset: T+0min through T+4min
        console.log(`Processing ${numFilesToProcess} timestamped data files (1-minute intervals)...`);

        for (let i = 1; i <= numFilesToProcess; i++) {
          console.log(`Processing time index ${i}...`);

          try {
            console.log(`Sending file ${i} to backend...`);
            await dataService.processLiveData(i);
            console.log(`✅ Backend processed file ${i}`);

            // Fetch and update notifications after EACH file completes
            console.log(`📥 Fetching notifications from backend...`);
            const notifResponse = await dataService.fetchNotifications(100, 0);
            console.log(`📥 Received response:`, notifResponse);

            if (notifResponse && notifResponse.notifications) {
              setNotifications(notifResponse.notifications);
              console.log(`✅ File ${i} complete: ${notifResponse.notifications.length} total notifications now visible`);
            } else {
              console.warn(`⚠️ No notifications in response:`, notifResponse);
            }

            // Fetch latest recommendation
            try {
              console.log(`📥 Fetching latest recommendation...`);
              const rec = await dataService.fetchLatestRecommendation();
              if (rec && !rec.error) {
                setRecommendation(rec);
                setDismissedRecommendation(false); // Show new recommendation
                console.log(`✅ Recommendation loaded:`, rec);
                console.log(`   - Action: ${rec.action || rec.consideration}`);
                console.log(`   - Rationale: ${rec.rationale || 'N/A'}`);
                console.log(`   - Confidence: ${rec.confidence_score}%`);
              }
            } catch (err) {
              console.log(`⚠️ No recommendation yet:`, err.message);
            }
          } catch (err) {
            console.error(`❌ Error processing file ${i}:`, err);
            console.error(`Full error details:`, err);
            // Continue to next file even if this one fails
          }

          // Add delay between processing to avoid rate limits (each file makes ~5 API calls)
          if (i < numFilesToProcess) {
            console.log(`Waiting 15s before processing next minute to avoid rate limits...`);
            await new Promise(resolve => setTimeout(resolve, 15000)); // 15s delay between minutes
          }
        }

        console.log(`✅ All files processed successfully!`);

        // Fetch final notification count
        const finalNotifResponse = await dataService.fetchNotifications(100, 0);
        console.log(`📊 Final notification count: ${finalNotifResponse?.notifications?.length || 0}`);
      } catch (err) {
        console.error("❌ Error processing timestamped data:", err);
        console.error("Error stack:", err.stack);
      } finally {
        setProcessingData(false);
      }
    }

    // Only process once data is loaded and we haven't processed yet
    if (data && !processingData && !hasProcessed.current) {
      processAllTimestampedData();
    }
  }, [data, processingData]);

  const handleToggle = useCallback((key) => {
    setLayers((prev) => ({ ...prev, [key]: !prev[key] }));
  }, []);

  const handleAcknowledge = useCallback((e, insightId) => {
    e.stopPropagation();
    setAcknowledged((prev) => ({ ...prev, [insightId]: !prev[insightId] }));
  }, []);

  const handleFlyTo = useCallback((insight) => {
    if (mapRef.current) {
      mapRef.current.flyTo([insight.lat, insight.lng], 14, { duration: 1 });
    }
  }, []);

  const activeInsights = aiInsights.filter((i) => !acknowledged[i.id]);
  const acknowledgedInsights = aiInsights.filter((i) => acknowledged[i.id]);

  if (loading) {
    return (
      <div className="app" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
        <div style={{ textAlign: 'center' }}>
          <div style={{ fontSize: '24px', marginBottom: '10px' }}>🔥 Loading WildfireOS...</div>
          <div style={{ color: '#6b7280' }}>Fetching fire data from backend</div>
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="app" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
        <div style={{ textAlign: 'center', color: '#ef4444' }}>
          <div st
[truncated — 6191 more characters]
```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### api/health.js

```javascript
/** Vercel serverless: GET /api/health — lightweight connectivity check */
export default function handler(_req, res) {
  res.setHeader('Cache-Control', 'no-store');
  res.status(200).json({ ok: true, ts: Date.now() });
}

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="theme-color" content="#dc2626" />
    <link rel="manifest" href="/manifest.json" />
    <title>Wildfire Intel - Incident Command</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

[24 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]