# Project export: Lifeline AI

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: UC Berkeley AI Hackathon 2026
- Tagline: Lifeline AI is a multi-agent emergency platform that fuses fragmented disaster reports into unified incident clusters. It learns from past crises to help responders prioritize life-saving actions.
- Devpost: https://devpost.com/software/lifeline-ai-xwh4oi
- GitHub: https://github.com/jshimpi02/Lifeline-AI/
- Team: 0 GitHub contributor(s) — 

## Devpost submission (written by the team)

### Inspiration

Lifeline AI was inspired by a simple but urgent problem: during disasters, the people who need help the most are often hidden inside scattered information. Emergency alerts, news updates, social media posts, community reports, and voice calls all contain valuable signals, but they are usually fragmented and difficult to process quickly. In a crisis, every minute matters. A post saying “need oxygen,” a call saying “my grandmother is trapped,” and an alert about rising floodwater may all point to the same high-risk situation. We wanted to build a system that could help responders connect those dots faster. Our goal was to create an AI-powered emergency intelligence platform that helps emergency teams, hospitals, shelters, NGOs, and local communities understand where help is needed most urgently. What Lifeline AI Does Lifeline AI collects crisis signals from multiple sources and transforms them into structured emergency incidents. The system can take information from alerts, web sources, community posts, and voice reports, then organize it into a clear response workflow. The core idea is: fragmented crisis signals → structured incidents → related incident clusters → priority scores → actionable recommendations Instead of showing responders hundreds of disconnected reports, Lifeline AI groups related information and ranks incidents by urgency. For example, if multiple reports mention flooding, a trapped person, and an oxygen need near the same location, the system can merge those signals into one high-priority incident cluster. How We Built It We built Lifeline AI as a multi-agent system with a backend, frontend dashboard, memory layer, and incident-processing pipeline. The backend was built using FastAPI. We created endpoints to process incoming crisis reports, retrieve incident clusters, and generate a priority queue. Each incoming report is converted into a structured incident with fields such as event type, location, urgency, medical need, confidence, and source. We designed the system around several key components: Data ingestion agents for collecting crisis signals from alerts, web reports, social/community inputs, and voice calls using browserbase and deepgram APIs ASI:One / orchestration logic to coordinate the workflow and reason over incoming reports. Incident extraction to convert raw text into structured emergency data. Incident fusion logic to decide whether multiple reports refer to the same event. Redis memory to store incident clusters and retrieve past context. Priority scoring to rank incidents based on urgency, medical need, vulnerability, and repeated confirmation. Responder dashboard to show the most urgent incidents and recommended actions. The fusion logic checks multiple signals before merging reports: [ \text{Fusion Score} = w_1(\text{semantic similarity}) + w_2(\text{location distance}) + w_3(\text{time closeness}) + w_4(\text{event type match}) ] This helped us represent the real-world idea that two reports should only be merged if they are similar in meaning, close in location, close in time, and describe the same type of emergency. What We Learned We learned that the most important part of disaster AI is not just collecting information, but making it usable. A dashboard full of raw reports can still overwhelm responders. The real value comes from clustering, prioritization, and clear recommendations. We also learned how important reliability and explainability are in emergency systems. A system like this cannot simply output an answer; it must show why an incident was prioritized. For that reason, we focused on making the priority score understandable through factors like medical need, trapped people, rising water, and multiple related reports. Another key learning was that building with multiple agents requires clean data structures. Once we created a shared incident schema, it became much easier to connect different parts of the system together. Challenges We Faced One of the biggest challenges was deciding the right scope for a hackathon. Disaster response is a large and complex problem, so we focused on building a working slice of the system: report intake, incident extraction, clustering, priority scoring, Redis memory, and a dashboard. We also faced technical challenges connecting the backend, frontend, and Redis memory layer. Import paths, package setup, and local development issues took time to debug. Another challenge was designing the fusion logic in a way that was simple enough to implement quickly but still meaningful enough to demonstrate real impact. A major product challenge was keeping the system understandable. We wanted judges and users to immediately understand what Lifeline AI does, so we simplified the demo around one clear story: scattered crisis reports enter the system, Lifeline AI organizes them, and responders receive a prioritized action plan. Impact Lifeline AI can help several groups during disasters: Emergency responders can identify urgent rescue and medical cases faster. Emergency operations centers can use it as a live intelligence dashboard. NGOs and shelters can understand where supplies and support are needed. Hospitals and medical teams can detect urgent needs like oxygen, insulin, dialysis, or elderly care. Local communities can report emergencies through accessible text or voice inputs. By reducing confusion, grouping duplicate reports, and prioritizing urgent cases, Lifeline AI can help responders allocate limited resources more effectively. Conclusion Lifeline AI is our attempt to use AI for a real human problem: helping people get support faster when disasters strike. We built it as a multi-agent emergency intelligence platform that turns fragmented crisis information into structured incidents, clustered reports, priority scores, and actionable recommendations.

