/** * ComparisonMode — Feature 3.6 * * Side-by-side comparison of two prediction scenarios (Before/After). * Users pick two mock patients (or two sets of parameters) and the component * runs both predictions and highlights the differences. * * Works with Engineering Mode style (no auth required) so it uses the * public /predict endpoint via the existing api utility. */ import { useState, useCallback } from 'react' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Cell, ResponsiveContainer, } from 'recharts' import { ArrowLeftRight, TrendingUp, TrendingDown, Minus, Loader2, AlertCircle, ChevronDown, CheckCircle2, XCircle, } from 'lucide-react' import { api } from '../api' import { useDisease } from '../context/DiseaseContext' import { useDiseaseSchema } from '../hooks/useDiseaseSchema' import { getPatientsForDisease } from '../mockPatients' // ── Helpers ─────────────────────────────────────────────────────────────────── function riskScore(result) { if (!result) return null return result.prediction === 1 ? result.confidence : 1 - result.confidence } function DeltaBadge({ before, after }) { if (before == null || after == null) return null const delta = after - before const pct = Math.round(Math.abs(delta) * 100) if (pct === 0) return No change const improving = delta < 0 return ( {improving ? : } {improving ? '↓' : '↑'}{pct}% risk ) } // ── Patient picker ──────────────────────────────────────────────────────────── function PatientPicker({ label, patients, selected, onSelect, color }) { const [open, setOpen] = useState(false) return (

{label}

{open && ( <>
setOpen(false)} />
{patients.map(p => ( ))}
)}
) } // ── Result column ───────────────────────────────────────────────────────────── function ResultColumn({ label, result, loading, error, color, colorLight }) { if (loading) return (
) if (error) return (

{error}

) if (!result) return (
No result yet
) const isPositive = result.prediction === 1 const conf = Math.round((result.confidence ?? 0) * 100) return (
{/* Badge */}
{isPositive ? : } {result.diagnosis ?? (isPositive ? 'Positive' : 'Negative')}

Confidence

{conf}%

{/* Risk score */}

Risk Score

{Math.round(riskScore(result) * 100)}%

) } // ── Feature diff table ──────────────────────────────────────────────────────── function FeatureDiffTable({ beforePatient, afterPatient, fields }) { if (!beforePatient || !afterPatient) return null const b = beforePatient.data || {} const a = afterPatient.data || {} const diffs = fields .filter(f => String(b[f.name]) !== String(a[f.name])) .map(f => ({ name: f.name, label: f.label || f.name, before: b[f.name], after: a[f.name] })) if (diffs.length === 0) { return (
Both patients have identical feature values.
) } return (
{diffs.map(d => { const numBefore = parseFloat(d.before) const numAfter = parseFloat(d.after) const isNumeric = !isNaN(numBefore) && !isNaN(numAfter) const delta = isNumeric ? numAfter - numBefore : null return ( ) })}
Feature Before After Change
{d.label} {String(d.before ?? '—')} {String(d.after ?? '—')} {delta != null ? ( 0 ? 'text-red-500' : delta < 0 ? 'text-green-600' : 'text-gray-400'}`}> {delta > 0 ? `+${delta.toFixed(1)}` : delta < 0 ? delta.toFixed(1) : '='} ) : ( {d.before} → {d.after} )}
) } // ── Side-by-side confidence chart ───────────────────────────────────────────── function ComparisonChart({ beforeResult, afterResult, beforeName, afterName }) { if (!beforeResult || !afterResult) return null const data = [ { name: 'Risk Score', Before: Math.round(riskScore(beforeResult) * 100), After: Math.round(riskScore(afterResult) * 100), }, { name: 'Confidence', Before: Math.round((beforeResult.confidence ?? 0) * 100), After: Math.round((afterResult.confidence ?? 0) * 100), }, ] return (

Side-by-Side Metrics

`${v}%`} tick={{ fontSize: 10 }} /> [`${v}%`]} />
) } // ── Main ComparisonMode ─────────────────────────────────────────────────────── export default function ComparisonMode() { const { selectedDisease } = useDisease() const { fields } = useDiseaseSchema(selectedDisease) const patients = getPatientsForDisease(selectedDisease) const [beforePatient, setBeforePatient] = useState(patients[0] ?? null) const [afterPatient, setAfterPatient] = useState(patients[1] ?? patients[0] ?? null) const [beforeResult, setBeforeResult] = useState(null) const [afterResult, setAfterResult] = useState(null) const [beforeLoading, setBeforeLoading] = useState(false) const [afterLoading, setAfterLoading] = useState(false) const [beforeError, setBeforeError] = useState(null) const [afterError, setAfterError] = useState(null) const [ran, setRan] = useState(false) const runComparison = useCallback(async () => { if (!beforePatient || !afterPatient || !selectedDisease) return setRan(true) setBeforeResult(null) setAfterResult(null) setBeforeError(null) setAfterError(null) setBeforeLoading(true) setAfterLoading(true) const [bRes, aRes] = await Promise.allSettled([ api.predict(selectedDisease, beforePatient.data), api.predict(selectedDisease, afterPatient.data), ]) setBeforeLoading(false) setAfterLoading(false) if (bRes.status === 'fulfilled') setBeforeResult(bRes.value) else setBeforeError(bRes.reason?.message ?? 'Prediction failed') if (aRes.status === 'fulfilled') setAfterResult(aRes.value) else setAfterError(aRes.reason?.message ?? 'Prediction failed') }, [beforePatient, afterPatient, selectedDisease]) const riskBefore = riskScore(beforeResult) const riskAfter = riskScore(afterResult) return (
{/* Header */}

Before / After Comparison

Compare two patient profiles side by side to evaluate risk changes

{/* Patient selectors + compare button */}
{ setBeforePatient(p); setRan(false) }} color="text-blue-600" /> { setAfterPatient(p); setRan(false) }} color="text-violet-600" />
{/* Results panel */} {ran && ( <> {/* Side-by-side result columns */}

Prediction Results

{riskBefore != null && riskAfter != null && ( )}

Before — {beforePatient?.name}

After — {afterPatient?.name}

{/* Chart comparison */} {/* Feature diff table */}

Changed Features

)} {/* Empty state */} {!ran && (

Select two patients and click Compare to see the results.

)}
) }