Enterprise Architecture
Modern cloud architecture with Module Federation, Modal serverless backend, and enterprise security
Overview
PublicRisk.ai uses a modern, scalable serverless architecture designed for enterprise security, performance, and unlimited scalability.
SOC 2 Type II Certified: Enterprise-grade security with role-based access control, encryption, and comprehensive audit logging
Architecture Diagram
Legend:
- Solid lines: Active connections in production
- Dotted lines: Module Federation structure (temporarily disabled, migrating from monolith)
Frontend Architecture
Module Federation Migration
PublicRisk.ai is migrating from a monolithic frontend to micro-frontends using Vite Module Federation.
Current Status: Structure exists but federation is temporarily disabled in production. Running as monolith until remote apps are deployed.
Current State (Production):
- Single monolithic build from
src/directory - Bundle size: 4.7 MB (uncompressed)
- All features deployed together
- Single Vite dev server (port 5173)
Target State (Planned):
- Four independent micro-frontends
- Lazy-loaded on demand using
@originjs/vite-plugin-federation - Independent deployments per module
- Reduced bundle sizes (estimated 1-1.5 MB per module)
Monorepo Structure:
frontend/
├── apps/
│ ├── shell/ # Host app (main container)
│ ├── document-comparator/ # Remote module (port 5001)
│ ├── query-explorer/ # Remote module (port 5002)
│ └── document-generator/ # Remote module (port 5003)
├── packages/
│ └── shared/ # Shared utilities, hooks, contexts
└── src/ # Legacy monolith (being migrated)Benefits:
- ✅ Faster builds: Parallel compilation
- ✅ Independent deploys: Update one module without touching others
- ✅ Team autonomy: Different teams own different modules
- ✅ Better caching: Browser caches unchanged modules
- ✅ Smaller bundles: Load only what's needed
Status: Structure created, federation temporarily disabled until remote apps deploy
Technology Stack
Core:
- React 18.3.1
- TypeScript 5.6.2
- Vite 5.4.11
- Material-UI 6.1.1
State Management:
- React Context API
- Zustand (for complex state)
- TanStack Query (server state)
Routing:
- React Router 6.27.0
- Lazy loading with
React.lazy() - Error boundaries
Maps & Visualization:
- React Leaflet 4.2.1
- Recharts 2.12.7
- D3.js 7.9.0
Backend Architecture
Modal Serverless Platform
All backend services run on Modal - a modern serverless platform with automatic scaling and GPU support.
Key Features:
- ✅ Auto-scaling: 0 to 1000+ concurrent requests
- ✅ GPU acceleration: For AI model inference
- ✅ Cold start optimization: ~30-60s first request, less than 3s subsequent
- ✅ Built-in monitoring: Metrics and logs included
- ✅ Zero ops: No infrastructure management
Services:
Consolidated Backend
https://publicrisk--publicrisk-consolidated-backend-serve.modal.runPurpose: Main API gateway for authentication, user management, and general queries
Endpoints:
POST /api/ai/query- AI-powered queriesGET /api/admin/users- User management (Admin only)POST /api/documents/upload- Document upload with RAG ingestionGET /health- Health check
Technology:
- FastAPI
- Python 3.11
- OpenRouter integration
- ChromaDB for RAG
DSPy Optimized Service
https://publicrisk--dspy-optimized-service-fastapi-app.modal.runPurpose: DSPy-optimized prompts for improved accuracy
Features:
- 1000+ training examples
- ReAct Teacher-Student pipeline
- Multi-turn conversation optimization
- ~92% accuracy (up from ~75%)
PEFT Adapters Service
https://publicrisk--publicrisk-peft-adapters-fastapi-app.modal.runPurpose: Domain-specific fine-tuned LoRA adapters
Technology:
- Base Model: Llama 3.1 70B Instruct
- Method: LoRA (Low-Rank Adaptation)
- GPU: A100 (training), A10G (inference)
- Training: 1,500-2,000 samples per domain
35 Specialized Domains:
- Public Sector: public_education, public_financing, municipal_codes, municipal, law_enforcement, emergency_management, infrastructure, utilities, procurement, CA_government_code, CA_education_code
- Insurance: insurance, insurance_exposures, liability, property, workers-comp, regulatory, school-risk, risk-analysis
- Enterprise: cybersecurity, healthcare, financial, legal, hr-employment, operational, supply_chain, technology, reputational, geopolitical
- Environmental: environmental, climate, natural_disasters, nepa, academic_research, education
Status: Production ready with async training and real-time job tracking
HAZUS Hazard Service
https://publicrisk--publicrisk-hazard-service-*.modal.runPurpose: Real-time natural hazard assessment
Data Sources:
- FEMA NFHL (flood)
- USGS NSHM (earthquake)
- NOAA (hurricane, tsunami)
- NIFC (wildfire)
Cache: Redis (60s TTL for wildfire, 24hr for static hazards)
SIPMath Service
https://publicrisk--publicrisk-sipmath-fastapi-app.modal.runPurpose: Probabilistic risk modeling
Features:
- Monte Carlo simulation (10,000 trials)
- Gaussian copulas for correlation
- SLURP aggregation
- Confidence intervals
Environmental Hazards Service
https://publicrisk--publicrisk-environmental-hazards-fastapi-app.modal.runPurpose: Real-time environmental monitoring and risk assessment
Features:
- Air Quality: EPA AirNow API integration for real-time AQI data
- PFAS Contamination: EPA UCMR3/UCMR5 database integration for per- and polyfluoroalkyl substances
- Chemical Spills: NOAA ERMA (Environmental Response Management Application) for incident tracking
- Historical Analysis: Time-series data for trend analysis
Data Sources:
- EPA AirNow (hourly updates)
- EPA UCMR databases (quarterly updates)
- NOAA ERMA (real-time incidents)
Wicked Decisions Service
https://publicrisk--wicked-decisions-*.modal.runPurpose: Multi-stakeholder decision analysis for complex "wicked problems"
Endpoints:
GET /list- List all decision scenariosGET /get/{scenario_id}- Retrieve scenario detailsPOST /generate- Generate new stakeholder analysisPOST /simulate- Run simulation with stakeholder preferencesGET /health- Service health check
Features:
- Stakeholder Mapping: Identify affected parties and their interests
- Trade-off Analysis: Quantify conflicts between competing objectives
- Scenario Simulation: Model outcomes under different decision paths
- Consensus Building: Identify common ground and compromise solutions
Use Cases:
- Urban planning (development vs. preservation)
- Climate policy (economic growth vs. emissions reduction)
- Healthcare allocation (cost vs. access vs. quality)
- Infrastructure investment (safety vs. budget constraints)
Authentication & Security
Zero Data Retention (ZDR) Policy
Privacy-First AI Model Selection
PublicRisk.ai only uses Zero Data Retention (ZDR) AI model providers. ZDR providers will not store your data for any period of time.
We only route to AI Model endpoints that have a Zero Data Retention policy. This means:
- No training on your data: Your queries and documents are never used to train AI models
- No data storage: Providers do not retain your data after processing
- Endpoint-specific policies: We verify ZDR compliance at the endpoint level, not just provider level
- Special agreements: In some cases, we create custom agreements with providers for enhanced privacy guarantees
OpenRouter's Role:
- Tracks each provider's specific policy for every endpoint
- Works with providers to keep these policies current
- Creates special agreements with providers when needed to ensure data retention or training policies are more privacy-focused than their default policies
Your data security and privacy is our top priority.
Supabase Authentication
Features:
- Email/password login
- JWT token-based sessions
- Two-Factor Authentication (TOTP)
- Recovery codes
- Session management
Token Storage:
// localStorage structure
{
"authTokens": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"tokenType": "bearer",
"expiresIn": 3600
},
"authUser": {
"id": "user-uuid",
"email": "user@example.com",
"role": "Admin"
}
}Token Validation:
- Checked on every app initialization
- Auto-refresh before expiration
- Logout on invalid token
Role-Based Access Control (RBAC)
Roles:
| Role | Permissions | Features |
|---|---|---|
| Viewer | Read-only | Query Explorer, Risk Calculator (read) |
| Editor | Create/Edit | + Document upload, HITL review |
| Admin | Manage users | + User management, Adapter training |
| SuperAdmin | Full access | + System settings, Model configuration |
Implementation:
// Frontend route protection
<PrivateRoute requiredRole="Admin">
<AdminPortal />
</PrivateRoute>
// Backend endpoint protection
@require_role("Admin")
async def admin_endpoint():
...Two-Factor Authentication (2FA)
User Self-Service:
- Navigate to Profile → Security & 2FA
- Scan QR code with authenticator app
- Save recovery codes
- Configure verification interval (daily/weekly/monthly/quarterly/yearly)
Admin Management:
- View 2FA status for all users
- Require 2FA for specific users
- Reset 2FA if user loses device
Login Flow:
- Username + password
- If 2FA enabled: Enter 6-digit code
- Verify code or use recovery code
- Set session duration based on interval
Data Layer
ChromaDB (Vector Store)
Purpose: RAG (Retrieval Augmented Generation) document storage
Configuration:
# Embedding model
model = "nomic-embed-text" # 768 dimensions, FREE
chunk_size = 1000 # tokens
overlap = 200 # tokens
# Collection settings
collection = chroma.get_or_create_collection(
name="publicrisk_documents",
metadata={"hnsw:space": "cosine"}
)Data Flow:
Supabase PostgreSQL
Purpose: Relational data (users, roles, sessions, audit logs)
Schema:
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
role VARCHAR(50) NOT NULL,
two_factor_enabled BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
-- Sessions table
CREATE TABLE sessions (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
token TEXT NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Audit logs
CREATE TABLE audit_logs (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
action VARCHAR(100) NOT NULL,
resource VARCHAR(100),
timestamp TIMESTAMP DEFAULT NOW(),
ip_address INET
);Redis Cache
Purpose: Fast caching for HAZUS API responses
Configuration:
# Cache TTL by data type
TTL = {
"wildfire": 60, # 1 minute (real-time)
"flood": 86400, # 24 hours (stable)
"earthquake": 86400, # 24 hours (stable)
"hurricane": 3600 # 1 hour (during events)
}Cache Key Format:
hazard:{type}:{lat}:{lng}:{timestamp}
Examples:
- hazard:flood:34.0522:-118.2437:20251204
- hazard:wildfire:38.7519:-120.7983:20251204_1430Performance Optimization
Cold Start Handling
Problem: Modal services sleep after 5-10 min inactivity. First request takes 30-60s.
Solution:
// Frontend: Show user-friendly message
const [coldStartWarning, setColdStartWarning] = useState(false);
useEffect(() => {
if (loading) {
const timer = setTimeout(() => {
setColdStartWarning(true);
}, 10000); // Show after 10s
return () => clearTimeout(timer);
}
}, [loading]);
{coldStartWarning && (
<Alert severity="info">
Service is starting up (first-time may take 30-60s).
Subsequent requests will be fast.
</Alert>
)}Backend: Keep-alive ping
# Ping service every 5 minutes to prevent sleep
@app.on_event("startup")
async def schedule_keepalive():
scheduler.add_job(
ping_self,
trigger="interval",
minutes=5
)Debouncing & Throttling
Map Interactions:
// Prevent excessive API calls when dragging map
const debouncedFetch = debounce(fetchHazards, 500);
map.on('moveend', () => {
debouncedFetch(map.getCenter());
});Search:
// Wait for user to stop typing
const debouncedSearch = debounce(performSearch, 300);
<TextField
onChange={(e) => debouncedSearch(e.target.value)}
placeholder="Search documents..."
/>Bundle Optimization
Code Splitting:
// Lazy load heavy components
const DocumentGenerator = React.lazy(
() => import('./components/DocumentGenerator')
);
// Wrap in Suspense
<Suspense fallback={<CircularProgress />}>
<DocumentGenerator />
</Suspense>Chunk Analysis:
npm run build
# Analyze bundle
npm run previewCurrent Bundle Sizes:
- Main: 2.1 MB (includes React, MUI)
- Vendor: 1.8 MB (third-party libs)
- App: 800 KB (application code)
- Total: 4.7 MB → Target: less than 3 MB with Module Federation
Monitoring & Observability
Logging
Frontend:
// Sentry integration
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
tracesSampleRate: 1.0
});
// Log errors
Sentry.captureException(error);Backend:
# Structured logging
import structlog
logger = structlog.get_logger()
logger.info(
"api_request",
endpoint="/api/ai/query",
user_id=user.id,
duration_ms=234
)Metrics
Modal Dashboard:
- Request count
- Latency (p50, p95, p99)
- Error rate
- Cold start frequency
- GPU utilization
Custom Metrics:
# Prometheus metrics
from prometheus_client import Counter, Histogram
api_requests = Counter(
'api_requests_total',
'Total API requests',
['endpoint', 'status']
)
api_latency = Histogram(
'api_latency_seconds',
'API latency',
['endpoint']
)Deployment
Frontend Deployment
Build:
npm run build
# Output: dist/
# Preview locally
npm run previewDeploy to Vercel:
vercel --prod
# Custom domain: app.publicrisk.aiEnvironment Variables:
# .env.production
VITE_MODAL_BASE_URL=https://publicrisk--publicrisk-consolidated-backend-serve.modal.run
VITE_HAZARD_SERVICE_BASE_URL=https://publicrisk--publicrisk-hazard-service
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-keyBackend Deployment
Modal Deployment:
# Deploy all services
modal deploy backend/main.py
# Deploy specific service
modal deploy backend/hazard_service.py
# Check status
modal app listUpdate Strategy:
- Blue-Green: Deploy new version, test, switch traffic
- Canary: Gradually route traffic to new version
- Rollback: Instant rollback to previous version if issues
Security Best Practices
API Key Management
Storage:
# Never commit API keys!
# Store in Modal secrets
import modal
stub = modal.Stub("publicrisk")
@stub.function(
secrets=[
modal.Secret.from_name("openrouter-api-key"),
modal.Secret.from_name("supabase-service-key")
]
)
def api_endpoint():
openrouter_key = os.environ["OPENROUTER_API_KEY"]
...Rotation:
- Rotate keys every 90 days
- Use different keys for dev/staging/prod
- Monitor key usage for anomalies
CORS Configuration
# Allow only specific origins
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://app.publicrisk.ai",
"https://staging.publicrisk.ai"
],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"]
)Rate Limiting
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/api/ai/query")
@limiter.limit("100/hour") # Max 100 queries per hour
async def query_endpoint():
...Disaster Recovery
Backup Strategy
Database Backups:
- Automated daily backups (Supabase)
- 30-day retention
- Point-in-time recovery
Vector Store Backups:
- Weekly ChromaDB snapshots
- Stored in S3 with versioning
- 90-day retention
Failover Plan
Service Redundancy:
# Multiple Modal regions
REGIONS = ["us-west-2", "us-east-1"]
# Automatic failover
@app.middleware("http")
async def failover_middleware(request, call_next):
for region in REGIONS:
try:
response = await call_next(request)
return response
except Exception:
continue # Try next regionCDN Caching:
- CloudFlare CDN for frontend
- Cache static assets for 7 days
- Always serve stale on origin failure
Next Steps
STORM Document Generator
Complete guide to using STORM (Synthesis of Topic Outline and Research Methodology) for AI-powered document creation with research, citations, and professional formatting
HAZUS Integration - Technical Guide
Complete technical documentation for FEMA HAZUS integration including API endpoints, data sources, damage functions, and implementation details