import { useState, useEffect } from 'react'; import { api } from '../api/client'; const SEVERITIES = [ { value: 'warning', label: 'Warning', desc: 'Early signs, low impact', factor: 0.3 }, { value: 'partial_shutdown', label: 'Partial Shutdown', desc: '30–70% capacity loss', factor: 0.6 }, { value: 'full_shutdown', label: 'Full Shutdown', desc: '100% production halt', factor: 1.0 }, ]; const COMM_COLORS = ['#818cf8', '#34d399', '#f472b6', '#fbbf24', '#38bdf8', '#a78bfa']; function riskColor(score) { if (score >= 75) return '#f43f5e'; if (score >= 50) return '#f59e0b'; if (score >= 25) return '#60a5fa'; return '#10b981'; } function RiskBar({ score }) { const color = riskColor(score); return (
{score.toFixed(0)}
); } export default function Simulate() { const [provinces, setProvinces] = useState([]); const [province, setProvince] = useState(''); const [duration, setDuration] = useState(30); const [severity, setSeverity] = useState('partial_shutdown'); const [result, setResult] = useState(null); const [propagation, setPropagation] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [graphLoading, setGraphLoading] = useState(true); useEffect(() => { api.getGraph() .then(g => { const ps = (g.nodes || []).filter(n => n.type === 'province'); setProvinces(ps); if (ps.length) setProvince(ps[0].id); }) .catch(() => {}) .finally(() => setGraphLoading(false)); }, []); async function run() { if (!province) return; setLoading(true); setError(null); setResult(null); setPropagation(null); try { const [simRes, propRes] = await Promise.all([ api.simulate(province, Number(duration), severity), api.getPropagation(province).catch(() => null), ]); setResult(simRes); setPropagation(propRes); } catch (e) { setError(e.message); } finally { setLoading(false); } } // Merge simulate result + propagation data for richer display const affectedWithPR = result?.affected_drugs?.map(drug => { const prData = propagation?.affected_nodes?.[drug.id]; return { ...drug, risk: drug.current_risk || 0, pagerank: prData?.components?.pagerank_normalized ?? null, buffer_days: prData?.components?.buffer_days ?? null, substitutability: prData?.components?.substitutability ?? null, community_label: prData?.components?.community_label ?? null, community_amp: prData?.components?.community_amplifier ?? null, }; }) || []; return (

Shock Simulator

Model downstream impact via Personalized PageRank ·{' '} R = PR(shock) × (1-S) × e^(−B/τ) × C

{/* Config Card */}
{/* Province */}
{graphLoading ? (
) : ( )}
{/* Duration */}
setDuration(Number(e.target.value))} style={{ width: '100%', accentColor: 'var(--primary)' }} />
1 day90 days180 days
{/* Severity */}
{SEVERITIES.map(s => ( ))}
{error && (
{error}
)} {result && (
{/* Propagation Explanation */}
📊 Propagation Analysis Personalized PageRank

{result.propagation_explanation}

{propagation && (
Total affected: {propagation.total_affected} nodes Method: {propagation.method} Community cluster: #{propagation.propagation_trace?.origin_community ?? 'N/A'}
)}

Simulated at {new Date(result.simulated_at).toLocaleString('en-IN')}

{/* Affected Drugs — with PageRank breakdown */} {affectedWithPR.length > 0 ? (
Top {affectedWithPR.length} Affected Inputs R = PR × (1−S) × e^(−B/τ) × C
{affectedWithPR.map((drug, i) => (

{drug.name}

{drug.therapeutic_class || drug.nlem_tier}
{/* PageRank component breakdown */} {drug.pagerank !== null && (
PR: {(drug.pagerank * 100).toFixed(1)}% {drug.buffer_days !== null && ( Buffer: {drug.buffer_days}d )} {drug.substitutability !== null && ( Sub: {(drug.substitutability * 100).toFixed(0)}% )} {drug.community_label && ( {drug.community_label} )}
)}
))}
) : (

No inputs significantly affected at this severity level

)} {/* Community Propagation Summary */} {propagation?.propagation_trace?.propagation_edges?.length > 0 && (
🔗 Supply Chain Propagation Paths
{propagation.propagation_trace.propagation_edges.slice(0, 8).map((edge, i) => (
{edge.from} → {edge.name} w={edge.weight.toFixed ? edge.weight.toFixed(2) : edge.weight} {edge.type}
))}
)}
)}
); }