"use client"; import { useState } from "react"; import { Sliders, Activity, Cpu, Shuffle } from "lucide-react"; import ShapChart from "@/components/ShapChart"; import { PatientVitals } from "@/lib/edge/types"; import { evaluateEdge } from "@/lib/edge/inference"; import { appendSessionHistory } from "@/lib/edge/history"; interface PredictionResult { prediction: boolean; prediction_label: string; probability: number; model_outputs?: Record; model_probs?: Record; weights?: Record; } // Clinically bounded randomized vitals generator function generateRandomVitals(): PatientVitals { const hr = Math.floor(Math.random() * (162 - 48 + 1)) + 48; // 48 - 162 BPM const spo2 = Math.floor(Math.random() * (99 - 78 + 1)) + 78; // 78 - 99 % const sys = Math.floor(Math.random() * (190 - 86 + 1)) + 86; // 86 - 190 mmHg const diaMax = Math.min(sys - 18, 120); const diaMin = Math.max(Math.min(diaMax - 14, 52), 46); const dia = Math.floor(Math.random() * (diaMax - diaMin + 1)) + diaMin; const temp = Number((Math.random() * (39.3 - 35.8) + 35.8).toFixed(1)); return { Heart_Rate: hr, SpO2_Level: spo2, Systolic_BP: sys, Diastolic_BP: dia, Body_Temp: temp, }; } const DEFAULT_VITALS: PatientVitals = { Heart_Rate: 75, SpO2_Level: 98, Systolic_BP: 120, Diastolic_BP: 80, Body_Temp: 37.0, }; const PRESET_VECTORS = [ { name: "BASELINE NORMAL", tag: "NORMAL", desc: "Normotensive resting telemetry", data: { Heart_Rate: 72, SpO2_Level: 98, Systolic_BP: 118, Diastolic_BP: 78, Body_Temp: 37.0 }, }, { name: "ISCHEMIC RISK", tag: "CRITICAL", desc: "Tachycardic desaturation event", data: { Heart_Rate: 128, SpO2_Level: 91, Systolic_BP: 165, Diastolic_BP: 104, Body_Temp: 37.3 }, }, { name: "HYPOXIC EVENT", tag: "WARNING", desc: "Hypoxemia with bradycardia depression", data: { Heart_Rate: 48, SpO2_Level: 82, Systolic_BP: 88, Diastolic_BP: 54, Body_Temp: 35.8 }, }, { name: "HYPERTENSIVE", tag: "WARNING", desc: "Arterial pressure elevation", data: { Heart_Rate: 98, SpO2_Level: 96, Systolic_BP: 195, Diastolic_BP: 118, Body_Temp: 37.1 }, }, ]; export default function Simulator() { const [formData, setFormData] = useState(DEFAULT_VITALS); const [submittedData, setSubmittedData] = useState(null); const [prediction, setPrediction] = useState(null); const [loading, setLoading] = useState(false); // Manual slider adjustment updates input parameters only const handleSliderChange = (field: keyof PatientVitals, val: number) => { setFormData((prev) => ({ ...prev, [field]: val })); }; // Preset selection updates input parameters only const handleSelectPreset = (presetData: PatientVitals) => { setFormData({ ...presetData }); }; // Randomization updates input parameters only const handleRandomize = () => { const randomVitals = generateRandomVitals(); setFormData(randomVitals); }; // Inference execution runs strictly when the user clicks the action button const handlePredict = async () => { setLoading(true); const targetData = { ...formData }; try { const apiUrl = process.env.NEXT_PUBLIC_API_URL; if (apiUrl) { const res = await fetch(`${apiUrl}/predict`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(targetData), }); if (res.ok) { const data = await res.json(); setPrediction(data); setSubmittedData(targetData); // Append execution record to session audit log appendSessionHistory({ timestamp: new Date().toISOString().replace("T", " ").substring(0, 19), heart_rate: targetData.Heart_Rate, spo2: targetData.SpO2_Level, sys_bp: targetData.Systolic_BP, dia_bp: targetData.Diastolic_BP, body_temp: targetData.Body_Temp, prediction_label: data.prediction_label || (data.prediction ? "Anomaly" : "Normal"), confidence: data.probability ?? 0.95, is_at_risk: Boolean(data.prediction), }); setLoading(false); return; } } } catch { // Cloud API unreachable, seamlessly fall back to local Edge inference } // Zero-Cloud Local TinyML Ensemble Inference Execution try { const edgeRes = evaluateEdge(targetData); setPrediction({ prediction: edgeRes.is_at_risk === 1, prediction_label: edgeRes.label, probability: edgeRes.confidence, model_outputs: edgeRes.model_outputs, model_probs: edgeRes.model_probs, weights: edgeRes.weights, }); setSubmittedData(targetData); // Append execution record to session audit log appendSessionHistory({ timestamp: new Date().toISOString().replace("T", " ").substring(0, 19), heart_rate: targetData.Heart_Rate, spo2: targetData.SpO2_Level, sys_bp: targetData.Systolic_BP, dia_bp: targetData.Diastolic_BP, body_temp: targetData.Body_Temp, prediction_label: edgeRes.label, confidence: edgeRes.confidence, is_at_risk: edgeRes.is_at_risk === 1, }); } catch (err) { console.error("Local inference error", err); } finally { setLoading(false); } }; const isStagedDirty = submittedData !== null && (formData.Heart_Rate !== submittedData.Heart_Rate || formData.SpO2_Level !== submittedData.SpO2_Level || formData.Systolic_BP !== submittedData.Systolic_BP || formData.Diastolic_BP !== submittedData.Diastolic_BP || formData.Body_Temp !== submittedData.Body_Temp); return (
{/* Header */}
EXPLAINABLE AI MODULE

