# Project export: Crisismap

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: Cal Hacks 12.0
- Tagline: Empowering governments to save lives through real-time disaster resource management.
- Devpost: https://devpost.com/software/crisismap-3zihl8
- GitHub: https://github.com/Tanisha928/crisismap
- Video: https://www.youtube.com/embed/FURLdsslzhw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Tanisha928 (3 commits)

## Devpost submission (written by the team)

### Overview

Overview CrisisMap is an intelligent disaster management dashboard designed to empower governments and emergency response teams with real-time insights for efficient resource allocation and strategic recovery planning. The platform ensures that critical aid—such as food, water, medical supplies, and shelter—reaches affected areas swiftly and effectively as disasters unfold. According to the United Nations Office for Disaster Risk Reduction (UNDRR), natural disasters affected over 185 million people globally in 2024, causing an estimated $280 billion in economic losses. Timely decision-making and data-driven coordination can reduce disaster-related casualties by up to 40%, and this is where CrisisMap plays a transformative role. Features -Resource Management: Streamline the allocation and monitoring of essential resources such as food, water, medical aid, and logistics. CrisisMap’s analytics engine ensures up to 30% faster resource deployment compared to traditional manual coordination. -Emergency Services Locator: Provides real-time information on the nearest shelters, ambulances, police stations, and hospitals, enabling rapid routing for both responders and civilians. -Recovery Planning: Facilitates data-driven recovery strategies to reduce long-term damage and speed up rehabilitation by identifying priority zones and estimating needs based on population density and impact severity. -Real-time Dashboard: A unified platform offering live updates, heat maps, and predictive analytics to support informed decision-making and efficient crisis management. Goal The primary goal of CrisisMap is to minimize human and economic losses during natural disasters through proactive planning, real-time intelligence, and optimized resource management. By leveraging technology, data visualization, and automation, CrisisMap aims to help governments respond substantially faster, save more lives, and build resilient communities for the future.

## README (from the GitHub repository)

# CrisisMap React + TypeScript + Tailwind Starter

A polished starter for your disaster resource‑management UI. Built with **Vite + React + TypeScript + Tailwind**, prewired layout and components matching the demo plan.

## ✨ Features
- Clean 3‑panel layout: **Layers** · **Map** · **Copilot**
- Header KPIs + Dispatch Dock
- Sample data + types ready to bind to your backend
- ESLint + Prettier
- React Query + Zustand

## 🚀 Quick start
```bash
pnpm i   # or npm i / yarn
pnpm dev # or npm run dev
```

Open http://localhost:5173

## 🔧 Configure API
Copy `.env.example` → `.env` and set:
```
VITE_API_BASE_URL=http://localhost:8787
```

Use `src/lib/config.ts` to read env values.

## 🗺️ Map integration
Replace the `MapCanvas` placeholder with MapLibre:
- `npm i maplibre-gl`
- Initialize map in `useEffect`, draw tiles/routes from your data.

## 📦 Scripts
- `dev` – local dev server
- `build` – typecheck + production build
- `preview` – preview static build
- `lint` – ESLint
- `format` – Prettier write

## 🧩 Structure
```
src/
  components/   # UI components
  lib/          # types, config, sample data
  App.tsx
  main.tsx
```

## 📝 Notes
- Keep components small and stateless; use React Query for data.
- Tailwind utility classes give you quick, modern UI without extra libs.
- Swap sample data with your live endpoints when ready.


## Detected evidence (automated analysis)

Indexed codebase: 44 recognized source files, 209 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (54 of 54)

```
backend/.env
backend/.env.example
backend/agent.py
backend/app.py
backend/config.py
backend/db/database.py
backend/db/models.py
backend/letta.py
backend/models/incident.py
backend/models/resource.py
backend/models/response.py
backend/models/zone.py
backend/openstreetmap.py
backend/pop.py
backend/population_density.py
backend/requirements.txt
backend/routes/incident_routes.py
backend/routes/population_routes.py
backend/routes/resource_routes.py
backend/routes/stats_routes.py
backend/routes/zone_routes.py
backend/services/allocation_service.py
backend/services/letta_service.py
backend/services/population_service.py
backend/services/resource_service.py
backend/simulate.py
backend/utils/cache.py
backend/utils/geospatial.py
backend/utils/priority.py
frontend/.env.example
frontend/.eslintrc.cjs
frontend/.gitignore
frontend/.npmrc
frontend/.prettierrc
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/src/App.tsx
frontend/src/components/CopilotPanel.tsx
frontend/src/components/DispatchDock.tsx
frontend/src/components/HeaderKpis.tsx
frontend/src/components/LayerSidebar.tsx
frontend/src/components/MapCanvas.tsx
frontend/src/components/StatsOverview.tsx
frontend/src/index.css
frontend/src/lib/config.ts
frontend/src/lib/sampleData.ts
frontend/src/lib/types.ts
frontend/src/main.tsx
frontend/tailwind.config.js
frontend/tsconfig.json
frontend/vite.config.ts
pop_endpoint.py
README.md
```

