import { useState, useEffect } from "react"; import { predictSoh, fetchModels, PredictRequest, PredictResponse, ModelInfo } from "../api"; const DEFAULTS: PredictRequest = { battery_id: "B0005", cycle_number: 100, ambient_temperature: 24, peak_voltage: 4.2, min_voltage: 2.7, avg_current: 2.0, avg_temp: 25.0, temp_rise: 3.0, cycle_duration: 3600, Re: 0.04, Rct: 0.02, delta_capacity: -0.005, model_name: null, }; /** Colour coding for model family badges */ const familyColour = (family: string) => { switch (family) { case "classical": return "bg-blue-900/50 text-blue-300"; case "deep_pytorch": return "bg-purple-900/50 text-purple-300"; case "deep_keras": return "bg-pink-900/50 text-pink-300"; case "ensemble": return "bg-green-900/50 text-green-300"; default: return "bg-gray-700 text-gray-300"; } }; export default function PredictionForm() { const [form, setForm] = useState(DEFAULTS); const [models, setModels] = useState([]); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Load available models on mount useEffect(() => { fetchModels() .then((ms) => { // Prefer loaded models; sort by r2 desc const sorted = [...ms].sort((a, b) => { if (a.loaded !== b.loaded) return a.loaded ? -1 : 1; return (b.r2 ?? 0) - (a.r2 ?? 0); }); setModels(sorted); }) .catch(() => {/* silently ignore — model list is optional */}); }, []); const handleChange = (key: keyof PredictRequest, val: string) => { setForm((prev) => ({ ...prev, [key]: key === "battery_id" || key === "model_name" ? val || null : parseFloat(val) || 0, })); }; const handleSubmit = async () => { setLoading(true); setError(null); try { const res = await predictSoh(form); setResult(res); } catch (e: any) { setError(e.response?.data?.detail || e.message); } finally { setLoading(false); } }; const stateColor = (state: string) => { switch (state) { case "Healthy": return "text-green-400"; case "Moderate": return "text-yellow-400"; case "Degraded": return "text-orange-400"; default: return "text-red-400"; } }; const numericFields: { key: keyof PredictRequest; label: string; step?: string }[] = [ { key: "cycle_number", label: "Cycle Number", step: "1" }, { key: "ambient_temperature", label: "Ambient Temp (°C)", step: "0.1" }, { key: "peak_voltage", label: "Peak Voltage (V)", step: "0.01" }, { key: "min_voltage", label: "Min Voltage (V)", step: "0.01" }, { key: "avg_current", label: "Avg Current (A)", step: "0.1" }, { key: "avg_temp", label: "Avg Cell Temp (°C)", step: "0.1" }, { key: "temp_rise", label: "Temp Rise (°C)", step: "0.1" }, { key: "cycle_duration", label: "Duration (s)", step: "1" }, { key: "Re", label: "Re (Ω)", step: "0.001" }, { key: "Rct", label: "Rct (Ω)", step: "0.001" }, { key: "delta_capacity", label: "ΔCapacity (Ah)", step: "0.001" }, ]; // Find info for the currently selected model const selectedModel = models.find( (m) => m.name === (form.model_name ?? models.find((x) => x.is_default)?.name) ) ?? models.find((m) => m.is_default); return (
{/* Form */}

SOH Prediction

{/* Battery ID */}
handleChange("battery_id", e.target.value)} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-green-500" />
{/* Model selector */}
{selectedModel && (

{selectedModel.algorithm} {selectedModel.r2 != null && ` · R²=${selectedModel.r2.toFixed(3)}`}

)}
{/* Numeric feature inputs */}
{numericFields.map((f) => (
handleChange(f.key, e.target.value)} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-green-500" />
))}
{error && (
{error}
)}
{/* Result panel */}

Result

{result ? (
{/* SOH gauge */}
= 90 ? "#22c55e" : result.soh_pct >= 80 ? "#eab308" : result.soh_pct >= 70 ? "#f97316" : "#ef4444" } strokeWidth="12" strokeDasharray={`${(result.soh_pct / 100) * 327} 327`} strokeLinecap="round" transform="rotate(-90 60 60)" /> {result.soh_pct.toFixed(1)}% SOH
{/* Details grid */}
{/* Version badge */} {result.model_version && (
v{result.model_version} model version
)}
) : (
Enter parameters and click Predict
)}
); } function InfoBox({ label, value, className = "text-white", }: { label: string; value: string; className?: string; }) { return (
{label}
{value}
); }