## README (from the GitHub repository)

# 🚨 Lifeline AI - Crisis Management Platform

> **Powered 100% by Fetch.ai's ASI:One API** - Intelligent emergency response with semantic clustering, geocoding, and AI-powered recommendations.

![Version](https://img.shields.io/badge/version-2.0-blue)
![ASI:One](https://img.shields.io/badge/AI-ASI%3AOne-green)
![Status](https://img.shields.io/badge/status-production--ready-success)

---

## 🎯 What is Lifeline AI?

A **multi-agent emergency intelligence platform** that transforms crisis reports into actionable insights:

- 📊 **Semantic Clustering** - Groups related incidents using AI embeddings
- 🗺️ **Map Visualization** - Interactive geographic view of all incidents
- 🎨 **Color-Coded Categories** - 6 distinct event types for quick identification
- 🤖 **AI Recommendations** - Context-aware emergency response plans
- 📍 **Geocoding** - Automatic location coordinate resolution
- ⚡ **Real-time Updates** - Auto-refresh every 10 seconds

---

## 🌟 Key Features

### 1. Intelligent Clustering
- Uses **ASI:One embeddings** for semantic similarity (not just keyword matching)
- Combines text similarity (70% threshold) + geographic proximity (5km radius)
- Automatically groups related incidents across different locations

### 2. Visual Dashboard
- **Priority Queue**: Ranked by urgency and impact
- **Map View**: Interactive Leaflet map with color-coded markers
- **Clusters Tab**: Category-based organization (Medical, Rescue, Environmental, etc.)

### 3. AI-Powered Insights
- **ASI:One Chat API** generates intelligent recommendations
- Context-aware action plans for emergency responders
- Fallback to rule-based logic if API unavailable

### 4. Color-Coded Categories
- 🔴 **Medical Emergency** - Oxygen, insulin, critical care
- 🟠 **Rescue Request** - Trapped, stuck, immediate help needed
- 🔵 **Flooding** - Water rising, flood warnings
- 🟡 **Power Outage** - Electrical infrastructure issues
- 🟢 **Shelter Update** - Housing, evacuation centers
- 🟣 **General Alert** - Other crisis situations

---

## 🚀 Quick Start

### Prerequisites
- Python 3.9+
- Node.js 18+
- Redis server
- ASI:One API key (already configured!)

### Installation

**1. Clone and setup backend:**
```bash
cd backend
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install -r requirements.txt
```

**2. Start Redis:**
```bash
redis-server
```

**3. Run backend:**
```bash
uvicorn main:app --reload --port 8000
```

**4. Setup frontend:**
```bash
cd frontend
npm install
npm run dev
```

**5. Open browser:**
```
http://localhost:5173
```

---

## 📖 Usage

### Submit a Crisis Report
1. Enter emergency description (e.g., "Grandmother needs oxygen, water rising")
2. Add location (e.g., "Berkeley, CA")
3. Click "Process Event"

### View Results
- **Priority Queue**: See ranked incidents by urgency
- **Map View**: Visualize incidents geographically
- **Clusters**: Browse by category (Medical, Rescue, etc.)

### Example Reports
```
Text: "My grandmother is trapped and needs oxygen urgently"
Location: "Oakland, CA"
→ Creates Medical Emergency cluster (Red)

Text: "Flooding on Main Street, multiple people stuck"
Location: "Berkeley, CA"
→ Creates Flooding cluster (Blue)

Text: "Power outage affecting hospital backup systems"
Location: "San Francisco, CA"
→ Creates Power Outage cluster (Yellow)
```

---

## 🏗️ Architecture

```
┌─────────────────────────────────────────┐
│         Frontend (React + Leaflet)      │
│  - Priority Queue  - Map View  - Clusters│
└──────────────────┬──────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│         FastAPI Backend                 │
│  - Event Processing  - Clustering       │
└──────────────────┬──────────────────────┘
                   │
        ┌──────────┴──────────┐
        ▼                     ▼
┌───────────────┐    ┌────────────────┐
│  ASI:One API  │    │  Redis Cache   │
│  - Embeddings │    │  - Clusters    │
│  - Chat/Recs  │    │  - Incidents   │
└───────────────┘    └────────────────┘
```

---

## 🔑 ASI:One Integration

### Single API Powers Everything!

**Embeddings** (`asi1-embedding`):
- Converts incident text to vector embeddings
- Enables semantic similarity comparison
- Powers intelligent clustering

**Chat Completions** (`asi1-mini`):
- Generates context-aware recommendations
- Analyzes cluster severity and urgency
- Provides actionable response plans

**No OpenAI Required!** 🎉

See [ASI_ONE_INTEGRATION.md](./ASI_ONE_INTEGRATION.md) for detailed API usage.

---

## 📁 Project Structure

```
lifeline-ai/
├── backend/
│   ├── main.py                      # FastAPI app
│   ├── schemas.py                   # Data models
│   ├── asi_client.py                # ASI:One chat API
│   ├── agents/
│   │   ├── asi_coordinator.py       # Event processing
│   │   └── resource_agent.py        # Resource finding
│   ├── processing/
│   │   ├── incident_extractor.py    # Text analysis + geocoding
│   │   ├── incident_fusion.py       # Semantic clustering
│   │   ├── semantic_clustering.py   # ASI:One embeddings
│   │   ├── geocoding.py             # Location → lat/lng
│   │   ├── priority_scorer.py       # Urgency calculation
│   │   └── recommendation_engine.py # AI recommendations
│   ├── memory/
│   │   ├── incident_memory.py       # Redis operations
│   │   └── redis_client.py          # Redis connection
│   └── browser/
│       └── browserbase_search.py    # Resource search
├── frontend/
│   ├── src/
│   │   ├── App.tsx                  # Main app + tabs
│   │   ├── components/
│   │   │   ├── EventInput.tsx       # Report submission
│   │   │   ├── PriorityQueue.tsx    # Ranked list
│   │   │   ├── ClusterMap.tsx       # Interactive map
│   │   │   └── ClustersView.tsx     # Category view
│   │   └── App.css                  # Styling
│   └── package.json
└── docs/
    ├── SETUP.md                     # Installation guide
    ├── IMPROVEMENTS_SUMMARY.md      # What's new
    └── ASI_ONE_INTEGRATION.md       # API details
```

---

## 🎨 Screenshots

### Priority Queue
Color-coded clusters ranked by urgency with AI recommendations

### Map View
Interactive Leaflet map showing all incidents geographically

### Clusters Tab
Organized by category: Medical, Rescue, Environmental, Infrastructure, Shelter, General

---

## 📊 Performance

- **Clustering**: ~1-2s per incident (ASI:One API)
- **Geocoding**: ~0.5-1s per location (Nominatim)
- **Map Rendering**: Optimized for 100+ incidents
- **Auto-refresh**: Every 10 seconds
- **Fallback**: Instant keyword-based clustering if API unavailable

---

## 🛠️ API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/events/process` | Submit new crisis report |
| `GET` | `/priority-queue` | Get sorted clusters by priority |
| `GET` | `/clusters` | Get all clusters (unsorted) |
| `DELETE` | `/memory` | Clear all clusters from Redis |

---

## 🧪 Testing

### Test Semantic Clustering
```bash
# Submit similar incidents
curl -X POST http://localhost:8000/events/process \
  -H "Content-Type: application/json" \
  -d '{"source":"test","text":"Grandmother needs oxygen","location":"Berkeley, CA"}'

curl -X POST http://localhost:8000/events/process \
  -H "Content-Type: application/json" \
  -d '{"source":"test","text":"Elderly person requires medical oxygen","location":"Berkeley, CA"}'

# Check clustering
curl http://localhost:8000/clusters
```

**Expected**: Both incidents in same cluster (semantic similarity detected)

---

## 🌍 Environment Variables

```bash
# Required
ASI_ONE_API_KEY=sk_...    # Fetch.ai ASI:One API key
REDIS_HOST=localhost
REDIS_PORT=6379

# Optional
BROWSERBASE_API_KEY=...   # For resource search
DEE

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 50 recognized source files, 83 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- Redis (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (61 of 61)

```
.gitignore
backend/app/__init__.py
backend/app/agents/__init__.py
backend/app/agents/asi_tool_orchestrator.py
backend/app/agents/coordinator_agent.py
backend/app/agents/extraction_agent.py
backend/app/agents/fusion_agent.py
backend/app/agents/memory_agent.py
backend/app/agents/prioritization_agent.py
backend/app/asi_client.py
backend/app/confidence.py
backend/app/config.py
backend/app/embeddings.py
backend/app/fusion.py
backend/app/historical_memory.py
backend/app/main.py
backend/app/models.py
backend/app/routes/__init__.py
backend/app/routes/clusters.py
backend/app/routes/events.py
backend/app/routes/map.py
backend/app/scoring.py
backend/app/services/clustering.py
backend/app/services/prioritization.py
backend/app/services/summarization.py
backend/app/storage.py
backend/app/triage.py
backend/app/utils/agent_logger.py
backend/app/utils/geo.py
backend/app/utils/similarity.py
backend/data/historical_disasters.json
backend/requirements.txt
backend/tests/sample_events.json
backend/tests/test_fusion.py
browserbase-agent/index.ts
browserbase-agent/package.json
browserbase-agent/tsconfig.json
docs/api_contract.md
docs/architecture.md
docs/schemas.md
frontend/.gitignore
frontend/AGENTS.md
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/page.tsx
frontend/CLAUDE.md
frontend/components/AgentActivityFeed.tsx
frontend/components/IncidentDetails.tsx
frontend/components/IncidentMap.tsx
frontend/components/LiveFeed.tsx
frontend/components/MapInner.tsx
frontend/components/PriorityQueue.tsx
frontend/components/VoiceReporter.tsx
frontend/eslint.config.mjs
frontend/lib/api.ts
frontend/next.config.ts
frontend/package.json
frontend/postcss.config.mjs
frontend/README.md
frontend/tsconfig.json
README.md
```

### Dependencies

- backend/requirements.txt: fastapi, numpy, pydantic, python-dotenv, uvicorn
- browserbase-agent/package.json: @browserbasehq/stagehand@^3.6.0, @types/node@^26.0.0, dotenv@^17.4.2, tsx@^4.22.4
- frontend/package.json: @tailwindcss/postcss@^4, @types/leaflet@^1.9.21, @types/node@^20, @types/react@^19, @types/react-dom@^19, axios@^1.18.0, eslint@^9, eslint-config-next@16.2.9, leaflet@^1.9.4, next@16.2.9, react@19.2.4, react-dom@19.2.4, react-leaflet@^5.0.0, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- Update README.md
- files

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

### frontend/CLAUDE.md

```markdown
@AGENTS.md

```

### frontend/AGENTS.md

```markdown
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

```

### backend/requirements.txt

```
fastapi
uvicorn
pydantic
python-dotenv
numpy
```

### browserbase-agent/package.json

```
{
  "name": "browserbase-agent",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "tsx index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "@browserbasehq/stagehand": "^3.6.0",
    "dotenv": "^17.4.2"
  },
  "devDependencies": {
    "@types/node": "^26.0.0",
    "tsx": "^4.22.4"
  }
}

```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "axios": "^1.18.0",
    "leaflet": "^1.9.4",
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "react-leaflet": "^5.0.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/leaflet": "^1.9.21",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### browserbase-agent/index.ts

```typescript
import "dotenv/config";

const LIFELINE_API =
  process.env.LIFELINE_API || "http://127.0.0.1:8000";

const SF_LAT = 37.7749;
const SF_LNG = -122.4194;

async function sendToLifeline(report: string) {
  const res = await fetch(`${LIFELINE_API}/agent/asi-process-report`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      raw_text: report,
      source: "weather",
    }),
  });

  const data = await res.json();
  console.log("✓ Sent weather report:", report);
  return data;
}