### Dependencies

- backend/requirements.txt: aiofiles@==23.2.1, alembic@==1.13.1, fastapi@==0.109.0, letta-client@==0.1.0, passlib[bcrypt]@==1.7.4, psycopg2-binary@==2.9.9, pydantic@==2.5.3, pydantic-settings@==2.1.0, python-dotenv@==1.0.0, python-jose[cryptography]@==3.3.0, python-multipart@==0.0.6, redis@==5.0.1, requests@==2.31.0, sqlalchemy@==2.0.25, uvicorn[standard]@==0.27.0
- frontend/package.json: @tanstack/react-query@^5.51.1, @types/react@^18.3.3, @types/react-dom@^18.3.0, @typescript-eslint/eslint-plugin@^7.18.0, @typescript-eslint/parser@^7.18.0, @vitejs/plugin-react@^4.3.2, autoprefixer@^10.4.20, clsx@^2.1.1, eslint@8.57.0, eslint-config-prettier@^9.1.0, lucide-react@^0.548.0, maplibre-gl@^5.9.0, postcss@^8.4.47, prettier@^3.3.3, react@^18.3.1, react-dom@^18.3.1, tailwind-merge@^2.5.2, tailwindcss@^3.4.14, typescript@^5.6.3, vite@^5.4.10, zustand@^4.5.4

### Recent commits (newest first)

- Remove sensitive API keys from .env file
- frontend and backend
- initial setup

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

### backend/requirements.txt

```
fastapi==0.109.0
uvicorn[standard]==0.27.0
pydantic==2.5.3
pydantic-settings==2.1.0
python-dotenv==1.0.0
requests==2.31.0
letta-client==0.1.0
sqlalchemy==2.0.25
psycopg2-binary==2.9.9
alembic==1.13.1
redis==5.0.1
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.6
aiofiles==23.2.1
```

### frontend/package.json

```
{
  "name": "crisismap-react-ts-tailwind-starter",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview",
    "lint": "eslint .",
    "format": "prettier --write ."
  },
  "dependencies": {
    "@tanstack/react-query": "^5.51.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.548.0",
    "maplibre-gl": "^5.9.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "tailwind-merge": "^2.5.2",
    "zustand": "^4.5.4"
  },
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@typescript-eslint/eslint-plugin": "^7.18.0",
    "@typescript-eslint/parser": "^7.18.0",
    "@vitejs/plugin-react": "^4.3.2",
    "autoprefixer": "^10.4.20",
    "eslint": "8.57.0",
    "eslint-config-prettier": "^9.1.0",
    "postcss": "^8.4.47",
    "prettier": "^3.3.3",
    "tailwindcss": "^3.4.14",
    "typescript": "^5.6.3",
    "vite": "^5.4.10"
  }
}

```

### backend/app.py

```python
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
import time
import logging

from config import settings
from routes import incident_routes, population_routes, resource_routes, zone_routes, stats_routes
from db.database import engine, Base

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Startup and shutdown events"""
    # Startup
    logger.info("Starting Disaster Management API...")
    
    # Create database tables
    Base.metadata.create_all(bind=engine)
    logger.info("Database tables created")
    
    yield
    
    # Shutdown
    logger.info("Shutting down Disaster Management API...")


# Create FastAPI app
app = FastAPI(
    title="Disaster Management API",
    description="Multi-agent disaster response system with real-time resource allocation",
    version="1.0.0",
    lifespan=lifespan
)

# CORS Middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.allowed_origins_list,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# Request timing middleware
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response


# Exception handlers
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    logger.error(f"Global exception: {str(exc)}", exc_info=True)
    return JSONResponse(
        status_code=500,
        content={
            "ok": False,
            "error": "Internal server error",
            "detail": str(exc) if settings.DEBUG else "An error occurred"
        }
    )


# Health check endpoint
@app.get("/")
async def root():
    return {
        "message": "Disaster Management API",
        "version": "1.0.0",
        "status": "operational"
    }


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


# Include routers
app.include_router(incident_routes.router, prefix="/api/incident", tags=["Incidents"])
app.include_router(population_routes.router, prefix="/api/population", tags=["Population"])
app.include_router(resource_routes.router, prefix="/api/resources", tags=["Resources"])
app.include_router(zone_routes.router, prefix="/api/zones", tags=["Zones"])
app.include_router(stats_routes.router, prefix="/api/stats", tags=["Statistics"])


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(
        "app:app",
        host=settings.HOST,
        port=settings.PORT,
        reload=settings.DEBUG
    )
```

