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 (
);
}
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 */}
{/* 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}
))}
)}
)}
);
}