AirMedSim is built as a modern web application with three main components:
- Simulation Engine (Python/SimPy) - Discrete-event simulation core
- Backend API (FastAPI) - RESTful API serving hospital data and simulation results
- Frontend (React/TypeScript) - Interactive visualization and playback
┌─────────────────┐
│ React Frontend│
│ (Port 5173) │
│ │
│ - Interactive │
│ Hospital Map │
│ - Simulation │
│ Playback │
│ - Metrics │
│ Dashboard │
└────────┬────────┘
│ HTTP/REST
▼
┌─────────────────┐
│ FastAPI Backend│
│ (Port 8000) │
│ │
│ - Hospital API │
│ - Simulation │
│ Results API │
│ - Config API │
└────────┬────────┘
│
▼
┌─────────────────┐
│ SimPy Engine │
│ │
│ - Discrete- │
│ Event Sim │
│ - Resource │
│ Management │
│ - Event Queue │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Data Layer │
│ │
│ - CMS Hospital │
│ Data │
│ - Simulation │
│ Results │
│ - Cache (JSON) │
└─────────────────┘
Key Technologies:
- React 18 with functional components and hooks
- TypeScript 5.6 for type safety
- Vite for fast development and optimized builds
- Tailwind CSS v3 for styling with custom theme
- @vis.gl/react-google-maps for mapping
- Recharts for data visualization
- Playwright for E2E testing
Components:
App.tsx- Main application container with hospital data loadingMap.tsx- Google Maps integration with hospital markersSimulationViewer.tsx- Simulation playback containerPlaybackControls.tsx- Play/pause/speed controlsMetricsDashboard.tsx- Real-time metrics displayAnimatedAmbulance.tsx- Ambulance marker with status
Custom Hooks:
useSimulationPlayer.ts- Manages simulation playback state and animation
State Management:
- React hooks (useState, useEffect, useCallback)
- Props drilling for component communication
- Local state for UI interactions
API Endpoints:
GET /api/hospitals
- Returns list of all Montana hospitals with geocoded locations
- Cached in data/hospitals_cache.json
- Response: { hospitals: Hospital[], count: number }
GET /api/config/{simulation_name}
- Returns simulation configuration
- Includes ambulance fleet definition
- Response: { ambulances: Ambulance[], ... }
GET /api/results/{filename}
- Returns simulation results
- Includes events and summary statistics
- Response: { summary: Summary, events: Event[] }
POST /api/simulation/run
- Runs a new simulation with provided configuration
- Returns simulation results with events and KPIs
GET /api/simulation/runs
- Lists all saved simulation runs from database
- Returns run metadata, status, and summary
GET /api/reports/executive/{run_id}
- Downloads executive summary PDF report
- Includes KPI charts and recommendations
GET /api/reports/detailed/{run_id}
- Downloads detailed analysis PDF report
- Includes transfer-level data and timing breakdowns
POST /api/reports/comparison
- Downloads comparison PDF for multiple simulation runs
- Body: list of run IDs to compare
Data Models:
class Hospital(BaseModel):
id: str
name: str
latitude: float
longitude: float
city: str
type: str
total_beds: int
has_emergency: bool
rating: Optional[float]
class Ambulance(BaseModel):
id: str
base_hospital: str
vehicle_type: str # 'BLS' | 'ALS' | 'CCT' | 'Air'
capacity: int
speed_mph: float # 45 BLS, 55 ALS, 50 CCT, 150 Air
range_miles: Optional[float] # Air ambulances: 175 miles
class Event(BaseModel):
time: float
type: str
ambulance_id: str
details: dictCORS Configuration:
- Allows localhost:5173 for development
- Configurable for production deployments
Core Components:
# Environment
env = simpy.Environment()
# Resources
ambulances = [simpy.Resource(env, capacity=1) for _ in fleet]
hospital_beds = {h.id: simpy.Container(env, capacity=h.beds, init=h.beds) for h in hospitals}
# Processes
def patient_generator(env, hospitals, ambulances):
"""Generates patient transfer requests"""
while True:
yield env.timeout(random.expovariate(arrival_rate))
env.process(handle_transfer(env, ...))
def handle_transfer(env, patient, hospitals, ambulances):
"""Manages individual transfer lifecycle"""
# 1. Request ambulance
# 2. Travel to origin
# 3. Load patient
# 4. Travel to destination
# 5. Unload patient
# 6. Release ambulanceEvent Collection:
- All state changes logged with timestamps
- Events stored in list for post-processing
- Summary statistics calculated after simulation
Hospital Data Collection:
- Download from CMS Hospital Compare dataset
- Filter for Montana hospitals
- Geocode addresses using Google Maps API
- Cache results in JSON format
- Validate data quality
Caching Strategy:
- Hospital data cached for 30 days
- Simulation results stored permanently
- Configuration files version controlled
User → Frontend → Backend → Cache Check → CMS API → Geocoding → Cache → Response
User Config → SimPy Engine → Event Generation → Results JSON → Backend → Frontend Playback
Simulation Results → useSimulationPlayer Hook → State Updates → Component Re-renders → Animated Map
Terminal 1: cd web/backend && poetry run uvicorn api:app --reload
Terminal 2: cd web/frontend && npm run dev
Docker Compose:
- Nginx reverse proxy
- FastAPI backend (Gunicorn + Uvicorn workers)
- React frontend (static build)
- PostgreSQL database with asyncpg
- OSRM routing server (Montana road network)
Why SimPy?
- Pure Python discrete-event simulation
- Excellent for queueing systems
- Well-documented with healthcare examples
- Easy integration with Python ecosystem
Why FastAPI?
- Fast, modern Python web framework
- Automatic API documentation (OpenAPI/Swagger)
- Type hints and validation with Pydantic
- Async support for scalability
Why React + TypeScript?
- Component-based architecture
- Strong typing prevents bugs
- Large ecosystem of libraries
- Excellent developer experience
Why Tailwind CSS?
- Utility-first approach
- Consistent design system
- Minimal CSS bundle size
- Easy customization
Why @vis.gl/react-google-maps?
- Official Google Maps integration
- React-friendly API
- Advanced features (deck.gl integration)
- Better than previous react-google-maps library
Frontend:
- Marker clustering for dense hospital networks
- Virtualized scrolling for event feeds
- WebSocket updates instead of polling (planned)
- Lazy loading of simulation data
Backend:
- Response caching with TTL
- Async request handling
- Efficient JSON serialization
- Connection pooling (future with PostgreSQL)
Simulation:
- Efficient event queue (heapq in SimPy)
- Minimal state tracking
- Batch result processing
- Configurable time step granularity
API Security:
- CORS whitelisting
- Input validation with Pydantic
- No sensitive data in responses
- Rate limiting (future)
Data Privacy:
- Synthetic patient data only
- No PHI in current implementation
- Geographic data publicly available
- Simulation results anonymized
Frontend:
- E2E tests with Playwright
- Component tests (planned)
- Visual regression tests
Backend:
- Unit tests with pytest
- API integration tests
- Data validation tests
Simulation:
- Unit tests for core logic
- Validation against analytical models
- Sensitivity analysis
Completed Architecture Features:
-
Database Integration ✅
- PostgreSQL for persistent storage with asyncpg
- Event sourcing for simulation history
- Materialized views for analytics (mv_simulation_kpis, mv_hospital_utilization)
- Alembic migrations for schema versioning
-
Weather Integration ✅
- NOAA API client for real-time weather data
- Travel time multipliers (wet 1.15x, snow 1.5x, ice 2.0x)
- Air transport restrictions (icing, visibility, wind)
-
Advanced Analytics ✅
- Time-series demand forecasting (SMA, ETS, ARIMA)
- Statistical comparison with K-S tests
- Multi-objective policy optimization
- PDF report generation (WeasyPrint + Jinja2)
-
Multiple Resource Types ✅
- BLS/ALS/CCT ground ambulances
- Air ambulances with operational radius
- Capability-based dispatch algorithms
Planned Architecture Changes:
-
Real-time Collaboration
- WebSocket for live simulation streaming
- Multi-user simulation runs
- Shared scenarios
-
Advanced ML Models
- LSTM for complex seasonal patterns
- Anomaly detection for demand spikes
- Automated parameter calibration
-
Microservices (for scale)
- Separate simulation engine service
- Dedicated data ingestion service
- Independent scaling
Backend:
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
pydantic>=2.4.0
pandas>=2.1.0
simpy>=4.0.0
python-dotenv>=1.0.0
httpx>=0.25.0
sqlalchemy[asyncio]>=2.0.0
asyncpg>=0.29.0
alembic>=1.13.0
weasyprint>=61.0 # PDF generation
jinja2>=3.1.0 # Template rendering
matplotlib>=3.8.0 # Chart generation
Frontend:
react@18.3.1
typescript@5.6.2
@vis.gl/react-google-maps@1.7.1
tailwindcss@3.4.18
@playwright/test@1.49.1
vite@6.0.5
recharts@2.15.0 # Data visualization
react-resizable-panels # Resizable layout
Environment Variables:
GOOGLE_MAPS_API_KEY- For geocoding and mappingVITE_GOOGLE_MAPS_API_KEY- Frontend map renderingAPI_BASE_URL- Backend API endpoint (production)
Configuration Files:
tailwind.config.js- Tailwind theme customizationvite.config.ts- Vite build configurationplaywright.config.ts- E2E test configurationpyproject.toml- Python dependencies
- Application metrics (Prometheus)
- Error tracking (Sentry)
- Performance monitoring (APM)
- Log aggregation (ELK stack)