### frontend/src/main.tsx

```typescript
import 'maplibre-gl/dist/maplibre-gl.css'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
import './index.css'

const qc = new QueryClient()

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <QueryClientProvider client={qc}>
      <App />
    </QueryClientProvider>
  </StrictMode>
)

```

### frontend/src/App.tsx

```typescript
import { useState } from 'react'
import HeaderKpis from './components/HeaderKpis'
import LayerSidebar from './components/LayerSidebar'
import MapCanvas from './components/MapCanvas'
import CopilotPanel from './components/CopilotPanel'
import DispatchDock from './components/DispatchDock'
import StatsOverview from './components/StatsOverview'

export default function App() {
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
  const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true)
  const [isRightPanelOpen, setIsRightPanelOpen] = useState(true)

  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 text-slate-100">
      <HeaderKpis 
        onMenuToggle={() => setIsMobileMenuOpen(!isMobileMenuOpen)} 
        isMobileMenuOpen={isMobileMenuOpen}
      />
      
      {/* Stats Overview - New Component */}
      <StatsOverview />
      
      <div className="relative">
        {/* Main Content Grid */}
        <div className="grid lg:grid-cols-[320px_1fr_380px] gap-4 p-3 sm:p-4">
          {/* Left Sidebar */}
          <div className={`
            ${isLeftSidebarOpen ? 'block' : 'hidden'} 
            lg:block
            ${isMobileMenuOpen ? 'fixed inset-0 z-40 bg-slate-950/95 backdrop-blur-lg' : ''}
          `}>
            <LayerSidebar onClose={() => setIsMobileMenuOpen(false)} />
          </div>
          
          {/* Map Canvas */}
          <div className="relative">
            <MapCanvas />
            
            {/* Toggle Buttons for Mobile */}
            <div className="lg:hidden absolute top-4 left-4 right-4 flex justify-between gap-2 z-10">
              <button
                onClick={() => setIsLeftSidebarOpen(!isLeftSidebarOpen)}
                className="px-3 py-2 bg-slate-900/95 backdrop-blur-sm rounded-lg border border-slate-700 text-sm font-medium hover:bg-slate-800 transition-colors"
              >
                {isLeftSidebarOpen ? 'Hide' : 'Show'} Layers
              </button>
              <button
                onClick={() => setIsRightPanelOpen(!isRightPanelOpen)}
                className="px-3 py-2 bg-slate-900/95 backdrop-blur-sm rounded-lg border border-slate-700 text-sm font-medium hover:bg-slate-800 transition-colors"
              >
                {isRightPanelOpen ? 'Hide' : 'Show'} Copilot
              </button>
            </div>
          </div>
          
          {/* Right Panel */}
          <div className={`
            ${isRightPanelOpen ? 'block' : 'hidden'} 
            lg:block
          `}>
            <CopilotPanel />
          </div>
        </div>
      </div>
      
      <DispatchDock />
    </div>
  )
}
```

### pop_endpoint.py

