simulator/tests/timing-2026-11.test.mjs
// Timing review (2026-11): loading → maintenance, induction order, premedication → induction,
// stop-before-end anchors, pre-incision verification, state-gated repeat boluses, timing validator.
// EDUCATIONAL SIMULATION — fictional patients — not a medical device.
import test from 'node:test';
import assert from 'node:assert/strict';
import { REAL, runFor, clinicianResolves } from './helpers.mjs';
import { derivePatient } from '../engine/patient.js';
import { createSimulation } from '../engine/sim.js';
import { EXAMPLE_PATIENTS, EXAMPLE_PLANS, buildPlan } from '../data/scenarios.js';
import { buildTemplatePlan, TECHNIQUE_TEMPLATES } from '../engine/templates.js';
import { createAutoRunner, buildSequence } from '../engine/autorun.js';
import { AUTORUN_CONFIG } from '../engine/autorun-config.js';
import { TIMING_CONFIG, PREMED_TO_INDUCTION_MIN_SEC, validatePlanTiming, liveTimingChecks, preIncisionItems, loadingPairs } from '../engine/timing.js';
import { makeSteps, loadingOverlapWarning, checkStep, getDrugById } from '../ui/plan-model.js';
if (REAL) {
const EX = (id) => derivePatient(EXAMPLE_PATIENTS.find((x) => x.id === id).patient, { library: REAL });
const sim0 = (p, plan, seed = 1) => createSimulation({ patient: p, plan, seed, library: REAL });
const DONE = ['completed', 'stopped', 'manual'];
const T = (k) => TIMING_CONFIG[k].value;
const firstDose = (sim, pred) => sim.doseLog.find(pred) || null;
/** Drives a runner like a user would (guided: approve enabled gates; both: clinician resolves clinician-only items). */
function drive(sim, r, mode, maxSec = 14000, onTick = null) {
for (let i = 0; i < maxSec && !DONE.includes(r.state); i++) {
if (r.state === 'paused') { for (const a of sim.state.alarms) if (a.priority === 'high' && !a.acknowledged) sim.action(`acknowledge:${a.id}`); if (sim.core.holdLatched) sim.action('resume_hold'); clinicianResolves(r); r.resume(); continue; }
let st = r.status();
if (mode === 'guided' && st.current && st.current.items && !st.current.enabled) { clinicianResolves(r); st = r.status(); }
if (mode === 'guided' && st.current && st.current.gate && st.current.enabled) { assert.ok(r.approve(st.current.id).ok); continue; }
sim.step(1); r.tick(); if (onTick) onTick();
}
}
// ------------------------------------------------------------------ 1. loading → maintenance
test('T1 mac_dex: dexmedetomidine loading 1 mcg/kg over 10 min at +0:00, maintenance at +10:00 (label: loading, then maintenance)', () => {
for (const ex of EXAMPLE_PATIENTS) {
const { plan } = buildTemplatePlan('mac_dex', EX(ex.id), { library: REAL });
const load = plan.steps.find((s) => s.drugId === 'dexmedetomidine' && s.loading);
const inf = plan.steps.find((s) => s.drugId === 'dexmedetomidine' && s.kind === 'infusion');
assert.equal(load.atSec, 0); assert.equal(load.overSec, 600);
assert.equal(inf.atSec, 600, `${ex.id}: maintenance starts when the loading ends`);
assert.match(inf.timingRationale.text, /Loading infusion over 10 min, then maintenance \(label\)/); assert.deepEqual([...inf.timingRationale.sources], ['S9']);
assert.ok(validatePlanTiming(plan, REAL).filter((c) => c.id === 'loading_maintenance').every((c) => c.status === 'pass'));
// live: the maintenance infusion starts at 10:00 after sedation starts, not before
const sim = sim0(EX(ex.id), plan); sim.setPhase('sedation'); runFor(sim, 700);
const infLog = firstDose(sim, (d) => d.drugId === 'dexmedetomidine' && d.kind === 'infusion');
const loadLog = firstDose(sim, (d) => d.drugId === 'dexmedetomidine' && d.kind === 'bolus');
assert.equal(infLog.tSec, loadLog.tSec + 600, 'maintenance starts exactly when the loading dose ends (anchored)');
assert.ok(liveTimingChecks(sim).filter((c) => c.id === 'loading_maintenance').every((c) => c.status === 'pass'));
}
});
test('T2 mac_propofol: propofol initiation 0.5 mg/kg over 5 min at +4:00 (after midazolam over ≥ 2 min + ≥ 2 min to evaluate; timing cross-check 2026-11), maintenance at +9:00 (label: initiation, then maintenance)', () => {
for (const ex of EXAMPLE_PATIENTS) {
const { plan } = buildTemplatePlan('mac_propofol', EX(ex.id), { library: REAL });
const load = plan.steps.find((s) => s.drugId === 'propofol' && s.loading);
const inf = plan.steps.find((s) => s.drugId === 'propofol' && s.kind === 'infusion');
assert.equal(load.atSec, TIMING_CONFIG.MAC_SEDATIVE_AFTER_MIDAZOLAM_SEC.value); assert.equal(load.atSec, 240); assert.equal(load.overSec, 300); assert.equal(inf.atSec, 540, ex.id);
}
// the MAC example plan uses the same rule
const p = EX('obese_osa'); const { plan } = buildPlan(EXAMPLE_PLANS.find((x) => x.id === 'mac_obese_osa'), p, REAL);
assert.equal(plan.steps.find((s) => s.drugId === 'propofol' && s.kind === 'infusion').atSec, 540);
});
test('T3 plan editor: adding a loading regimen schedules maintenance at loading end; an overlap gives a warning (editor + validator)', () => {
const p = EX('healthy_adult'); const c = { lib: REAL, patient: p, interlocks: false, patientVersion: 1 };
const { steps } = makeSteps(c, 'dexmedetomidine', 'sedation_moderate', 'sedation', 30);
assert.equal(steps.length, 2); assert.equal(steps[0].loading, true); assert.equal(steps[0].atSec, 30);
assert.equal(steps[1].atSec, 630, 'maintenance at loading start + 10 min');
assert.ok(steps[1].timingRationale && /then maintenance/.test(steps[1].timingRationale.text));
assert.equal(loadingOverlapWarning(steps[1], steps, getDrugById(REAL, 'dexmedetomidine')), null);
steps[1].atSec = 30; // user schedules them overlapping
const w = loadingOverlapWarning(steps[1], steps, getDrugById(REAL, 'dexmedetomidine'));
assert.match(w, /overlaps its loading dose/); assert.match(w, /set the infusion to \+10:30/);
assert.ok(checkStep(c, steps[1], steps).warnings.some((x) => /overlaps its loading dose/.test(x)));
const v = validatePlanTiming({ mode: 'MAC', steps }, REAL).find((x) => x.id === 'loading_maintenance');
assert.equal(v.status, 'warn'); assert.match(v.message, /overlap by 10:00/);
assert.equal(loadingPairs(steps).length, 1);
});
// ------------------------------------------------------------------ 2. induction order
test('T4 ga_opioid: fentanyl ≥ 3 min before propofol; lidocaine pretreatment immediately before propofol; rocuronium after loss of consciousness', () => {
for (const ex of EXAMPLE_PATIENTS) {
const { plan } = buildTemplatePlan('ga_opioid', EX(ex.id), { library: REAL });
const at = (id, rk) => plan.steps.find((s) => s.drugId === id && (!rk || s.regimenKey === rk)).atSec;
assert.ok(at('propofol', 'induction') - at('fentanyl') >= T('OPIOID_LEAD_SEC'), ex.id);
assert.equal(at('propofol', 'induction') - at('lidocaine', 'pretreatment'), 10);
assert.ok(at('rocuronium') - at('propofol', 'induction') >= 40, 'after the label hypnosis onset (40 s)');
const v = validatePlanTiming(plan, REAL);
for (const id of ['opioid_hypnotic', 'pretreatment', 'nmb_after_hypnotic', 'ponv_dexamethasone', 'ponv_ondansetron', 'reversal', 'antibiotic_window', 'premed_interval']) {
const x = v.find((y) => y.id === id); assert.ok(x, id); assert.notEqual(x.status, 'warn', `${ex.id} ${id}: ${x.message}`);
}
}
// the GA example plans follow the same spacing (no opioid → hypnotic warning)
for (const tpl of EXAMPLE_PLANS.filter((x) => x.mode === 'GA')) {
const { plan } = buildPlan(tpl, EX(tpl.patientId), REAL);
assert.equal(validatePlanTiming(plan, REAL).find((x) => x.id === 'opioid_hypnotic').status, 'pass', tpl.id);
}
assert.equal(TIMING_CONFIG.OPIOID_LEAD_SEC.provenance, 'simulator_convention');
assert.match(TIMING_CONFIG.OPIOID_LEAD_SEC.rationale, /several minutes \[S13\]/);
assert.match(REAL.SOURCES.S13.supports, /maximal analgesic and respiratory depressant effect may not be noted for several minutes/);
});
// ------------------------------------------------------------------ 3. every template step has a timing rationale
test('T5 every template step has a timingRationale with sources or an explicit convention provenance; sources exist', () => {
const PROV = ['label', 'guideline', 'consensus', 'trial', 'simulator convention', 'clinical practice convention (owner)'];
for (const t of TECHNIQUE_TEMPLATES) for (const s of t.steps) {
const r = s.timingRationale; const L = `${t.id} ${s.phase} ${s.drugId || s.fluidId} ${s.kind}`;
assert.ok(r && typeof r.text === 'string' && r.text.length > 20, `${L}: text`);
assert.ok(PROV.includes(r.provenance), `${L}: provenance ${r.provenance}`);
assert.ok(r.sources.length > 0 || /convention/.test(r.provenance), `${L}: sources or convention`);
for (const id of r.sources) assert.ok(REAL.SOURCES[id], `${L}: ${id}`);
assert.ok(!/(?<!not |un|never )\bvalidated\b|\baccurate\b|closed-loop/i.test(r.text), `${L}: wording`);
}
// carried into the built plan (plan editor and timeline show it)
const { plan } = buildTemplatePlan('ga_opioid', EX('healthy_adult'), { library: REAL });
assert.ok(plan.steps.every((s) => s.timingRationale), 'every built step keeps its rationale');
// and listed in the parameter inventory
const inv = sim0(EX('healthy_adult'), plan).parameterInventory();
assert.equal(inv.filter((x) => x.component === 'template_timing').length, TECHNIQUE_TEMPLATES.reduce((a, t) => a + t.steps.length, 0));
assert.equal(inv.filter((x) => x.component === 'timing').length, Object.keys(TIMING_CONFIG).length);
});
// ------------------------------------------------------------------ 4. dexmedetomidine stops before the end of surgery
test('T6 ga_opioid_free: dexmedetomidine stops 15 min before the planned end (full demonstration) / at End surgery (guided); lidocaine infusion after its loading', () => {
const p = EX('healthy_adult');
const { plan } = buildTemplatePlan('ga_opioid_free', p, { library: REAL });
const stop = plan.steps.find((s) => s.kind === 'stop' && s.drugId === 'dexmedetomidine');
assert.equal(stop.phase, 'maintenance'); assert.equal(stop.beforeEndSec, T('DEX_STOP_BEFORE_END_SEC'));
assert.equal(validatePlanTiming(plan, REAL).find((x) => x.id === 'stop_before_end').status, 'pass');
// full demonstration
const sim = sim0(p, plan); const r = createAutoRunner({ sim, mode: 'full' }); r.start(); drive(sim, r, 'full');
assert.equal(r.state, 'completed', r.message);
const ex = sim.executed.get(stop.id);
const surgStart = r.history.find((h) => h.id === 'start_surgery').tSec;
assert.equal(ex.tSec, surgStart + AUTORUN_CONFIG.SURGERY_DURATION_SEC.value - T('DEX_STOP_BEFORE_END_SEC'), 'stopped at planned end − 15 min');
assert.ok(ex.tSec < sim.stim.surgeryEndAt);
assert.equal(liveTimingChecks(sim).find((x) => x.id === 'stop_before_end').status, 'pass');
// lidocaine infusion only after its loading dose (anchored across phases)
const lidoLoad = firstDose(sim, (d) => d.drugId === 'lidocaine' && d.kind === 'bolus');
const lidoInf = firstDose(sim, (d) => d.drugId === 'lidocaine' && d.kind === 'infusion' && d.rate > 0);
assert.ok(lidoInf.tSec >= lidoLoad.tSec + lidoLoad.overSec, `lidocaine infusion ${lidoInf.tSec} after loading end ${lidoLoad.tSec + lidoLoad.overSec}`);
// guided: no planned end → the stop runs when the clinician presses End surgery
const g = sim0(p, plan); const rg = createAutoRunner({ sim: g, mode: 'guided' }); rg.start();
let stoppedBeforeEnd = false;
drive(g, rg, 'guided', 14000, () => { if (g.executed.has(stop.id) && g.stim.surgeryEndAt === null) stoppedBeforeEnd = true; });
assert.equal(rg.state, 'completed', rg.message);
assert.equal(stoppedBeforeEnd, false, 'guided: not stopped before End surgery (no planned end)');
const lag = g.executed.get(stop.id).tSec - g.stim.surgeryEndAt;
assert.ok(lag >= 0 && lag <= 1, `guided: stopped at End surgery (next simulated second; lag ${lag} s)`);
// manual: the emergence fallback also stops it if End surgery is never pressed
const m = sim0(p, plan); m.setPhase('induction'); runFor(m, 300); m.setPhase('maintenance'); runFor(m, 60); m.setPhase('emergence'); runFor(m, 2);
assert.ok(m.executed.has(stop.id));
// invalid anchor on a non-stop step never runs
const bad = sim0(p, { mode: 'GA', steps: [{ id: 'x', phase: 'maintenance', atSec: 0, kind: 'bolus', drugId: 'fentanyl', amount: 25, amountUnit: 'mcg', beforeEndSec: 60 }] });
assert.match(bad.plan.steps[0].invalid, /beforeEndSec/);
});
// ------------------------------------------------------------------ 5. premedication never overlaps induction
const withPremed = (plan) => ({ ...plan, steps: [{ id: 'pm', phase: 'premed', atSec: 0, order: -1, drugId: 'midazolam', kind: 'bolus', amount: 1, amountUnit: 'mg', overSec: 120, regimenKey: 'premed' }, ...plan.steps] });
for (const mode of ['guided', 'full']) {
test(`T7 premedication midazolam never within the minimum interval of any induction/sedation drug — ${mode}, all templates × all example patients`, () => {
let checked = 0;
for (const t of TECHNIQUE_TEMPLATES) for (const ex of EXAMPLE_PATIENTS) {
const p = EX(ex.id); let { plan } = buildTemplatePlan(t.id, p, { library: REAL });
if (!plan.steps.some((s) => s.phase === 'premed' && s.kind === 'bolus')) plan = withPremed(plan); // MAC / opioid-free: add one
const sim = sim0(p, plan); const r = createAutoRunner({ sim, mode }); r.start(); drive(sim, r, mode);
const pre = sim.plan.steps.filter((s) => s.phase === 'premed' && s.kind === 'bolus' && sim.executed.has(s.id));
if (!pre.length) continue;
const end = Math.max(...pre.map((s) => sim.executed.get(s.id).tSec + (s.overSec || 0)));
const first = firstDose(sim, (d) => d.phase === 'induction' || d.phase === 'sedation');
assert.ok(first, `${t.id}/${ex.id}: an induction/sedation drug was given`);
const min = t.mode === 'GA' ? PREMED_TO_INDUCTION_MIN_SEC : PREMED_TO_INDUCTION_MIN_SEC - T('PREOXYGENATION_SEC');
assert.ok(first.tSec - end >= min - 1e-9, `${t.id}/${ex.id}: first ${first.drugId} ${first.tSec - end} s after premedication ended (min ${min})`);
assert.equal(liveTimingChecks(sim).find((x) => x.id === 'premed_interval').status, 'pass');
checked++;
}
assert.equal(checked, TECHNIQUE_TEMPLATES.length * EXAMPLE_PATIENTS.length);
});
}
test('T8 engine guard: induction drugs never start while a premedication bolus is pending or running (manual mode)', () => {
const p = EX('healthy_adult'); const { plan } = buildPlan(EXAMPLE_PLANS.find((x) => x.id === 'ga_healthy'), p, REAL);
const sim = sim0(p, plan); runFor(sim, 10); sim.setPhase('induction'); // clinician starts induction too early
runFor(sim, 30);
const fent = sim.planStatus().find((s) => s.drugId === 'fentanyl');
assert.equal(fent.status, 'pending'); assert.match(fent.waiting, /premedication bolus has finished/);
runFor(sim, 200);
const f = firstDose(sim, (d) => d.drugId === 'fentanyl'); const mid = firstDose(sim, (d) => d.drugId === 'midazolam');
assert.equal(f.tSec, mid.tSec + 120, 'fentanyl at the end of the 2-min midazolam injection');
const c = liveTimingChecks(sim).find((x) => x.id === 'premed_interval');
assert.equal(c.status, 'warn', 'early induction is flagged'); assert.match(c.message, /0:00 after the premedication ended \(minimum 10:00\)/);
});
// ------------------------------------------------------------------ 6. gate countdowns
test('T9 guided gates: Transfer to OR and Start induction stay disabled with a countdown until the interval has elapsed', () => {
const p = EX('healthy_adult'); const { plan } = buildTemplatePlan('ga_opioid', p, { library: REAL });
const sim = sim0(p, plan); const r = createAutoRunner({ sim, mode: 'guided' }); r.start();
sim.step(1); r.tick(); // premedication midazolam starts (first simulated second), given over 2 min (label: over at least 2 min)
const preEnd = sim.doseLog.find((d) => d.drugId === 'midazolam').tSec + 120;
while (sim.state.tSec < preEnd + T('PREMED_TO_TRANSFER_SEC') - 1) { sim.step(1); r.tick(); }
let st = r.status();
assert.equal(st.current.id, 'transfer_to_or'); assert.equal(st.current.enabled, false); assert.equal(st.current.countdownSec, 1);
assert.match(st.current.unmet[0], /transfer 5 min after the premedication ended — 1 s to go/);
assert.equal(r.approve('transfer_to_or').ok, false);
sim.step(1); r.tick(); st = r.status();
assert.equal(st.current.enabled, true); assert.equal(st.current.countdownSec, 0);
assert.ok(r.approve('transfer_to_or').ok);
// OR setup (automatic), then preoxygenation; induction gate counts down
for (let i = 0; i < T('OR_SETUP_SEC'); i++) { sim.step(1); r.tick(); }
assert.equal(r.current.id, 'start_induction'); assert.equal(sim.state.airway.fio2, 1, 'preoxygenation FiO2 1.0');
st = r.status(); assert.equal(st.current.enabled, false); assert.ok(st.current.countdownSec > 0 && st.current.countdownSec <= T('PREOXYGENATION_SEC'));
for (let i = 0; i < T('PREOXYGENATION_SEC') - 1; i++) { sim.step(1); r.tick(); }
assert.equal(r.status().current.enabled, false);
sim.step(1); r.tick();
assert.equal(r.status().current.enabled, true);
assert.equal(sim.state.tSec - preEnd, PREMED_TO_INDUCTION_MIN_SEC, 'induction possible exactly at the minimum interval');
assert.deepEqual(buildSequence('GA').slice(0, 3).map((s) => s.id), ['transfer_to_or', 'preoxygenate', 'start_induction']);
});
// ------------------------------------------------------------------ 7. pre-incision verification: one test per item
function atIncisionGate() {
const p = EX('healthy_adult'); const { plan } = buildTemplatePlan('ga_opioid', p, { library: REAL });
const sim = sim0(p, plan); const r = createAutoRunner({ sim, mode: 'guided' }); r.start();
for (let i = 0; i < 6000 && !(r.current && r.current.id === 'start_surgery'); i++) {
if (r.state === 'paused') { for (const a of sim.state.alarms) if (a.priority === 'high' && !a.acknowledged) sim.action(`acknowledge:${a.id}`); if (sim.core.holdLatched) sim.action('resume_hold'); r.resume(); continue; }
const st = r.status(); if (st.current.enabled) { r.approve(st.current.id); continue; }
sim.step(1); r.tick();
}
assert.equal(r.current.id, 'start_surgery');
idle(sim, r, 120);
return { sim, r };
}
/** Steps time; the user resolves interlock holds / HIGH alarms (never clinician-only items). */
function idle(sim, r, n) {
for (let i = 0; i < n; i++) {
sim.step(1); r.tick();
if (r.state === 'paused') { for (const a of sim.state.alarms) if (a.priority === 'high' && !a.acknowledged) sim.action(`acknowledge:${a.id}`); if (sim.core.holdLatched) sim.action('resume_hold'); r.resume(); }
}
}
const gateOpen = (r) => r.status().current.enabled;
const item = (r, id) => r.status().current.items.find((x) => x.id === id);
test('T10 pre-incision gate: WHO time out missing keeps it closed; only the clinician confirms it (sourced S54)', () => {
const { sim, r } = atIncisionGate();
assert.equal(item(r, 'time_out').met, false); assert.equal(item(r, 'time_out').clinician, true); assert.deepEqual(item(r, 'time_out').sources, ['S54']);
assert.equal(gateOpen(r), false); assert.equal(r.approve('start_surgery').ok, false);
idle(sim, r, 300);
assert.equal(sim.state.periop.timeOut, null, 'never confirmed automatically'); assert.equal(sim.stim.surgeryStartAt, null);
clinicianResolves(r);
assert.ok(sim.state.periop.timeOut); assert.equal(gateOpen(r), true, JSON.stringify(r.status().current.unmet));
assert.ok(sim.state.log.some((e) => /time out confirmed by the clinician/.test(e.message)));
});
const breakItem = (name, mutate, restore, id) => test(`T11 pre-incision gate: ${name} missing keeps it closed`, () => {
const { sim, r } = atIncisionGate(); clinicianResolves(r);
assert.equal(gateOpen(r), true, 'all items met');
const saved = mutate(sim);
assert.equal(item(r, id).met, false, `${id} unmet`); assert.equal(gateOpen(r), false); assert.equal(r.approve('start_surgery').ok, false);
restore(sim, saved); assert.equal(gateOpen(r), true);
});
breakItem('airway/ventilation', (s) => { s.state.airway.ventilated = false; }, (s) => { s.state.airway.ventilated = true; }, 'airway');
breakItem('depth index above the GA band', (s) => { const v = s.state.vitals.depthIndex; s.state.vitals.depthIndex = 70; return v; }, (s, v) => { s.state.vitals.depthIndex = v; }, 'depth');
breakItem('depth index below the GA band without acknowledgement', (s) => { const v = [s.state.vitals.depthIndex, s.state.periop.depthAck]; s.state.vitals.depthIndex = 30; s.state.periop.depthAck = null; return v; }, (s, v) => { s.state.vitals.depthIndex = v[0]; s.state.periop.depthAck = v[1]; }, 'depth');
breakItem('analgesia', (s) => { const d = s.doseLog; s.doseLog = d.filter((x) => x.drugId !== 'fentanyl'); return d; }, (s, d) => { s.doseLog = d; }, 'analgesia');
breakItem('neuromuscular block (TOF count > 2)', (s) => { const v = s.state.vitals.tofCount; s.state.vitals.tofCount = 4; return v; }, (s, v) => { s.state.vitals.tofCount = v; }, 'nmb');
breakItem('antibiotic in window', (s) => { const d = s.state.prophylaxis.doses; s.state.prophylaxis.doses = []; return d; }, (s, d) => { s.state.prophylaxis.doses = d; }, 'antibiotic');
test('T12 pre-incision items: GA band from B-Unaware/BAG-RECALL (S52, S53); an index below the band needs the clinician; MAC items include the time out', () => {
assert.deepEqual([...TIMING_CONFIG.GA_DEPTH_BAND.value], [40, 60]); assert.deepEqual([...TIMING_CONFIG.GA_DEPTH_BAND.sourceIds], ['S52', 'S53']);
for (const id of ['S52', 'S53', 'S54']) assert.ok(REAL.SOURCES[id] && REAL.SOURCES[id].verifiedVia, id);
const { sim, r } = atIncisionGate(); r.clinicianAction('confirm_time_out');
sim.state.vitals.depthIndex = 35;
const d = item(r, 'depth'); assert.equal(d.met, false); assert.equal(d.clinician, true); assert.match(d.detail, /clinician must acknowledge/);
assert.ok(r.clinicianAction('acknowledge_depth_below_band').ok);
assert.equal(item(r, 'depth').met, true);
const mac = preIncisionItems(sim, { planMode: 'MAC', sedationCheck: { met: true, detail: 'moderate' } }).map((x) => x.id);
assert.ok(mac.includes('time_out') && mac.includes('sedation') && !mac.includes('airway'));
});
test('T13 full demonstration never self-confirms: it pauses for the time out and surgery never starts without the clinician', () => {
const p = EX('healthy_adult'); const { plan } = buildTemplatePlan('ga_opioid', p, { library: REAL });
const sim = sim0(p, plan); const r = createAutoRunner({ sim, mode: 'full' }); r.start();
let timeOutPause = null;
for (let i = 0; i < 6000 && !DONE.includes(r.state); i++) {
if (r.state === 'paused') {
const tp = r.pauseReasons.find((x) => x.item === 'time_out');
if (tp) { timeOutPause = tp; break; }
for (const a of sim.state.alarms) if (a.priority === 'high' && !a.acknowledged) sim.action(`acknowledge:${a.id}`); if (sim.core.holdLatched) sim.action('resume_hold'); r.resume(); continue;
}
sim.step(1); r.tick();
}
assert.ok(timeOutPause, 'paused for the WHO time out'); assert.match(timeOutPause.message, /never confirms it/);
assert.equal(r.resume().ok, false, 'resume refused until the clinician confirms');
for (let i = 0; i < 600; i++) { sim.step(1); r.tick(); }
assert.equal(sim.state.periop.timeOut, null); assert.equal(sim.stim.surgeryStartAt, null);
assert.equal(r.current.id, 'start_surgery');
});
// ------------------------------------------------------------------ 8. state-gated repeat boluses
test('T14 rocuronium top-up is held until TOF count ≥ 3 (label: 25% recovery of control T1 = 3 twitches); manual top-up warned', () => {
const p = EX('healthy_adult');
const { plan } = buildPlan({ mode: 'GA', steps: [
{ phase: 'induction', atSec: 0, drugId: 'propofol', regimenKey: 'induction', kind: 'bolus' },
{ phase: 'induction', atSec: 60, drugId: 'rocuronium', regimenKey: 'induction', kind: 'bolus' },
{ phase: 'maintenance', atSec: 0, drugId: 'propofol', regimenKey: 'maintenance', kind: 'infusion' },
{ phase: 'maintenance', atSec: 60, drugId: 'rocuronium', regimenKey: 'maintenance', kind: 'bolus' },
] }, p, REAL);
const sim = sim0(p, plan); sim.setPhase('induction'); runFor(sim, 200); sim.action('secure_airway'); sim.action('start_ventilation'); sim.setPhase('maintenance');
runFor(sim, 120);
const top = sim.planStatus().find((s) => s.drugId === 'rocuronium' && s.phase === 'maintenance');
assert.equal(top.status, 'pending'); assert.match(top.waiting, /TOF count ≥ 3/);
assert.ok(sim.state.vitals.tofCount < 3);
let i = 0; for (; i < 7200 && !sim.executed.has(top.id); i++) sim.step(1);
const ex = firstDose(sim, (d) => d.drugId === 'rocuronium' && d.phase === 'maintenance');
assert.ok(ex && ex.tofCount >= 3, `top-up given at TOF count ${ex && ex.tofCount}`);
assert.ok(liveTimingChecks(sim).some((c) => c.id === 'repeat_bolus' && /held/.test(c.message)));
assert.equal(validatePlanTiming(plan, REAL).find((c) => c.label === 'Rocuronium top-up').status, 'pass');
// manual top-up at deep block: allowed (clinician's choice) but warned and recorded
const m = sim0(p, plan); m.administer({ drugId: 'rocuronium', kind: 'bolus', amount: 40, amountUnit: 'mg' }); runFor(m, 120);
const w = m.administer({ drugId: 'rocuronium', kind: 'bolus', amount: 10, amountUnit: 'mg' });
assert.ok(w.ok && w.warnings.some((x) => /Repeat-bolus condition not met: rocuronium top-up waits for TOF count ≥ 3.*\[S12\]/.test(x)), w.warnings.join(' | '));
assert.equal(liveTimingChecks(m).find((c) => c.id === 'repeat_bolus').status, 'warn');
});
test('T15 repeat midazolam waits 4 min (label: over ≥ 2 min, then ≥ 2 min to evaluate)', () => {
const p = EX('healthy_adult');
const { plan } = buildPlan({ mode: 'MAC', sedationTarget: 'moderate', steps: [
{ phase: 'sedation', atSec: 0, drugId: 'midazolam', regimenKey: 'sedation_moderate', kind: 'bolus' },
{ phase: 'sedation', atSec: 60, drugId: 'midazolam', regimenKey: 'sedation_moderate', kind: 'bolus' },
] }, p, REAL);
assert.equal(validatePlanTiming(plan, REAL).find((c) => c.id === 'repeat_bolus').status, 'warn');
const sim = sim0(p, plan); sim.setPhase('sedation'); runFor(sim, 300);
const doses = sim.doseLog.filter((d) => d.drugId === 'midazolam');
assert.equal(doses.length, 2); assert.equal(doses[1].tSec - doses[0].tSec, T('MIDAZOLAM_REPEAT_MIN_SEC'));
});
// ------------------------------------------------------------------ 9. the validator flags each violation type
test('T16 plan timing validator: a crafted bad plan triggers every plan-level warning type', () => {
const p = EX('healthy_adult');
const { plan } = buildPlan({ mode: 'GA', steps: [
{ phase: 'premed', atSec: 0, drugId: 'midazolam', regimenKey: 'premed', kind: 'bolus' },
{ phase: 'induction', atSec: 0, drugId: 'fentanyl', regimenKey: 'induction', kind: 'bolus' },
{ phase: 'induction', atSec: 30, drugId: 'propofol', regimenKey: 'induction', kind: 'bolus' }, // opioid only 30 s before
{ phase: 'induction', atSec: 0, drugId: 'lidocaine', regimenKey: 'pretreatment', kind: 'bolus' }, // not immediately before
{ phase: 'induction', atSec: 40, drugId: 'rocuronium', regimenKey: 'induction', kind: 'bolus' }, // 10 s after the hypnotic
{ phase: 'maintenance', atSec: 0, drugId: 'dexmedetomidine', regimenKey: 'sedation_moderate', kind: 'infusion' },
{ phase: 'maintenance', atSec: 0, drugId: 'sevoflurane', regimenKey: 'maintenance', kind: 'inhaled', dial: 'mac1' },
{ phase: 'maintenance', atSec: 0, drugId: 'ketamine', kind: 'stop', beforeEndSec: 600 }, // nothing to stop
{ phase: 'maintenance', atSec: 0, drugId: 'midazolam', regimenKey: 'premed', kind: 'bolus' },
{ phase: 'maintenance', atSec: 30, drugId: 'midazolam', regimenKey: 'premed', kind: 'bolus' },
{ phase: 'maintenance', atSec: 60, drugId: 'sugammadex', regimenKey: 'emergence', kind: 'bolus' }, // reversal during maintenance
{ phase: 'emergence', atSec: 0, drugId: 'dexamethasone', regimenKey: 'induction', kind: 'bolus' }, // not at induction
{ phase: 'induction', atSec: 0, drugId: 'ondansetron', regimenKey: 'emergence', kind: 'bolus' }, // not at end of surgery
] }, p, REAL);
plan.prophylaxisIndicated = true; // no antibiotic planned
// the dexmedetomidine loading + infusion are scheduled correctly by buildPlan: make them overlap like a user would
plan.steps.find((s) => s.drugId === 'dexmedetomidine' && s.kind === 'infusion').atSec = 0;
const v = validatePlanTiming(plan, REAL);
const warn = new Set(v.filter((x) => x.status === 'warn').map((x) => x.id));
for (const id of ['loading_maintenance', 'opioid_hypnotic', 'pretreatment', 'nmb_after_hypnotic', 'antibiotic_window', 'ponv_dexamethasone', 'ponv_ondansetron', 'reversal', 'repeat_bolus', 'stop_before_end']) assert.ok(warn.has(id), `${id} flagged: ${[...warn].join(', ')}`);
for (const x of v) assert.ok(x.sources.length > 0 || /convention/.test(x.provenance) || x.status === 'info', `${x.id}: source or convention`);
});
}
test('T17 timing config: one frozen block; every rule has value, unit, rationale, provenance; conventions carry no sourceIds', () => {
assert.ok(Object.isFrozen(TIMING_CONFIG));
for (const [k, x] of Object.entries(TIMING_CONFIG)) {
assert.ok(Object.isFrozen(x), k); assert.ok(x.value !== undefined && x.value !== null, k);
assert.ok(typeof x.unit === 'string' && x.unit, k); assert.ok(x.rationale.length > 30, k);
assert.ok(['fda_label', 'guideline', 'trial_or_physiology_study', 'simulator_convention', 'clinical_practice_convention_owner'].includes(x.provenance), `${k}: ${x.provenance}`);
if (x.provenance === 'simulator_convention') assert.equal(x.sourceIds.length, 0, `${k}: convention`);
else assert.ok(x.sourceIds.length > 0, `${k}: sourced`);
if (REAL) for (const id of x.sourceIds) assert.ok(REAL.SOURCES[id], `${k}: ${id}`);
}
assert.equal(PREMED_TO_INDUCTION_MIN_SEC, 600);
assert.equal(TIMING_CONFIG.PREMED_TO_TRANSFER_SEC.provenance, 'clinical_practice_convention_owner');
});
if (REAL) {
test('T18 live timing checks of complete runs: opioid → hypnotic spacing and hypnotic → NMB pass for every GA template run (1-Hz stepping tolerance)', () => {
for (const ex of EXAMPLE_PATIENTS) {
const p = derivePatient(ex.patient, { library: REAL });
const { plan } = buildTemplatePlan('ga_opioid', p, { library: REAL });
const sim = createSimulation({ patient: p, plan, seed: 1, library: REAL }); const r = createAutoRunner({ sim, mode: 'full' }); r.start();
for (let i = 0; i < 14000 && !['completed', 'stopped', 'manual'].includes(r.state); i++) {
if (r.state === 'paused') { for (const a of sim.state.alarms) if (a.priority === 'high' && !a.acknowledged) sim.action(`acknowledge:${a.id}`); if (sim.core.holdLatched) sim.action('resume_hold'); clinicianResolves(r); r.resume(); continue; }
sim.step(1); r.tick();
}
const c = liveTimingChecks(sim);
for (const id of ['opioid_hypnotic', 'pretreatment', 'nmb_after_hypnotic', 'premed_interval', 'antibiotic_window', 'ponv_dexamethasone', 'ponv_ondansetron']) {
const x = c.find((y) => y.id === id); if (!x) continue; assert.equal(x.status, 'pass', `${ex.id} ${id}: ${x.message}`);
}
const o = sim.doseLog.find((d) => d.drugId === 'fentanyl'); const h = sim.doseLog.find((d) => d.drugId === 'propofol' && d.regimenKey === 'induction');
assert.ok(h.tSec - o.tSec >= TIMING_CONFIG.OPIOID_LEAD_SEC.value - 1, `${ex.id}: ${h.tSec - o.tSec} s`);
}
});
}