async function fetchWeatherData() {
  const url =
    `https://api.open-meteo.com/v1/forecast` +
    `?latitude=${SF_LAT}` +
    `&longitude=${SF_LNG}` +
    `&current=temperature_2m,precipitation,rain,wind_speed_10m` +
    `&hourly=precipitation_probability,precipitation,rain,wind_speed_10m` +
    `&forecast_days=1`;

  const res = await fetch(url);
  return res.json();
}

function generateWeatherReports(data: any): string[] {
  const reports: string[] = [];

  const current = data.current;
  const hourly = data.hourly;

  const temp = current.temperature_2m;
  const rain = current.rain || 0;
  const precipitation = current.precipitation || 0;
  const wind = current.wind_speed_10m || 0;

  const maxRain = Math.max(...(hourly.rain || [0]));
  const maxPrecipProb = Math.max(
    ...(hourly.precipitation_probability || [0])
  );
  const maxWind = Math.max(...(hourly.wind_speed_10m || [0]));

  reports.push(
    `Weather update for San Francisco: current temperature is ${temp}°C, wind speed is ${wind} km/h, rainfall is ${rain} mm.`
  );

  if (rain > 0 || precipitation > 0 || maxRain > 0.5) {
    reports.push(
      `Weather agent reports rainfall in San Francisco with possible flood risk near low-lying roads and intersections.`
    );
  }

  if (maxPrecipProb >= 50) {
    reports.push(
      `Weather agent reports ${maxPrecipProb}% precipitation probability today in San Francisco. Emergency teams should monitor flood-prone areas.`
    );
  }

  if (wind >= 25 || maxWind >= 30) {
    reports.push(
      `Weather agent reports strong winds in San Francisco. Potential risk for downed branches, traffic disruption, and power outages.`
    );
  }

  return reports;
}

