Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { AlertTriangle, Zap, HelpCircle, CheckCircle } from "lucide-react"; | |
| import clsx from "clsx"; | |
| interface ProcessFlowProps { | |
| data: any; | |
| simulated?: any; | |
| } | |
| import { InfoTooltip } from "@/components/ui/tooltip"; | |
| export function ProcessFlow({ data, simulated }: ProcessFlowProps) { | |
| if (!data || !data.intelligence) return <div className="text-neutral-500">Loading intelligence...</div>; | |
| const { | |
| waterfall, norm_adequacy, intervention_roi, yield_rate, break_even_tolerance, | |
| blame_breakdown, elasticity, false_yield_warning, safety_recommendation, | |
| po_imbalance, min_charge_distortion, risk_fingerprint | |
| } = data.intelligence; | |
| const hasShortfall = data.metrics['Shortfall'] > 0; | |
| const maxValue = Math.max(...waterfall.map((w: any) => Math.abs(w.value))) * 1.2; | |
| return ( | |
| <div className="w-full space-y-6"> | |
| {/* SUCCESS BANNER - Only show when fulfilled */} | |
| {!hasShortfall && ( | |
| <div className="bg-emerald-900/30 border border-emerald-700 rounded-lg p-3 flex items-center gap-3"> | |
| <CheckCircle className="h-5 w-5 text-emerald-400 shrink-0" /> | |
| <div> | |
| <span className="font-bold text-emerald-200">Order Fulfilled</span> | |
| <span className="text-emerald-300 ml-2"> | |
| Surplus: +{Math.round(Math.abs(data.metrics['Shortfall']))}m | |
| </span> | |
| </div> | |
| </div> | |
| )} | |
| {/* FALSE YIELD WARNING BANNER - Only when there's a shortfall */} | |
| {hasShortfall && false_yield_warning && ( | |
| <div className="bg-amber-900/30 border border-amber-700 rounded-lg p-3 flex items-center gap-3"> | |
| <AlertTriangle className="h-5 w-5 text-amber-400 shrink-0" /> | |
| <div> | |
| <span className="font-bold text-amber-200">False Yield Alert:</span> | |
| <span className="text-amber-300 ml-2">High yield ({yield_rate}%) but insufficient delivery ({norm_adequacy}%)</span> | |
| </div> | |
| </div> | |
| )} | |
| {/* 1. Policy Intelligence Header */} | |
| <div className={clsx("grid gap-3 grid-cols-1 sm:grid-cols-2", hasShortfall ? "lg:grid-cols-5" : "lg:grid-cols-4")}> | |
| <div className="bg-neutral-900 border border-neutral-800 p-4 rounded-xl"> | |
| <div className="text-xs text-neutral-500 uppercase tracking-wider mb-1"> | |
| Norm Score | |
| <InfoTooltip content="Output ÷ Order Quantity. Shows how well norms predict requirements." /> | |
| </div> | |
| <div className={clsx("text-2xl font-bold", norm_adequacy < 95 ? "text-red-400" : "text-emerald-400")}> | |
| {norm_adequacy}% | |
| </div> | |
| </div> | |
| <div className="bg-neutral-900 border border-neutral-800 p-4 rounded-xl"> | |
| <div className="text-xs text-neutral-500 uppercase tracking-wider mb-1"> | |
| Production Efficiency | |
| <InfoTooltip content="Total: Output ÷ Total Issued. Fresh: First-run efficiency (Output ÷ Fresh Issued)." /> | |
| </div> | |
| <div className="flex items-baseline gap-2"> | |
| <span className="text-2xl font-bold text-blue-400">{yield_rate}%</span> | |
| <span className="text-[10px] text-neutral-500">Total</span> | |
| </div> | |
| {data.metrics?.["Fresh Yield %"] !== undefined && ( | |
| <div className="flex items-baseline gap-2 mt-1 pt-1 border-t border-neutral-800"> | |
| <span className="text-lg font-bold text-emerald-400">{data.metrics["Fresh Yield %"]}%</span> | |
| <span className="text-[10px] text-neutral-500">Fresh Yield</span> | |
| </div> | |
| )} | |
| </div> | |
| <div className="bg-neutral-900 border border-neutral-800 p-4 rounded-xl"> | |
| <div className="text-xs text-neutral-500 uppercase tracking-wider mb-1"> | |
| Elasticity | |
| <InfoTooltip content="How responsive this article is to safety margin changes. LOW = changing policy won't help much." /> | |
| </div> | |
| <div className={clsx("text-lg font-bold", | |
| elasticity.classification === "HIGH" ? "text-emerald-400" : | |
| elasticity.classification === "MEDIUM" ? "text-amber-400" : "text-red-400" | |
| )}> | |
| {elasticity.classification} | |
| </div> | |
| <div className="text-xs text-neutral-600">{elasticity.value}</div> | |
| </div> | |
| <div className="bg-neutral-900 border border-neutral-800 p-4 rounded-xl"> | |
| <div className="text-xs text-neutral-500 uppercase tracking-wider mb-1"> | |
| Intervention ROI | |
| <InfoTooltip content="Did the planner's manual adjustment (over/under issue) help or hurt?" /> | |
| </div> | |
| <div className={clsx("text-sm font-bold", intervention_roi.includes("High") ? "text-emerald-400" : "text-amber-400")}> | |
| {intervention_roi} | |
| </div> | |
| </div> | |
| {/* Only show Safety Needed when there's a shortfall */} | |
| {hasShortfall && ( | |
| <div className="bg-purple-900/30 border border-purple-700 p-4 rounded-xl"> | |
| <div className="text-xs text-purple-300 uppercase tracking-wider mb-1"> | |
| Safety Needed | |
| <InfoTooltip content="The exact safety margin % that would have fulfilled this order." /> | |
| </div> | |
| <div className="text-2xl font-bold text-purple-400">+{safety_recommendation.value}%</div> | |
| <div className="text-xs text-purple-500">{safety_recommendation.confidence_low}-{safety_recommendation.confidence_high}%</div> | |
| </div> | |
| )} | |
| </div> | |
| {/* 2. BLAME BREAKDOWN - Only show when there's a shortfall */} | |
| {hasShortfall && ( | |
| <div className="bg-neutral-900 border border-neutral-800 rounded-xl p-5"> | |
| <h3 className="text-sm font-bold mb-4 text-neutral-300 uppercase tracking-wider"> | |
| Shortfall Responsibility Breakdown | |
| <InfoTooltip content="Who caused the shortfall? Policy (norms too low), Execution (planner cut), or Process (manufacturing loss)." /> | |
| </h3> | |
| <div className="flex gap-1 h-8 mb-3 overflow-hidden rounded-lg"> | |
| <div | |
| className="bg-red-600 flex items-center justify-center text-xs font-bold text-white transition-all" | |
| style={{ width: `${Math.max(1, blame_breakdown.policy_pct)}%` }} | |
| title={`Norm: ${blame_breakdown.policy_impact}m`} | |
| > | |
| {blame_breakdown.policy_pct > 15 && `${blame_breakdown.policy_pct}%`} | |
| </div> | |
| <div | |
| className="bg-amber-500 flex items-center justify-center text-xs font-bold text-black transition-all" | |
| style={{ width: `${Math.max(1, blame_breakdown.execution_pct)}%` }} | |
| title={`Execution: ${blame_breakdown.execution_impact}m`} | |
| > | |
| {blame_breakdown.execution_pct > 15 && `${blame_breakdown.execution_pct}%`} | |
| </div> | |
| <div | |
| className="bg-blue-500 flex items-center justify-center text-xs font-bold text-white transition-all" | |
| style={{ width: `${Math.max(1, blame_breakdown.process_pct)}%` }} | |
| title={`Process: ${blame_breakdown.process_impact}m`} | |
| > | |
| {blame_breakdown.process_pct > 15 && `${blame_breakdown.process_pct}%`} | |
| </div> | |
| </div> | |
| <div className="flex justify-between text-xs"> | |
| <span className="text-red-400">■ Policy {blame_breakdown.policy_pct}%</span> | |
| <span className="text-amber-400">■ Execution {blame_breakdown.execution_pct}%</span> | |
| <span className="text-blue-400">■ Process {blame_breakdown.process_pct}%</span> | |
| </div> | |
| {blame_breakdown.policy_pct > 70 && ( | |
| <div className="mt-3 text-xs text-red-300 bg-red-900/20 p-2 rounded"> | |
| ⚠️ {Math.round(blame_breakdown.policy_pct)}% of shortfall is policy-driven. Operators did NOT cause this loss. | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* 3. Loss Waterfall Visual */} | |
| <div className="bg-neutral-900 border border-neutral-800 rounded-xl p-5"> | |
| <h3 className="text-sm font-bold mb-4 flex items-center text-neutral-300 uppercase tracking-wider"> | |
| <Zap className="mr-2 h-4 w-4 text-yellow-500" /> | |
| {hasShortfall ? "Loss Waterfall" : "Material Flow"} | |
| <InfoTooltip content="Shows how material quantity changes through each stage: Order Quantity → Norm → Execution → Manufacturing → Output" /> | |
| </h3> | |
| <div className="space-y-3"> | |
| {waterfall.map((step: any, i: number) => { | |
| const isNegative = step.value < 0; | |
| const maxValue = Math.max(...waterfall.map((w: any) => Math.abs(w.value))); | |
| const width = Math.min(100, (Math.abs(step.value) / maxValue) * 100); | |
| return ( | |
| <div key={i} className="flex items-center text-sm"> | |
| <div className="w-40 text-neutral-400 font-medium text-xs">{step.label}</div> | |
| <div className="flex-1 h-6 flex items-center bg-neutral-950 rounded relative overflow-hidden"> | |
| <div | |
| className={clsx( | |
| "h-full rounded-sm transition-all duration-500 flex items-center px-2", | |
| step.type === 'base' ? "bg-blue-900/50 text-blue-200" : | |
| step.type === 'final' ? (step.value < waterfall[0].value ? "bg-red-900/50 text-red-200" : "bg-emerald-900/50 text-emerald-200") : | |
| isNegative ? "bg-red-900/40 text-red-300" : "bg-emerald-900/40 text-emerald-300" | |
| )} | |
| style={{ width: `${width}%` }} | |
| > | |
| <span className="font-mono text-xs font-bold"> | |
| {step.value > 0 && step.type !== 'base' ? '+' : ''}{Math.round(step.value)}m | |
| </span> | |
| </div> | |
| </div> | |
| <div className="w-24 text-right text-[10px] text-neutral-500 ml-3"> | |
| {step.desc} | |
| </div> | |
| </div> | |
| ) | |
| })} | |
| </div> | |
| </div> | |
| {/* 5. PO IMBALANCE WARNING */} | |
| {po_imbalance?.detected && ( | |
| <div className="bg-amber-900/20 border border-amber-800 rounded-lg p-3 flex items-start gap-3"> | |
| <AlertTriangle className="h-5 w-5 text-amber-400 shrink-0 mt-0.5" /> | |
| <div> | |
| <div className="font-bold text-amber-200 text-sm"> | |
| Uneven PO Norm Allocation Detected | |
| <InfoTooltip content="Different POs for the same article got different norm %s. This may indicate minimum charge distortion." /> | |
| </div> | |
| <div className="text-xs text-amber-400 mt-1"> | |
| StdDev of Norm Gap: {po_imbalance.stddev}% across {po_imbalance.details?.length || 0} POs | |
| </div> | |
| {min_charge_distortion && ( | |
| <div className="text-xs text-red-400 mt-1">⚠️ Possible Minimum Charge Distortion</div> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |