Independent education · Sources and limitations documented
Portfolio / Smart NOA simulation

Software simulation

Source snapshot · 5 November 2026

simulator/tests/autorun.test.mjs

// Guided (phase-gated) mode and "Run full case automatically" (engine/autorun.js).
// EDUCATIONAL SIMULATION — fictional patients — not a medical device.
import test from 'node:test';
import assert from 'node:assert/strict';
import { REAL, clinicianResolves } from './helpers.mjs';
import { derivePatient } from '../engine/patient.js';
import { createSimulation, ALARM_THRESHOLDS } from '../engine/sim.js';
import { NOA_CONFIG } from '../engine/interlocks.js';
import { EXAMPLE_PATIENTS, EXAMPLE_PLANS, buildPlan } from '../data/scenarios.js';
import { createAutoRunner, buildSequence, BANNERS } from '../engine/autorun.js';
import { AUTORUN_CONFIG, CONVENTION } from '../engine/autorun-config.js';
import { CASE_ACTIONS } from '../engine/case-actions.js';

const DONE = ['completed', 'stopped', 'manual'];

/** Builds an example case with spies on sim.action and on the case actions. */
function setup(planId, { seed = 1, mode = 'full', editPlan = null } = {}) {
  const tpl = EXAMPLE_PLANS.find((p) => p.id === planId);
  const patient = derivePatient(EXAMPLE_PATIENTS.find((p) => p.id === tpl.patientId).patient, { library: REAL });
  const { plan } = buildPlan(tpl, patient, REAL);
  if (editPlan) editPlan(plan);
  const before = JSON.parse(JSON.stringify(plan.steps));
  const sim = createSimulation({ patient, plan, seed, library: REAL });
  const calls = { runner: [], user: [], caseActions: [] };
  let byUser = false;
  const action = sim.action.bind(sim);
  sim.action = (id) => { (byUser ? calls.user : calls.runner).push(id); return action(id); };
  const actions = Object.fromEntries(Object.entries(CASE_ACTIONS).map(([k, fn]) => [k, (...a) => { calls.caseActions.push(k); return fn(...a); }]));
  const runner = createAutoRunner({ sim, mode, actions });
  const asUser = (fn) => { byUser = true; try { return fn(); } finally { byUser = false; } };
  /** What a user does when the runner pauses: acknowledge HIGH alarms, release a latched hold, Resume. */
  const userResolves = () => asUser(() => {
    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(runner); // clinician-only items (time out, index below band, missing prophylaxis)
    return runner.resume();
  });
  /** Guided: the clinician confirms clinician-only items at the pre-incision / pre-procedure gate. */
  const clinician = () => asUser(() => clinicianResolves(runner));
  return { tpl, patient, plan, before, sim, runner, calls, asUser, userResolves, clinician };
}

function driveFull(c, maxSec = 12000) {
  const { sim, runner } = c; let pauses = 0;
  for (let i = 0; i < maxSec && !DONE.includes(runner.state); i++) {
    if (runner.state === 'paused') { pauses++; c.userResolves(); continue; }
    sim.step(1); runner.tick();
  }
  return pauses;
}

/** Guided: approve each gate as soon as it is enabled, recording what was seen. */
function driveGuided(c, maxSec = 12000, { onGate = null } = {}) {
  const { sim, runner } = c; const gates = [];
  for (let i = 0; i < maxSec && !DONE.includes(runner.state); i++) {
    if (runner.state === 'paused') { c.userResolves(); continue; }
    const st = runner.status();
    if (st.current && st.current.gate) {
      if (gates[gates.length - 1] !== st.current.id) gates.push(st.current.id);
      if (onGate) onGate(st, c);
      if (st.current.items && !st.current.enabled) c.clinician();
      if (runner.status().current.enabled) { const r = runner.approve(st.current.id); assert.ok(r.ok, r.message); continue; }
    }
    sim.step(1); runner.tick();
  }
  return gates;
}

