PublicRisk.ai

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

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.run

Purpose: Main API gateway for authentication, user management, and general queries

Endpoints:

  • POST /api/ai/query - AI-powered queries
  • GET /api/admin/users - User management (Admin only)
  • POST /api/documents/upload - Document upload with RAG ingestion
  • GET /health - Health check

Technology:

  • FastAPI
  • Python 3.11
  • OpenRouter integration
  • ChromaDB for RAG

DSPy Optimized Service

https://publicrisk--dspy-optimized-service-fastapi-app.modal.run

Purpose: 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.run

Purpose: 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.run

Purpose: 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.run

Purpose: 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.run

Purpose: 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.run

Purpose: Multi-stakeholder decision analysis for complex "wicked problems"

Endpoints:

  • GET /list - List all decision scenarios
  • GET /get/{scenario_id} - Retrieve scenario details
  • POST /generate - Generate new stakeholder analysis
  • POST /simulate - Run simulation with stakeholder preferences
  • GET /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:

RolePermissionsFeatures
ViewerRead-onlyQuery Explorer, Risk Calculator (read)
EditorCreate/Edit+ Document upload, HITL review
AdminManage users+ User management, Adapter training
SuperAdminFull 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:

  1. Username + password
  2. If 2FA enabled: Enter 6-digit code
  3. Verify code or use recovery code
  4. 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_1430

Performance 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 preview

Current 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 preview

Deploy to Vercel:

vercel --prod
# Custom domain: app.publicrisk.ai

Environment 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-key

Backend Deployment

Modal Deployment:

# Deploy all services
modal deploy backend/main.py

# Deploy specific service
modal deploy backend/hazard_service.py

# Check status
modal app list

Update 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 region

CDN Caching:

  • CloudFlare CDN for frontend
  • Cache static assets for 7 days
  • Always serve stale on origin failure

Next Steps

On this page