// CRA2 — Meridian Supply Co. simulator. // Toggle each task between Human / Human + Agent / Agent (/ Eliminate) // and watch capacity, throughput, and cost per order respond. const MERIDIAN_TASKS = [ { id: 'docs', name: 'Verify order documents', hours: 120, leak: true, canEliminate: false, recommended: 'Agent', notes: { Agent: 'Verification is pattern work. Agents do it in seconds, with an audit trail.' } }, { id: 'entry', name: 'Re-enter data across 4 systems', hours: 90, leak: true, canEliminate: false, recommended: 'Agent', notes: { Agent: 'The same order was being typed in four times. Now it is typed zero times.' } }, { id: 'credit', name: 'Credit and risk review', hours: 60, leak: false, canEliminate: false, recommended: 'Human + Agent', notes: { 'Human + Agent': 'Agents prepare the file. People make the judgment call.' }, warns: { Agent: 'Flagged: high-risk judgment work. Fully delegating credit decisions gives up human oversight where it matters most. Tokaro would push back on this design.' } }, { id: 'welcome', name: 'Customer welcome calls', hours: 50, leak: false, canEliminate: false, recommended: 'Human', notes: { Agent: 'Meridian kept this one human. Relationships are judgment work.' } }, { id: 'status', name: 'Order status updates', hours: 40, leak: true, canEliminate: false, recommended: 'Agent', notes: {} }, { id: 'dup', name: 'Duplicate compliance re-checks', hours: 30, leak: true, canEliminate: true, recommended: 'Eliminate', notes: { Eliminate: 'Two teams were checking the same thing. Nobody had ever seen it in one place.' } } ]; const MERIDIAN_FACTORS = { Human: 1, 'Human + Agent': 0.55, Agent: 0.15, Eliminate: 0 }; const MERIDIAN_RATE = 65; // $/human-hour const MERIDIAN_AGENT_RATE = 8; // $/agent-run-hour equivalent const MERIDIAN_BASE_THROUGHPUT = 240; // orders/week function meridianTween(target, setter) { // smooth number tween return React.useEffect(() => { let raf, start; const from = setterCache.get(setter) ?? target; if (from === target) { setter(target); setterCache.set(setter, target); return; } const dur = 500; function tick(ts) { if (!start) start = ts; const t = Math.min(1, (ts - start) / dur); const eased = 1 - Math.pow(1 - t, 3); const v = from + (target - from) * eased; setter(v); if (t < 1) raf = requestAnimationFrame(tick); else setterCache.set(setter, target); } raf = requestAnimationFrame(tick); const settle = setTimeout(() => { setter(target); setterCache.set(setter, target); }, dur + 100); return () => { cancelAnimationFrame(raf); clearTimeout(settle); }; }, [target]); } const setterCache = new WeakMap(); function MeridianSim() { const [modes, setModes] = React.useState(() => Object.fromEntries(MERIDIAN_TASKS.map((t) => [t.id, 'Human']))); const totalBase = MERIDIAN_TASKS.reduce((s, t) => s + t.hours, 0); let humanHours = 0, agentCost = 0; MERIDIAN_TASKS.forEach((t) => { const m = modes[t.id]; humanHours += t.hours * MERIDIAN_FACTORS[m]; if (m === 'Agent') agentCost += t.hours * 0.1 * MERIDIAN_AGENT_RATE; if (m === 'Human + Agent') agentCost += t.hours * 0.05 * MERIDIAN_AGENT_RATE; }); const freed = totalBase - humanHours; const freedShare = freed / totalBase; const throughput = MERIDIAN_BASE_THROUGHPUT * (1 + freedShare * 0.8); const weeklyCost = humanHours * MERIDIAN_RATE + agentCost; const costPerOrder = weeklyCost / throughput; const baselineCost = (totalBase * MERIDIAN_RATE) / MERIDIAN_BASE_THROUGHPUT; const changedCount = MERIDIAN_TASKS.filter((t) => modes[t.id] !== 'Human').length; const monthsAhead = Math.round((changedCount / MERIDIAN_TASKS.length) * 18); // tweened display values const [dFreed, setDFreed] = React.useState(freed); const [dThru, setDThru] = React.useState(throughput); const [dCost, setDCost] = React.useState(costPerOrder); meridianTween(freed, setDFreed); meridianTween(throughput, setDThru); meridianTween(costPerOrder, setDCost); function setMode(id, m) { setModes((prev) => ({ ...prev, [id]: m })); } function reset() { setModes(Object.fromEntries(MERIDIAN_TASKS.map((t) => [t.id, 'Human']))); } function recommend() { setModes(Object.fromEntries(MERIDIAN_TASKS.map((t) => [t.id, t.recommended]))); } return (