function assertAutoLogged(c) {
  const log = c.sim.state.log.filter((e) => e.kind === 'auto');
  assert.ok(log.every((e) => e.message.startsWith('AUTO: ')));
  const n = c.runner.seq.length;
  c.runner.history.forEach((h, i) => assert.ok(log.some((e) => e.message.startsWith(`AUTO: step ${i + 1}/${n} — ${h.label}`) && e.tSec === Math.round(h.tSec * 10) / 10), `step ${h.id} logged`));
}

function assertUnchangedPlan(c) {
  assert.deepEqual(c.plan.steps, c.before, 'plan object unchanged');
  const pick = (s) => ({ id: s.id, drugId: s.drugId, kind: s.kind, amount: s.amount, amountUnit: s.amountUnit, rate: s.rate, rateUnit: s.rateUnit, atSec: s.atSec, phase: s.phase });
  assert.deepEqual(c.sim.plan.steps.map(pick), c.before.map(pick), 'simulation plan amounts/rates unchanged');
}

function assertNoRunnerOverrides(c) {
  assert.ok(!c.calls.caseActions.includes('extubateOverride'), 'override path never called');
  assert.ok(!c.calls.runner.some((id) => id === 'resume_hold' || id.startsWith('acknowledge:')), `runner never released holds/acknowledged: ${c.calls.runner.join(',')}`);
  assert.ok(!c.sim.state.log.some((e) => /despite unmet readiness/.test(e.message)));
}

const GA_GATES = ['transfer_to_or', 'start_induction', 'secure_airway', 'start_maintenance', 'start_surgery', 'end_surgery', 'begin_emergence', 'extubate'];
const MAC_GATES = ['transfer_to_or', 'start_sedation', 'start_procedure', 'end_procedure'];
const CLINICIAN_CASE_ACTIONS = ['confirm_time_out', 'acknowledge_depth_below_band'];

// ---------------------------------------------------------------- config block
test('autorun config: one frozen block; every entry has value, unit, rationale and provenance; sourceIds exist', () => {
  assert.ok(Object.isFrozen(AUTORUN_CONFIG));
  const entries = Object.entries(AUTORUN_CONFIG);
  assert.ok(entries.length >= 20);
  for (const [k, x] of entries) {
    assert.ok(Object.isFrozen(x), k);
    assert.ok(x.value !== undefined && x.value !== null, `${k}: value`);
    assert.ok(typeof x.unit === 'string' && x.unit, `${k}: unit`);
    assert.ok(typeof x.rationale === 'string' && x.rationale.length > 20, `${k}: rationale`);
    assert.ok(['both', 'guided', 'full'].includes(x.appliesTo), `${k}: appliesTo`);
    assert.ok(Array.isArray(x.sourceIds), `${k}: sourceIds`);
    if (x.provenance === CONVENTION) assert.equal(x.sourceIds.length, 0, `${k}: convention has no sources`);
    else { assert.ok(['guideline', 'project_config'].includes(x.provenance), `${k}: provenance ${x.provenance}`); assert.ok(x.sourceIds.length > 0, `${k}: sourced`); }
    if (REAL) for (const id of x.sourceIds) assert.ok(REAL.SOURCES[id], `${k}: source ${id} exists`);
    assert.ok(!/(?<!not |un|never )\bvalidated\b|\bpredictive\b|\baccurate\b|closed-loop/i.test(x.rationale), `${k}: wording`);
  }
  // reused thresholds are the engine's sourced values, not new ones
  assert.equal(AUTORUN_CONFIG.EXTUBATION_TOFR_MIN.value, ALARM_THRESHOLDS.TOFR_EXTUBATION);
  assert.deepEqual([...AUTORUN_CONFIG.EXTUBATION_TOFR_MIN.sourceIds], [REAL ? REAL.SOURCE_IDS_ENGINE.tofExtubation : 'S5']);
  assert.equal(AUTORUN_CONFIG.INTERLOCK_MAP_CRITICAL_LOW.value, NOA_CONFIG.hemodynamic_thresholds.map_critical_low);
  assert.equal(AUTORUN_CONFIG.INTERLOCK_HR_CRITICAL_LOW.value, NOA_CONFIG.hemodynamic_thresholds.hr_critical_low);
  if (REAL) assert.deepEqual([...AUTORUN_CONFIG.SEDATION_TARGET_LEVEL.sourceIds], [REAL.SOURCE_IDS_ENGINE.sedationContinuum]);
  assert.deepEqual([...AUTORUN_CONFIG.PAUSE_ALARM_PRIORITIES.value], ['high'], 'medium/low alarms do not pause');
  // every timing used by the sequences comes from the block
  for (const m of ['GA', 'MAC']) for (const s of buildSequence(m)) for (const k of ['fullWaitSec', 'minSec', 'holdSec', 'timeoutSec']) if (s[k] !== undefined) assert.ok(entries.some(([, x]) => x.value === s[k]), `${m}/${s.id}.${k}`);
  assert.deepEqual(buildSequence('GA').filter((s) => s.gate).map((s) => s.id), GA_GATES);
  assert.deepEqual(buildSequence('MAC').filter((s) => s.gate).map((s) => s.id), MAC_GATES);
  assert.match(BANNERS.full, /^Automatic demonstration: scripted sequence, not a closed-loop controller or a clinical recommendation$/);
});