async function runWeatherFeed() {
  console.log("Starting Weather Intelligence Agent...");

  const weather = await fetchWeatherData();
  const reports = generateWeatherReports(weather);

  for (const report of reports) {
    await sendToLifeline(report);
  }

  console.log("Weather feed complete.");
}

runWeatherFeed().catch(console.error);
```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}

```

### frontend/app/page.tsx

```typescript
"use client";

import { useEffect, useState } from "react";
import {
  getMapClusters,
  getMapEvents,
  processReport,
  getAgentLogs,
} from "@/lib/api";

import LiveFeed from "@/components/LiveFeed";
import IncidentMap from "@/components/IncidentMap";
import IncidentDetails from "@/components/IncidentDetails";
import AgentActivityFeed from "@/components/AgentActivityFeed";
import PriorityQueue from "@/components/PriorityQueue";

export default function Home() {
  const [criticalCluster, setCriticalCluster] = useState<any | null>(null);
  const [clusters, setClusters] = useState<any[]>([]);
  const [events, setEvents] = useState<any[]>([]);
  const [selectedCluster, setSelectedCluster] = useState<any | null>(null);
  const [feed, setFeed] = useState<any[]>([]);
  const [notification, setNotification] = useState<string | null>(null);
  const [agentLogs, setAgentLogs] = useState<any[]>([]);

  async function refresh() {
    try {
      const [c, e, logs] = await Promise.all([
        getMapClusters(),
        getMapEvents(),
        getAgentLogs(),
      ]);

      setClusters(Array.isArray(c) ? c : []);
      setEvents(Array.isArray(e) ? e : []);
      setAgentLogs(Array.isArray(logs) ? logs : []);

      const ranked = Array.isArray(c)
        ? [...c].sort((a, b) => b.priority_score - a.priority_score)
        : [];

      if (ranked.length > 0) {
        setSelectedCluster(ranked[0]);
      }

      const critical = ranked.find((cluster) => cluster.priority_score >= 85);

      if (critical) {
        setCriticalCluster(critical);
      }
    } catch (err) {
      console.error("refresh failed:", err);
    }
  }

  async function submitReport(text: string, source = "field") {
    const result = await processReport(text, source);

    setFeed((prev) => [
      {
        source,
        text,
        time: new Date().toLocaleTimeString(),
        result,
      },
      ...prev,
    ]);

    const fusionTool = result.tool_calls_executed?.find(
      (t: any) => t.tool === "submit_to_fusion"
    );

    const cluster = fusionTool?.result?.cluster;

    if (cluster) {
      setNotification(
        `${cluster.summary} | Priority ${cluster.priority_score}`
      );
      setSelectedCluster(cluster);
    }

    await refresh();
  }

  useEffect(() => {
    refresh();
    const interval = setInterval(refresh, 2000);
    return () => clearInterval(interval);
  }, []);

  return (
    <main className="min-h-screen bg-[#070b12] text-white">
      <div className="border-b border-white/10 px-6 py-4 flex justify-between items-center">
        <div>
          <h1 className="text-2xl font-bold">LIFELINE AI</h1>
          <p className="text-sm text-gray-400">
            AI-Powered Disaster Response
          </p>
        </div>

        <div className="text-green-400 text-sm">● SYSTEM OPERATIONAL</div>
      </div>

      {criticalCluster && (
        <div className="mx-6 mt-4 rounded-xl border border-red-500/60 bg-red-500/15 px-5 py-4 shadow-lg">
          <div className="flex items-center justify-between gap-4">
            <div>
              <p className="text-sm font-bold text-red-300">
                🚨 CRITICAL INCIDENT DETECTED
              </p>

              <h2 className="mt-1 text-lg font-bold">
                {criticalCluster.summary}
              </h2>

              <p className="mt-1 text-sm text-gray-300">
                Priority: {criticalCluster.priority_score} • Confidence:{" "}
                {criticalCluster.confidence} • Reports:{" "}
                {criticalCluster.reports_count}
              </p>
            </div>

            <div className="flex gap-2">
              <button
                onClick={() => setSelectedCluster(criticalCluster)}
                className="rounded-lg bg-white/10 px-4 py-2 text-sm hover:bg-white/20"
              >
                View Incident
              </button>

              <button
                onClick={() => setCriticalCluster(null)}
                className="rounded-lg bg-red-600 px-4 py-2 text-sm font-bold hover:bg-red-700"
              >
                Acknowledge
              </button>
            </div>
          </div>
        </div>
      )}

      {notification && (
        <div className="mx-6 mt-4 rounded-xl border border-red-500/40 bg-red-500/10 px-4 py-3">
          🚨 High Priority Update: {notification}
        </div>
      )}

      <div className="grid grid-cols-12 gap-4 p-6">
        <section className="col-span-3">
          <LiveFeed feed={feed} onSubmitReport={submitReport} />
        </section>

        <section className="col-span-6 space-y-4">
          <IncidentMap
            clusters={clusters}
            events={events}
            onSelectCluster={setSelectedCluster}
          />

          <div className="grid grid-cols-2 gap-4">
            <PriorityQueue clusters={clusters} />

            <div className="rounded-xl border border-white/10 bg-white/5 p-4 h-[230px]">
              <h2 className="font-bold mb-3">SYSTEM INTELLIGENCE</h2>

              <div className="space-y-2 text-sm text-gray-300">
                <p>✅ ASI:One selecting and calling tools</p>
                <p>✅ Browserbase watching web/weather sources</p>
                <p>✅ Fusion Engine merging related reports</p>
                <p>✅ Memory Agent retrieving historical incidents</p>
                <p>✅ Priority engine ranking active clusters</p>
              </div>
            </div>
          </div>
        </section>

        <section className="col-span-3 space-y-4">
          <IncidentDetails cluster={selectedCluster} />
          <AgentActivityFeed trace={agentLogs} />
        </section>
      </div>
    </main>
  );
}
```

