ASCEND2 / tests /model.test.mjs
mmrech's picture
Launch ASCEND with linked interactive 3D globe
ebaf8c2 verified
Raw History Blame Contribute Delete
9.93 kB
import test from 'node:test';
import assert from 'node:assert/strict';
import { DEFAULTS, STRATEGIES, evaluate, landscape, recommendation, normalizeState } from '../model.mjs';
const near = (actual, expected, tolerance = 1e-7) => assert.ok(Math.abs(actual - expected) <= tolerance, `${actual} != ${expected}`);
const find = (results, id) => results.find(result => result.id === id);
test('defaults define four distinct strategies and a twelve-month illustrative baseline', () => {
assert.equal(DEFAULTS.months, 12);
assert.deepEqual(STRATEGIES.map(option => option.id), ['current', 'theatre', 'referral', 'combined']);
const results = evaluate();
assert.equal(results.length, 4);
near(find(results, 'current').completed, 1080);
near(find(results, 'current').queue, 960);
near(find(results, 'combined').incremental, 150);
assert.equal(recommendation(results).id, 'combined');
});
test('every month conserves people and respects each throughput constraint', () => {
const params = normalizeState({ budget: 2912345, capacity: 71.3, referralGain: 0.283, initialQueue: 53.7 });
for (const result of evaluate(params)) {
let queue = params.initialQueue;
let total = 0;
for (const month of result.monthly) {
near(month.openingQueue, queue);
near(month.openingQueue + month.arrivals, month.completed + month.closingQueue);
assert.ok(month.completed >= 0 && month.closingQueue >= 0);
for (const bound of [month.available, month.referralThroughput, month.capacity, month.fundedSlots]) {
assert.ok(month.completed <= bound + 1e-8);
}
queue = month.closingQueue;
total += month.completed;
}
near(total, result.completed);
near(queue, result.queue);
near(params.initialQueue + params.arrivals * params.months, result.completed + result.queue);
assert.ok(result.cost >= 0 && result.cost <= params.budget);
}
});
test('zero budget makes investment strategies infeasible and selects current service', () => {
const results = evaluate({ budget: 0 });
for (const result of results) {
near(result.cost, 0);
near(result.incremental, 0);
assert.equal(result.feasible, result.id === 'current');
}
assert.equal(recommendation(results).id, 'current');
});
test('referral programme is infeasible below fixed cost and spends its fixed cost when enabled', () => {
const below = evaluate({ budget: 599999 });
for (const id of ['referral', 'combined']) {
assert.equal(find(below, id).feasible, false);
near(find(below, id).cost, 0);
assert.match(find(below, id).reason, /fixed/i);
}
const exact = evaluate({ budget: 600000, capacity: 140 });
for (const id of ['referral', 'combined']) {
assert.equal(find(exact, id).feasible, true);
near(find(exact, id).cost, 600000);
near(find(exact, id).programmeCost, 600000);
near(find(exact, id).additionalSlots, 0);
}
});
test('investment costs remain committed even when another bottleneck prevents benefit', () => {
const results = evaluate({ capacity: 140, referralGain: 0, budget: 2400000 });
for (const id of ['theatre', 'referral', 'combined']) {
near(find(results, id).incremental, 0);
near(find(results, id).cost, 2400000);
}
assert.equal(recommendation(results).id, 'current');
});
test('baseline does not change with intervention budget or referral gain', () => {
const first = find(evaluate({ budget: 0, referralGain: 0 }), 'current');
const last = find(evaluate({ budget: 6000000, referralGain: 0.4 }), 'current');
assert.deepEqual(first, last);
});
test('theatre wins under a physical constraint and referral wins with existing spare capacity', () => {
assert.equal(recommendation(evaluate({ capacity: 40, budget: 2400000 })).id, 'theatre');
const winner = recommendation(evaluate({ capacity: 150, budget: 2400000 }));
assert.equal(winner.id, 'referral');
assert.deepEqual(winner.tiedIds, ['referral', 'combined']);
assert.equal(recommendation(evaluate({ capacity: 90, budget: 2400000 })).id, 'combined');
});
test('referral strategy cannot expand physical capacity and theatre cannot change referrals', () => {
const results = evaluate({ capacity: 75, budget: 6000000, referralGain: 0.4 });
const referral = find(results, 'referral');
const theatre = find(results, 'theatre');
assert.ok(referral.monthly.every(month => month.capacity === 75));
assert.ok(theatre.monthly.every(month => month.referralThroughput === 90));
near(theatre.additionalSlots, 6000000 / 12000 / 12);
near(referral.additionalSlots, (6000000 - 600000) / 12000 / 12);
});
test('more referral effect and more budget never reduce completions for a feasible strategy', () => {
for (const id of ['theatre', 'referral', 'combined']) {
let previous = -Infinity;
for (const budget of [600000, 1200000, 2400000, 6000000]) {
const result = find(evaluate({ budget, capacity: 80 }), id);
assert.ok(result.completed >= previous - 1e-7);
previous = result.completed;
}
previous = -Infinity;
for (const referralGain of [0, 0.1, 0.2, 0.4]) {
const result = find(evaluate({ referralGain, capacity: 120 }), id);
assert.ok(result.completed >= previous - 1e-7);
previous = result.completed;
}
}
});
test('zero demand does not create patients or outcomes', () => {
for (const result of evaluate({ arrivals: 0, initialQueue: 0 })) {
near(result.completed, 0);
near(result.queue, 0);
assert.equal(result.bottleneck, 'Demand');
}
});
test('tie tolerance uses lower cost then stable strategy order without implying strict superiority', () => {
const results = STRATEGIES.map((option, index) => ({ ...option, feasible: true, incremental: 10 + (index === 3 ? 1e-9 : 0), cost: index === 0 ? 0 : 100 }));
const chosen = recommendation(results);
assert.equal(chosen.id, 'current');
assert.deepEqual(chosen.tiedIds, ['current']);
assert.deepEqual(chosen.effectTiedIds, ['current', 'theatre', 'referral', 'combined']);
assert.throws(() => recommendation([]), /feasible/i);
});
test('normalization clamps finite numbers, preserves zero, and rejects invalid numeric inputs', () => {
assert.deepEqual(normalizeState(), { ...DEFAULTS });
const result = normalizeState({ budget: -20, capacity: 300, referralGain: 9, baseReferral: -1, months: 2.8, initialQueue: 0 });
assert.equal(result.budget, 0);
assert.equal(result.capacity, 180);
assert.equal(result.referralGain, 0.4);
assert.equal(result.baseReferral, 0);
assert.equal(result.months, 2);
assert.equal(result.initialQueue, 0);
for (const value of [NaN, Infinity, -Infinity, '90', null, undefined]) {
assert.throws(() => normalizeState({ capacity: value }), /capacity/);
}
});
test('landscape covers requested bounds and reproduces direct decisions at every cell', () => {
const cells = landscape({ budget: 2400000 }, 4, 3);
assert.equal(cells.length, 12);
assert.deepEqual([cells[0].x, cells[0].y], [40, 0]);
assert.deepEqual([cells.at(-1).x, cells.at(-1).y], [180, 0.4]);
for (const [index, cell] of cells.entries()) {
assert.equal(cell.index, index);
const winner = recommendation(evaluate({ budget: 2400000, capacity: cell.x, referralGain: cell.y }));
assert.equal(cell.winnerId, winner.id);
near(cell.incremental, winner.incremental);
}
assert.throws(() => landscape({}, 0, 2), /dimension/i);
assert.throws(() => landscape({}, 2, NaN), /dimension/i);
});
test('default landscape reveals all three active strategy regions without artificial coloring', () => {
const winners = new Set(landscape().map(cell => cell.winnerId));
for (const strategy of ['theatre', 'referral', 'combined']) assert.ok(winners.has(strategy));
});
test('all supported extremes remain finite, nonnegative, and budget-feasible', () => {
for (const budget of [0, 599999, 600000, 6000000]) {
for (const capacity of [40, 180]) {
for (const referralGain of [0, 0.4]) {
for (const result of evaluate({ budget, capacity, referralGain })) {
for (const field of ['completed', 'incremental', 'cost', 'queue']) {
assert.ok(Number.isFinite(result[field]), `${result.id}.${field} must be finite`);
assert.ok(result[field] >= 0, `${result.id}.${field} must not be negative`);
}
assert.ok(result.cost <= budget);
}
}
}
}
});
test('landscape preserves direct simulation totals and ties across extreme and fractional states', () => {
const scenarios = [
{ budget: 0 }, { budget: 599999 }, { budget: 600000 }, { budget: 6000000 },
{ arrivals: 0, initialQueue: 0 }, { arrivals: 0, initialQueue: 900 },
{ arrivals: 10000, initialQueue: 1000000, unitCost: 1, months: 120 },
{ baseReferral: 0, baselineFundedSlots: 0, months: 1 },
{ baseReferral: 1, initialQueue: 0, baselineFundedSlots: 10000, months: 120 },
{ budget: 1234567.89, arrivals: 157.73, unitCost: 15678.32, initialQueue: 25.87, months: 79 },
{ referralFixedCost: 0, budget: 1 },
{ referralFixedCost: 6000000, budget: 5999999 },
];
for (const state of scenarios) {
for (const cell of landscape(state, 7, 5)) {
const winner = recommendation(evaluate({ ...state, capacity: cell.x, referralGain: cell.y }));
assert.equal(cell.winnerId, winner.id);
assert.deepEqual(cell.tiedIds, winner.tiedIds);
assert.deepEqual(cell.effectTiedIds, winner.effectTiedIds);
for (const field of ['incremental', 'completed', 'cost', 'queue']) near(cell[field], winner[field]);
}
}
});
test('large 120-month landscape remains interactive without constructing detailed monthly histories', () => {
const started = performance.now();
const cells = landscape({ months: 120 }, 100, 100);
const elapsed = performance.now() - started;
assert.equal(cells.length, 10000);
assert.ok(elapsed < 750, `10000-cell landscape took ${elapsed.toFixed(1)}ms; target <750ms`);
});