MODEL
SIMULATOR

Manual override. Input custom telemetry to test ensemble classifier and view Shapley feature importance vectors.
{/* Main Workbench Grid */}
{/* Input Panel - 5 Columns */}

MANUAL INPUT MATRIX

5 Telemetry Inputs
{/* Clinical Profile Presets - Enlarged & Proportional Presence */}
CLINICAL TEST PROFILES SELECT TO LOAD
{PRESET_VECTORS.map((preset) => { const isActive = formData.Heart_Rate === preset.data.Heart_Rate && formData.SpO2_Level === preset.data.SpO2_Level && formData.Systolic_BP === preset.data.Systolic_BP && formData.Diastolic_BP === preset.data.Diastolic_BP && formData.Body_Temp === preset.data.Body_Temp; return ( ); })}
{/* Slider Inputs - Dynamically Distributed to Match Right Panel Height */}
handleSliderChange("Heart_Rate", v)} /> handleSliderChange("SpO2_Level", v)} /> handleSliderChange("Systolic_BP", v)} /> handleSliderChange("Diastolic_BP", v)} /> handleSliderChange("Body_Temp", v)} />
{/* Action Bar - Anchored to bottom with border */}
{/* Results Panel - 7 Columns */}

INFERENCE RESULTS & XAI

{isStagedDirty && ( INPUT MODIFIED · RE-RUN REQUIRED )}
{prediction && submittedData ? ( <>
MODEL OUTPUT
{(() => { const getStatusStyles = (label: string) => { const normalized = (label || "").toLowerCase(); if (normalized === "healthy" || normalized === "normal") return "bg-[#d1fae5] text-[#065f46] border-[#065f46]"; if (normalized.includes("heart")) return "bg-[#fef08a] text-[#854d0e] border-[#854d0e]"; if (normalized.includes("asthma")) return "bg-[#e0f2fe] text-[#075985] border-[#075985]"; if (normalized.includes("hypertension")) return "bg-[#f3e8ff] text-[#6b21a8] border-[#6b21a8]"; if (normalized.includes("diabetes")) return "bg-[#ffedd5] text-[#9a3412] border-[#9a3412]"; return "bg-hazard text-white border-hazard"; }; const diagnosisText = prediction.prediction ? prediction.prediction_label.toUpperCase() : "NORMAL"; return (
{diagnosisText}
CONFIDENCE: {(prediction.probability * 100).toFixed(1)}%
); })()} {/* Ensemble Diagnostics Breakdown */} {prediction.model_outputs && (
ENSEMBLE STACK DIAGNOSTICS WEIGHTED VOTING
{Object.keys(prediction.model_outputs).map((modelName) => { const outputLabel = prediction.model_outputs![modelName]; const isAnomaly = outputLabel !== "Healthy" && outputLabel !== "Normal"; return (
{modelName}
{outputLabel}
CONF: {(((prediction.model_probs?.[modelName] ?? prediction.probability ?? 0.95)) * 100).toFixed(1)}%
W: {(prediction.weights?.[modelName] ?? 0.25).toFixed(2)}
); })}
)}
{/* SHAP Feature Attributions Container */}
SHAPLEY ADDITIVE EXPLANATIONS DIRECTIONAL POLARITY
) : (
{loading ? "CALCULATING SHAP VALUES..." : "Awaiting Input · Click Execute Inference to Generate Attributions"}
)}
); } interface InputControlProps { label: string; value: number; min: number; max: number; step?: number; unit?: string; onChange: (v: number) => void; } function InputControl({ label, value, min, max, step = 1, unit, onChange }: InputControlProps) { return (
{value} {unit && {unit}}
onChange(Number(e.target.value))} className="fader-slider" />
{min} {max}
); }