### backend/app/main.py

```python
from fastapi import FastAPI
from uuid import uuid4

from app.models import IncidentEvent, IncidentCluster
from app.storage import events, clusters, cluster_events
from app.fusion import fusion_score, fusion_breakdown
from app.scoring import calculate_priority, severity_from_priority, recommended_action
from app.triage import is_actionable, actionability_score
from app.historical_memory import find_similar_history
from app.confidence import calculate_cluster_confidence, confidence_reasons
from app.routes.map import router as map_router
from app.agents.coordinator_agent import CoordinatorAgent
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
from app.agents.asi_tool_orchestrator import ASIToolOrchestrator
from app.routes.map import router as map_router
from app.storage import agent_logs




app = FastAPI(title="Lifeline AI Incident Fusion Engine")
app.include_router(map_router)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(map_router)
FUSION_THRESHOLD = 0.75

asi_tool_orchestrator = ASIToolOrchestrator()
coordinator_agent = CoordinatorAgent()


class AgentReportRequest(BaseModel):
    raw_text: str
    source: str = "web"

@app.get("/")
def root():
    return {"message": "Lifeline AI Incident Fusion Engine running"}


@app.post("/events")
def add_event(event: IncidentEvent):
    if event.id in events:
        return {
            "status": "duplicate_ignored",
            "event_id": event.id,
            "message": "Event already exists"
        }
    if not is_actionable(event):
        return {
            "status": "quarantined",
            "event_id": event.id,
            "actionability_score": actionability_score(event),
            "message": "Event not actionable enough for fusion"
        }
    events[event.id] = event

    best_cluster = None
    best_score = 0

    for cluster in clusters.values():
        score = fusion_score(event, cluster)
        if score > best_score:
            best_score = score
            best_cluster = cluster

    if best_cluster and best_score >= FUSION_THRESHOLD:
        cluster_id = best_cluster.cluster_id

        if event.id not in cluster_events[cluster_id]:
            cluster_events[cluster_id].append(event.id)

        updated_cluster = rebuild_cluster(cluster_id)
        clusters[cluster_id] = updated_cluster

        return {
            "status": "merged",
            "cluster_id": cluster_id,
            "fusion_score": best_score,
            "cluster": updated_cluster
        }

    cluster_id = f"cluster_{str(uuid4())[:8]}"
    cluster_events[cluster_id] = [event.id]

    new_cluster = IncidentCluster(
        cluster_id=cluster_id,
        event_type=event.event_type,
        summary=event.raw_text,
        event_ids=[event.id],
        lat=event.location.lat,
        lng=event.location.lng,
        severity=event.urgency,
        priority_score=event.urgency * 10,
        confidence=event.confidence,
        reports_count=1,
        recommended_action=recommended_action(event.urgency * 10, event.event_type),
        last_updated=event.timestamp
    )

    clusters[cluster_id] = new_cluster

    return {
        "status": "created",
        "cluster_id": cluster_id,
        "fusion_score": best_score,
        "fusion_reason": "Created new cluster because no matching cluster existed",
        "cluster": new_cluster
    }


def rebuild_cluster(cluster_id: str) -> IncidentCluster:
    ids = cluster_events[cluster_id]
    cluster_event_list = [events[eid] for eid in ids]

    priority = calculate_priority(cluster_event_list)
    severity = severity_from_priority(priority)

    cluster_confidence = calculate_cluster_confidence(cluster_event_list)

    lat_values = [e.location.lat for e in cluster_event_list if e.location.lat is not None]
    lng_values = [e.location.lng for e in cluster_event_list if e.location.lng is not None]

    lat = sum(lat_values) / len(lat_values) if lat_values else None
    lng = sum(lng_values) / len(lng_values) if lng_values else None

    event_types = [e.event_type for e in cluster_event_list]
    event_type = max(set(event_types), key=event_types.count)

    summary = generate_cluster_summary(cluster_event_list)

    return IncidentCluster(
        cluster_id=cluster_id,
        event_type=event_type,
        summary=summary,
        event_ids=ids,
        lat=lat,
        lng=lng,
        severity=severity,
        priority_score=priority,
        confidence=cluster_confidence,
        reports_count=len(ids),
        recommended_action=recommended_action(priority, event_type),
        last_updated=max(e.timestamp for e in cluster_event_list)
    )


def generate_cluster_summary(cluster_event_list: list[IncidentEvent]) -> str:
    event_type = cluster_event_list[0].event_type
    location = cluster_event_list[0].location.text
    count = len(cluster_event_list)

    total_people = sum(e.people_affected for e in cluster_event_list)

    return (
        f"{count} reports related to {event_type.replace('_', ' ')} "
        f"near {location}. Estimated {total_people} people affected."
    )


@app.get("/clusters")
def get_clusters():
    return list(clusters.values())


@app.get("/clusters/{cluster_id}")
def get_cluster(cluster_id: str):
    cluster = clusters.get(cluster_id)

    if not cluster:
        return {"error": "Cluster not found"}

    related_events = [events[eid] for eid in cluster_events[cluster_id]]

    return {
        "cluster": cluster,
        "events": related_events
    }


@app.get("/clusters/{cluster_id}/explain")
def explain_cluster(cluster_id: str):
    cluster = clusters.get(cluster_id)

    if not cluster:
        return {"error": "Cluster not found"}

    related_events = [events[eid] for eid in cluster_events[cluster_id]]

    reasoning = []

    max_urgency = max(e.urgency for e in related_events)
    total_people = sum(e.people_
[truncated — 5037 more characters]
```

### frontend/next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  async rewrites() {
    return [
      {
        source: "/api/:path*",
        destination: "http://127.0.0.1:8000/:path*",
      },
    ];
  },
};

export default nextConfig;
```

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