Download model.mjs from mmrech/ASCEND: direct link, hf CLI and curl.
- Browser
- Download file 9.8 kB
-
https://huggingface.co/spaces/mmrech/ASCEND/resolve/main/model.mjs
- Command line
-
hf download hf://spaces/mmrech/ASCEND/model.mjs
-
curl -L -o model.mjs https://huggingface.co/spaces/mmrech/ASCEND/resolve/main/model.mjs
9.8 kB
| /** | |
| * ASCEND illustrative service-flow model, not a fitted clinical or causal model. | |
| * Units: expected operations and unresolved cases; months; incremental BRL. | |
| * Fractional counts are deliberate expected flows, not individual patient records. | |
| * | |
| * Each month: available = opening unresolved cases + new arrivals; | |
| * completed = min(available, referral throughput, staffed capacity, funded slots). | |
| * Referral throughput = arrivals * min(1, baseline referral + intervention gain). | |
| * Therefore completions cannot exceed arrivals: this model can slow backlog growth | |
| * but cannot clear an existing backlog or estimate waiting times. It has no exits, | |
| * mortality, contraindications, repeat operations, or empirical effect estimates. | |
| * | |
| * An additional slot costs unitCost, bundling staff, theatre service, and funding. | |
| * Theatre/combined purchase capacity and fund it; referral only funds use of | |
| * existing capacity. All intervention budgets are committed expenditure, even | |
| * when other constraints make some purchased slots unused. Programme fixed costs | |
| * are paid in full. Current-service costs are excluded from incremental spending. | |
| */ | |
| export const DEFAULTS = Object.freeze({ | |
| budget: 2400000, | |
| capacity: 90, | |
| referralGain: 0.15, | |
| arrivals: 150, | |
| initialQueue: 240, | |
| baseReferral: 0.6, | |
| unitCost: 12000, | |
| referralFixedCost: 600000, | |
| baselineFundedSlots: 100, | |
| months: 12, | |
| }); | |
| export const STRATEGIES = Object.freeze([ | |
| Object.freeze({ id: 'current', name: 'Current service', color: '#8493a6' }), | |
| Object.freeze({ id: 'theatre', name: 'Expand surgical capacity', color: '#61d8bf' }), | |
| Object.freeze({ id: 'referral', name: 'Strengthen referral', color: '#a99bff' }), | |
| Object.freeze({ id: 'combined', name: 'Combine both', color: '#f4c56a' }), | |
| ]); | |
| const BOUNDS = Object.freeze({ | |
| budget: [0, 6000000], capacity: [40, 180], referralGain: [0, 0.4], | |
| arrivals: [0, 10000], initialQueue: [0, 1000000], baseReferral: [0, 1], | |
| unitCost: [1, 10000000], referralFixedCost: [0, 6000000], | |
| baselineFundedSlots: [0, 10000], months: [1, 120], | |
| }); | |
| const TOLERANCE = 1e-7; | |
| /** Only actual finite numbers are accepted; missing fields use defaults. */ | |
| export function normalizeState(input = {}) { | |
| if (input === null || typeof input !== 'object' || Array.isArray(input)) { | |
| throw new TypeError('Model state must be an object of finite numbers.'); | |
| } | |
| const state = {}; | |
| for (const [key, fallback] of Object.entries(DEFAULTS)) { | |
| const value = Object.hasOwn(input, key) ? input[key] : fallback; | |
| if (typeof value !== 'number' || !Number.isFinite(value)) { | |
| throw new TypeError(`${key} must be an actual finite number.`); | |
| } | |
| const [lower, upper] = BOUNDS[key]; | |
| state[key] = Math.max(lower, Math.min(upper, value)); | |
| } | |
| state.months = Math.floor(state.months); | |
| return state; | |
| } | |
| function simulate(state, { additionalSlots = 0, referralGain = 0, expand = false } = {}) { | |
| const capacity = state.capacity + (expand ? additionalSlots : 0); | |
| const fundedSlots = state.baselineFundedSlots + additionalSlots; | |
| const referralThroughput = state.arrivals * Math.min(1, state.baseReferral + referralGain); | |
| const monthly = []; | |
| let queue = state.initialQueue; | |
| let total = 0; | |
| for (let index = 0; index < state.months; index += 1) { | |
| const openingQueue = queue; | |
| const available = openingQueue + state.arrivals; | |
| const completed = Math.min(available, referralThroughput, capacity, fundedSlots); | |
| queue = Math.max(0, available - completed); | |
| total += completed; | |
| monthly.push({ | |
| month: index + 1, openingQueue, arrivals: state.arrivals, available, | |
| referralThroughput: Math.min(available, referralThroughput), capacity, | |
| fundedSlots, completed, closingQueue: queue, queue, | |
| }); | |
| } | |
| const final = monthly.at(-1); | |
| const limits = [ | |
| ['Demand', final.available], ['Referral flow', final.referralThroughput], | |
| ['Staffed capacity', final.capacity], ['Operating funds', final.fundedSlots], | |
| ]; | |
| const bindingConstraints = limits.filter(([, limit]) => Math.abs(limit - final.completed) <= TOLERANCE).map(([label]) => label); | |
| const bottleneck = bindingConstraints.includes('Demand') ? 'Demand' : bindingConstraints.join(' + '); | |
| return { completed: total, queue, monthly, bottleneck, bindingConstraints }; | |
| } | |
| /** Returns current, theatre, referral, combined; infeasible options are explicit. */ | |
| export function evaluate(input = {}) { | |
| const state = normalizeState(input); | |
| const baseline = simulate(state); | |
| return STRATEGIES.map(strategy => { | |
| const isCurrent = strategy.id === 'current'; | |
| const usesReferral = strategy.id === 'referral' || strategy.id === 'combined'; | |
| const expand = strategy.id === 'theatre' || strategy.id === 'combined'; | |
| const feasible = isCurrent || (state.budget > 0 && (!usesReferral || state.budget >= state.referralFixedCost)); | |
| const reason = feasible ? '' : state.budget <= 0 | |
| ? 'No incremental budget is available.' | |
| : 'Budget is below the fixed referral programme cost.'; | |
| const programmeCost = !isCurrent && feasible && usesReferral ? state.referralFixedCost : 0; | |
| const cost = !isCurrent && feasible ? state.budget : 0; | |
| const slotExpenditure = Math.max(0, cost - programmeCost); | |
| const additionalSlots = slotExpenditure / state.unitCost / state.months; | |
| const result = isCurrent || !feasible ? baseline : simulate(state, { | |
| additionalSlots, | |
| referralGain: usesReferral ? state.referralGain : 0, | |
| expand, | |
| }); | |
| return { | |
| ...strategy, ...result, feasible, reason, | |
| incremental: Math.max(0, result.completed - baseline.completed), | |
| cost, programmeCost, slotExpenditure, additionalSlots, | |
| }; | |
| }); | |
| } | |
| /** | |
| * Maximizes extra completed operations, then minimizes committed cost. | |
| * Exact outcome/cost ties retain input order; tiedIds makes that convention visible. | |
| * This is a scenario ranking, not a clinical recommendation or cost-effectiveness test. | |
| */ | |
| export function recommendation(results) { | |
| if (!Array.isArray(results)) throw new TypeError('Results must be an array.'); | |
| const feasible = results.filter(result => result.feasible !== false); | |
| if (!feasible.length) throw new RangeError('At least one feasible result is required.'); | |
| for (const result of feasible) { | |
| if (!Number.isFinite(result.incremental) || !Number.isFinite(result.cost)) { | |
| throw new TypeError('Feasible results must have finite incremental outcomes and cost.'); | |
| } | |
| } | |
| const bestEffect = Math.max(...feasible.map(result => result.incremental)); | |
| const effectTies = feasible.filter(result => bestEffect - result.incremental <= TOLERANCE); | |
| const lowestCost = Math.min(...effectTies.map(result => result.cost)); | |
| const ties = effectTies.filter(result => result.cost - lowestCost <= TOLERANCE); | |
| return { ...ties[0], tiedIds: ties.map(result => result.id), effectTiedIds: effectTies.map(result => result.id) }; | |
| } | |
| /** Row-major scenario grid. y=0 is no assumed referral improvement. */ | |
| export function landscape(input = {}, cols = 35, rows = 25) { | |
| if (![cols, rows].every(value => Number.isInteger(value) && value >= 2 && value <= 200)) { | |
| throw new RangeError('Landscape dimensions must be integers between 2 and 200.'); | |
| } | |
| const state = normalizeState(input); | |
| // Referral throughput never exceeds arrivals, so available demand cannot bind | |
| // below it, even with an empty opening queue. All remaining limits are fixed: | |
| // monthly completions are constant, and no monthly history is needed here. | |
| // Keep evaluate()'s detailed simulation for monthly inspection and conservation. | |
| const options = STRATEGIES.map(strategy => { | |
| const isCurrent = strategy.id === 'current'; | |
| const usesReferral = strategy.id === 'referral' || strategy.id === 'combined'; | |
| const feasible = isCurrent || (state.budget > 0 && (!usesReferral || state.budget >= state.referralFixedCost)); | |
| const cost = !isCurrent && feasible ? state.budget : 0; | |
| const programmeCost = !isCurrent && feasible && usesReferral ? state.referralFixedCost : 0; | |
| const additionalSlots = (cost - programmeCost) / state.unitCost / state.months; | |
| return { | |
| ...strategy, feasible, cost, additionalSlots, | |
| usesReferral: feasible && usesReferral, | |
| expand: feasible && (strategy.id === 'theatre' || strategy.id === 'combined'), | |
| }; | |
| }); | |
| const baseThroughput = state.arrivals * state.baseReferral; | |
| const cells = []; | |
| for (let row = 0; row < rows; row += 1) { | |
| const y = 0.4 * row / (rows - 1); | |
| const improvedThroughput = state.arrivals * Math.min(1, state.baseReferral + y); | |
| for (let col = 0; col < cols; col += 1) { | |
| const x = 40 + 140 * col / (cols - 1); | |
| const baselineCompleted = Math.min(baseThroughput, x, state.baselineFundedSlots) * state.months; | |
| const results = options.map(option => { | |
| const monthlyCompleted = Math.min( | |
| option.usesReferral ? improvedThroughput : baseThroughput, | |
| x + (option.expand ? option.additionalSlots : 0), | |
| state.baselineFundedSlots + option.additionalSlots, | |
| ); | |
| const completed = monthlyCompleted * state.months; | |
| return { | |
| ...option, completed, | |
| incremental: Math.max(0, completed - baselineCompleted), | |
| queue: Math.max(0, state.initialQueue + state.months * (state.arrivals - monthlyCompleted)), | |
| }; | |
| }); | |
| const winner = recommendation(results); | |
| cells.push({ | |
| index: cells.length, col, row, xIndex: col, yIndex: row, x, y, | |
| winnerId: winner.id, winnerName: winner.name, color: winner.color, | |
| incremental: winner.incremental, completed: winner.completed, | |
| cost: winner.cost, queue: winner.queue, tiedIds: winner.tiedIds, | |
| effectTiedIds: winner.effectTiedIds, | |
| }); | |
| } | |
| } | |
| return cells; | |
| } | |