if (REAL) {
  test('autorun config entries are in the parameter inventory with provenance', () => {
    const sim = createSimulation({ patient: derivePatient(EXAMPLE_PATIENTS[0].patient, { library: REAL }), library: REAL });
    const inv = sim.parameterInventory().filter((x) => x.component === 'autorun');
    assert.equal(inv.length, Object.keys(AUTORUN_CONFIG).length);
    for (const x of inv) {
      const c = AUTORUN_CONFIG[x.name];
      assert.equal(x.provenance, c.provenance === CONVENTION ? 'simulator_convention' : c.provenance);
      assert.deepEqual(x.sourceIds, [...c.sourceIds]);
    }
  });

  // ---------------------------------------------------------------- full demonstration, all examples
  for (const tpl of EXAMPLE_PLANS) {
    test(`full demonstration: ${tpl.id} runs the whole sequence with unchanged doses`, () => {
      const c = setup(tpl.id, { mode: 'full' });
      assert.ok(c.runner.start().ok);
      const pauses = driveFull(c);
      assert.equal(c.runner.state, 'completed', c.runner.message);
      assert.deepEqual(c.runner.history.map((h) => h.id), c.runner.seq.map((s) => s.id));
      assert.ok(c.runner.history.every((h) => h.by === 'auto'));
      assertUnchangedPlan(c); assertNoRunnerOverrides(c); assertAutoLogged(c);
      const sum = c.sim.summary();
      assert.equal(sum.automatic.used, true); assert.equal(sum.automatic.mode, 'full'); assert.equal(sum.automatic.outcome, 'completed');
      assert.equal(sum.automatic.pauses.length, pauses);
      assert.ok(c.sim.planStatus().every((s) => s.status === 'done'), JSON.stringify(c.sim.planStatus()));
      if (tpl.mode === 'GA') {
        assert.equal(c.sim.state.airway.secured, false);
        assert.equal(sum.extubation.ready, true, JSON.stringify(sum.extubation));
        assert.ok(sum.extubation.criteria.find((x) => x.id === 'tofr').value >= 0.9);
        assert.ok(c.calls.caseActions.includes('extubate'));
      } else {
        assert.equal(c.sim.state.phase, 'emergence');
        assert.ok(['minimal', 'awake'].includes(c.sim.state.vitals.sedationLevel));
      }
      // the sequence used the same shared case actions as the manual buttons
      const expect = tpl.mode === 'GA' ? ['transfer_to_or', 'set_fio2', 'mask_ventilate', 'start_induction', 'secure_airway', 'start_maintenance', 'start_surgery', 'end_surgery', 'begin_emergence', 'extubate']
        : ['transfer_to_or', 'set_fio2', 'start_sedation', 'start_procedure', 'end_procedure', 'end_sedation'];
      assert.deepEqual(c.calls.caseActions.filter((a) => !CLINICIAN_CASE_ACTIONS.includes(a)), expect);
      // the WHO time out was confirmed by the user (clinician), never by the runner
      assert.ok(c.calls.user.includes('confirm_time_out') && !c.calls.runner.includes('confirm_time_out'));
    });
  }

  test('full demonstration: MH-susceptible TIVA case completes with no volatile and no succinylcholine', () => {
    const c = setup('tiva_mh');
    assert.ok(!c.plan.steps.some((s) => ['sevoflurane', 'desflurane', 'isoflurane', 'succinylcholine'].includes(s.drugId)));
    c.runner.start(); driveFull(c);
    assert.equal(c.runner.state, 'completed', c.runner.message);
    const tot = c.sim.summary().totals;
    for (const d of ['sevoflurane', 'desflurane', 'isoflurane', 'succinylcholine']) assert.ok(!tot[d], d);
    assert.ok(tot.propofol.amount > 0);
  });

  // ---------------------------------------------------------------- guided mode, all examples
  for (const tpl of EXAMPLE_PLANS) {
    test(`guided mode: ${tpl.id} — gates in order, nothing advances without approval, conditions enforced`, () => {
      const c = setup(tpl.id, { mode: 'guided' });
      const { sim, runner } = c;
      runner.start();
      // nothing advances without the first gate (Transfer to OR: enabled 5 min after the premedication ended)
      for (let i = 0; i < 600; i++) { sim.step(1); runner.tick(); }
      assert.equal(sim.state.phase, 'premed'); assert.equal(runner.idx, 0);
      assert.ok(runner.status().current.enabled);
      let sawIntubateDisabled = false, sawExtubateDisabled = false, idleChecked = false;
      const gates = driveGuided(c, 12000, {
        onGate: (st) => {
          const id = st.current.id; const V = sim.state.vitals;
          if (id === 'secure_airway') {
            const ok = sim.planStatus().filter((s) => ['premed', 'induction'].includes(s.phase)).every((s) => s.status !== 'pending') && V.depthIndex <= AUTORUN_CONFIG.INTUBATION_DEPTH_MAX.value && V.tofCount <= AUTORUN_CONFIG.INTUBATION_TOF_COUNT_MAX.value;
            assert.equal(st.current.enabled, ok, `intubate enabled iff conditions met (depth ${V.depthIndex}, TOF ${V.tofCount})`);
            if (!ok) { sawIntubateDisabled = true; assert.equal(runner.approve('secure_airway').ok, false); assert.equal(sim.state.airway.secured, false); }
          }
          if (id === 'extubate') {
            const ready = sim.extubationReadiness().ready;
            assert.equal(st.current.enabled, ready);
            if (!ready) { sawExtubateDisabled = true; assert.equal(runner.approve('extubate').ok, false); assert.equal(sim.state.airway.secured, true); }
          }
          if (id === 'start_maintenance' && !idleChecked) {
            idleChecked = true; const ph = sim.state.phase;
            for (let i = 0; i < 200; i++) { sim.step(1); runner.tick(); if (runner.state === 'paused') c.userResolves(); }
            assert.equal(sim.state.phase, ph, 'maintenance waits for the clinician');
          }
        },
      });
      assert.equal(runner.state, 'completed', runner.message);
      assert.deepEqual(gates, tpl.mode === 'GA' ? GA_GATES : MAC_GATES);
      if (tpl.mode === 'GA') {
        assert.ok(sawIntubateDisabled, 'Intubate was disabled before conditions were met');
        assert.ok(sawExtubateDisabled, 'Extubate was disabled before readiness');
        assert.equal(c.sim.summary().extubation.ready, true);
        const surg = runner.history.find((h) => h.id === 'start_surgery');
        assert.equal(surg.by, 'clinician', 'surgery starts only through the pre-incision verification gate (no time-based start)');
      } else {
        assert.equal(runner.history.find((h) => h.id === 'start_procedure').by, 'clinician');
      }
      for (const h of runner.history) assert.equal(h.by, buildSequence(tpl.mode).find((s) => s.id === h.id).gate ? 'clinician' : 'auto');
      assertUnchangedPlan(c); assertNoRunnerOverrides(c); assertAutoLogged(c);
      assert.equal(c.sim.summary().automatic.mode, 'guided');
    });
  }

  // ---------------------------------------------------------------- failing extubation readiness
  const noReversal = (plan) => { plan.steps = plan.steps.filter((s) => s.drugId !== 'sugammadex'); };

  test('full demonstration: readiness not met (no reversal) — stops, reports unmet criteria, never extubates or overrides', () => {
    const c = setup('ga_elderly_cad_ckd', { editPlan: noReversal });
    c.runner.start(); driveFull(c);
    assert.equal(c.runner.state, 'stopped');
    assert.equal(c.runner.current.id, 'extubate');
    assert.ok(c.runner.unmetAtStop.some((u) => /TOF ratio/.test(u)), c.runner.unmetAtStop.join('; '));
    assert.ok(c.sim.state.vitals.tofRatio < 0.9);
    assert.equal(c.sim.state.airway.secured, true, 'tube still in');
    assert.ok(!c.calls.runner.includes('extubate'));
    assert.ok(!c.calls.caseActions.includes('extubate') && !c.calls.caseActions.includes('extubateOverride'));
    assertNoRunnerOverrides(c);
    const A = c.sim.summary().automatic;
    assert.equal(A.outcome, 'stopped'); assert.equal(A.stoppedAt.step, 'Extubate'); assert.ok(A.unmet.some((u) => /TOF ratio/.test(u)));
    assert.match(c.sim.state.log.filter((e) => e.kind === 'auto').pop().message, /stopped — Extubate: conditions not met.*Unmet: .*TOF ratio/);
  });

  test('guided mode: readiness not met — Extubate stays disabled, unmet criteria shown after the time limit, no override', () => {
    const c = setup('ga_elderly_cad_ckd', { mode: 'guided', editPlan: noReversal });
    const { sim, runner } = c;
    runner.start();
    for (let i = 0; i < 12000 && runner.current && runner.current.id !== 'extubate'; i++) {
      if (runner.state === 'paused') { c.userResolves(); continue; }
      let st = runner.status(); if (st.current.items && !st.current.enabled) { c.clinician(); st = runner.status(); }
      if (st.current.enabled) { runner.approve(st.current.id); continue; }
      sim.step(1); runner.tick();
    }
    assert.equal(runner.current.id, 'extubate');
    for (let i = 0; i < AUTORUN_CONFIG.EXTUBATION_TIMEOUT_SEC.value + 60; i++) { if (runner.state === 'paused') c.userResolves(); sim.step(1); runner.tick(); }
    const st = runner.status();
    assert.equal(runner.state, 'running', 'guided mode keeps waiting');
    assert.equal(st.current.enabled, false); assert.equal(st.current.timedOut, true);
    assert.ok(st.current.unmet.some((u) => /TOF ratio/.test(u)));
    assert.equal(runner.approve('extubate').ok, false);
    assert.equal(sim.state.airway.secured, true);
    assert.ok(sim.state.log.some((e) => e.kind === 'auto' && /Extubate: conditions not met after 30 min — .*TOF ratio/.test(e.message)));
    assertNoRunnerOverrides(c);
  });

  // ---------------------------------------------------------------- pauses
  function toSurgery(c) {
    c.runner.start();
    for (let i = 0; i < 3000 && !c.runner.history.some((h) => h.id === 'start_surgery'); i++) {
      if (c.runner.state === 'paused') { c.userResolves(); continue; }
      c.sim.step(1); c.runner.tick();
    }
    assert.ok(c.runner.history.some((h) => h.id === 'start_surgery'));
  }

  test('hemorrhage → MAP < 60 → pause with a message; the runner never resumes the hold or acknowledges', () => {
    const c = setup('ga_elderly_cad_ckd');
    toSurgery(c);
    c.asUser(() => c.sim.inject('hemorrhage'));
    for (let i = 0; i < 1800 && c.runner.state === 'running'; i++) { c.sim.step(1); c.runner.tick(); }
    assert.equal(c.runner.state, 'paused');
    assert.match(c.runner.message, /Paused — .*(MAP \d+ < 60|Interlock hold latched)/);
    // the clock keeps running in the engine (UI stops it): the runner stays paused and does nothing
    const idx = c.runner.idx; const nLog = c.sim.state.log.filter((e) => e.kind === 'auto').length;
    for (let i = 0; i < 120; i++) { c.sim.step(1); c.runner.tick(); }
    assert.equal(c.runner.state, 'paused'); assert.equal(c.runner.idx, idx);
    assert.equal(c.sim.state.log.filter((e) => e.kind === 'auto').length, nLog);
    assert.equal(c.sim.state.interlocks.holdLatched, true, 'hold latched (MAP < 60)');
    const blk = c.runner.blockers(false).map((b) => b.kind);
    assert.ok(blk.includes('interlock_hold'));
    const r = c.runner.resume();
    assert.equal(r.ok, false); assert.match(r.message, /Still paused — Interlock hold latched/);
    assert.deepEqual(c.calls.runner.filter((id) => id === 'resume_hold' || id.startsWith('acknowledge:')), []);
    assert.ok(c.sim.state.alarms.some((a) => a.priority === 'high' && !a.acknowledged));
    assert.equal(c.sim.summary().automatic.outcome, 'paused');
  });

  test('a HIGH-priority alarm (SpO2 probe off) pauses; medium alarms do not; resume only after the user acknowledges', () => {
    const c = setup('tiva_mh');
    toSurgery(c);
    // a medium alarm does not pause
    c.sim.alarmMgr.pulse(c.sim.state.tSec, 'test_medium', 'medium', 'test medium alarm');
    c.sim.step(1); c.runner.tick();
    assert.equal(c.runner.state, 'running');
    c.asUser(() => c.sim.inject('spo2_probe_off'));
    for (let i = 0; i < 10 && c.runner.state === 'running'; i++) { c.sim.step(1); c.runner.tick(); }
    assert.equal(c.runner.state, 'paused');
    assert.match(c.runner.message, /HIGH alarm needs a decision: SpO2 probe off/);
    assert.equal(c.sim.state.interlocks.holdLatched, false, 'SpO2 is not an interlock input');
    assert.equal(c.runner.resume().ok, false, 'still blocked while unacknowledged');
    assert.ok(c.sim.state.alarms.find((a) => a.id === 'sensor_fault_spo2' && !a.acknowledged));
    c.asUser(() => c.sim.action('acknowledge:sensor_fault_spo2'));
    assert.equal(c.runner.resume().ok, true);
    assertNoRunnerOverrides(c);
  });

  test('user pause/resume; manual control ends automatic mode (logged); start only from the beginning of a case', () => {
    const c = setup('ga_healthy');
    c.runner.start();
    c.runner.advance(30);
    assert.equal(c.runner.pause().ok, true); assert.equal(c.runner.state, 'paused');
    c.sim.step(1); c.runner.tick(); assert.equal(c.runner.state, 'paused');
    assert.equal(c.runner.resume().ok, true);
    c.runner.advance(200);
    const r = c.runner.takeOver('Bolus propofol');
    assert.ok(r.ok); assert.equal(c.runner.state, 'manual');
    const idx = c.runner.idx;
    for (let i = 0; i < 600; i++) { c.sim.step(1); c.runner.tick(); }
    assert.equal(c.runner.idx, idx, 'nothing automatic after takeover');
    assert.ok(c.sim.state.log.some((e) => e.kind === 'auto' && /AUTO: Automatic demonstration ended — manual control used \(Bolus propofol\)/.test(e.message)));
    const A = c.sim.summary().automatic;
    assert.equal(A.outcome, 'manual'); assert.equal(A.manualControl, 'Bolus propofol');
    assert.equal(c.runner.approve('secure_airway').ok, false);
    // mid-case start is refused
    const late = createAutoRunner({ sim: c.sim, mode: 'full' });
    assert.equal(late.start().ok, false);
    // summary of a case without automatic mode
    const plain = setup('ga_healthy');
    assert.deepEqual(plain.sim.summary().automatic, { used: false });
  });
}
Source and scope

Published from Smart-NOA-Controller/simulator/tests/autorun.test.mjs at revision 6e5b205e3843. Full source text is retained; HTML presentation does not establish independent clinical review. In-memory simulation with no patient, monitor or pump interface; not clinically validated.