```python
# pop_endpoint.py
# Run:  uvicorn pop_endpoint:app --host 0.0.0.0 --port 8000
# Deps: pip install fastapi uvicorn numpy
# Optional for real data: pip install rasterio
# Env: export POP_TIF_PATH=/path/to/worldpop_2025_1km.tif

import os
import math
from typing import Optional, Tuple, Dict, Any
from fastapi import FastAPI, HTTPException, Query

app = FastAPI(title="Population Service")

POP_TIF_PATH = os.getenv("POP_TIF_PATH")

def _approx_area_km2(min_lat: float, min_lon: float, max_lat: float, max_lon: float) -> float:
    # crude but fine for small boxes
    mean_lat = (min_lat + max_lat) / 2.0
    km_per_deg_lat = 111.32
    km_per_deg_lon = 111.32 * math.cos(math.radians(mean_lat))
    return abs(max_lat - min_lat) * km_per_deg_lat * abs(max_lon - min_lon) * km_per_deg_lon

def _label_from_density(density_pp_km2: float) -> str:
    # tweak thresholds to your taste
    if density_pp_km2 >= 1000: return "very_dense"
    if density_pp_km2 >= 300:  return "dense"
    if density_pp_km2 >= 100:  return "urban"
    if density_pp_km2 >= 25:   return "suburban"
    if density_pp_km2 >= 5:    return "rural"
    return "sparse"

def _sum_from_tif(min_lat: float, min_lon: float, max_lat: float, max_lon: float) -> Optional[int]:
    if not POP_TIF_PATH:
        return None
    try:
        import numpy as np
        import rasterio
        from rasterio.windows import from_bounds
        from rasterio.warp import transform_bounds

        with rasterio.open(POP_TIF_PATH) as ds:
            # Client supplies WGS84 bbox
            bbox_src = (min_lon, min_lat, max_lon, max_lat)  # (minx, miny, maxx, maxy) in EPSG:4326
            # Reproject bbox to raster CRS if needed
            if str(ds.crs).lower() not in ("epsg:4326", "wgs84"):
                minx, miny, maxx, maxy = transform_bounds("EPSG:4326", ds.crs, *bbox_src, densify_pts=21)
            else:
                minx, miny, maxx, maxy = bbox_src

            window = from_bounds(minx, miny, maxx, maxy, transform=ds.transform)
            data = ds.read(1, window=window, masked=True)
            total = float(data.sum())
            if math.isnan(total):
                return 0
            return int(round(total))
    except Exception:
        # If raster reading fails for any reason, let caller fall back to heuristic
        return None

def _bbox_from_point(lat: float, lon: float, radius_km: float) -> Tuple[float, float, float, float]:
    dlat = radius_km / 111.32
    dlon = radius_km / (111.32 * math.cos(math.radians(lat)))
    return (lat - dlat, lon - dlon, lat + dlat, lon + dlon)

@app.get("/pop")
def population(
    # Use either lat/lon(+radius_km) OR a bbox
    lat: Optional[float] = Query(None),
    lon: Optional[float] = Query(None),
    radius_km: Optional[float] = Query(3.0),
    min_lat: Optional[float] = Query(None),
    min_lon: Optional[float] = Query(None),
    max_lat: Optional[float] = Query(None),
    max_lon: Optional[float] = Query(None),
) -> Dict[str, Any]:
    # Resolve bbox
    if lat is not None and lon is not None:
        if radius_km is None or radius_km <= 0:
            raise HTTPException(400, "radius_km must be > 0 when using lat/lon")
        min_lat_v, min_lon_v, max_lat_v, max_lon_v = _bbox_from_point(lat, lon, radius_km)
    elif None not in (min_lat, min_lon, max_lat, max_lon):
        min_lat_v, min_lon_v, max_lat_v, max_lon_v = min_lat, min_lon, max_lat, max_lon
    else:
        raise HTTPException(400, "Provide either lat/lon (+radius_km) or a full bbox min_lat/min_lon/max_lat/max_lon")

    if min_lat_v >= max_lat_v or min_lon_v >= max_lon_v:
        raise HTTPException(400, "Invalid bbox (min must be < max)")

    # Try real raster first
    total = _sum_from_tif(min_lat_v, min_lon_v, max_lat_v, max_lon_v)
    source = "WorldPop 2025 1km" if total is not None else "heuristic"

    # Heuristic fallback (toy): density ~ 200 ppl/km² near cities, ~20 elsewhere
    if total is None:
        area = _approx_area_km2(min_lat_v, min_lon_v, max_lat_v, max_lon_v)
        # pretend urban-ish if within ±2° of lat 37.7/lon -122.4 (SF demo) else rural-ish
        near_sf = (abs((min_lat_v + max_lat_v)/2 - 37.75) < 2.0 and abs((min_lon_v + max_lon_v)/2 + 122.45) < 2.0)
        density = 300 if near_sf else 20
        total = int(round(area * density))

    area_km2 = max(_approx_area_km2(min_lat_v, min_lon_v, max_lat_v, max_lon_v), 1e-6)
    density = total / area_km2
    label = _label_from_density(density)

    return {
        "population_estimate": int(total),
        "population_label": label,
        "source": source
    }

```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/vite.config.ts

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

export default defineConfig({
  plugins: [react()],
  server: { port: 5173 },
  preview: { port: 5174 }
})

```

### 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" />
    <title>CrisisMap Ops UI</title>
  </head>
  <body class="bg-slate-950 text-slate-100">
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          500: '#0ea5e9',
          600: '#0284c7',
          950: '#0b1220'
        }
      },
      boxShadow: {
        soft: '0 6px 24px rgba(0,0,0,0.25)'
      },
      borderRadius: {
        '2xl': '1rem'
      }
    }
  },
  plugins: []
}

```

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