thirlwall-statistical-model

Interactive explorer: development plan

This is the working plan for interactive/. It records what the explorer is for, what each tab contains, which assumptions the reader can change, how each number is produced, and what has been decided. Update it whenever a stage lands or a decision changes; the change log is at the end.

The paper (“The Arithmetic of Suspicion”) and the Python model (../thirlwall_statistical_model.py, version 7, output in ../example_output.txt) remain the source of truth. The explorer recreates their numbers and lets the reader see how those numbers move when assumptions change. It never replaces them.

Status

Stage Scope Owner Status
1 Figure 2 explorer: threshold, base rate, unit type Codex, reviewed by Claude Done (commits be0d1be, 8fb9b70, 7c11a99)
2a Python reference export: full-precision JSON of every per-unit-type result Claude Done: reference/reference.json, printed output verified identical
2b Five tabs following Tables 1–5, shared state, live tables, all “Ready” controls Codex, reviewed Done: reference arithmetic, five linked views, file-open bundle, 1400/390 px review screenshots and regression checks
3a Simulation layer: JavaScript port of the model, validated against the Python baseline; no new reader controls Codex, reviewed Done: 288/288 cells within gate, all medians and Tables 4–5 pass; file-open worker smoke passed
3b Simulation mode in the page: the five shortlisted controls Codex, reviewed Done: selective worker results, source labels, intervals, paper round trips, file-open browser checks and six review screenshots
3c Rule E recalibration when the world changes Codex Done — Stage 3 complete: six paper thresholds and independent crossing rates pass; changed-mortality rule E and file-open browser checks pass
4 Optional extensions (see section 9) — Not planned

1. Purpose and principles

The paper’s tables are snapshots. Each rests on assumptions. The explorer turns each table into something the reader can take apart: change an assumption, watch the table rebuild, and follow the change through to the human cost.

  1. One idea per tab, in the paper’s order. Each tab introduces at most two new controls.
  2. Choices carry forward. Every tab reads one shared state. A change in Tab 1 updates Tabs 2–5.
  3. Every tab shows its table. Alongside the graph and controls, each tab shows a live version of the paper’s table, recomputed under the current settings.
  4. The paper’s values stay visible. Wherever a live value differs from the paper, the paper’s value is shown beside it.
  5. Show the working. Each tab has a “How this number is made” panel with the formula and current values substituted.
  6. Honesty over polish. A number the reference run did not compute is shown as “not computed”, never estimated. Monte Carlo estimates (Stage 3) carry their uncertainty.
  7. The paper’s terminology throughout (section 7).

2. Architecture

The scenario bar sits above the tabs and lists every current choice, for example “Rule C · own-exposure · +4 · base rate 1 in 10,000 · staff-proportional · q = 1 · 18 months”. Each item links to the tab where it is set.

3. Tabs

Key for “Needs”:

Shared world (set from any tab; shown in the scenario bar)

Assumption Paper value Control Needs
Number of units by type 45 / 85 / 45 3 sliders, 0–150 Ready (system totals scale; per-unit rates unchanged)
Expected deaths a year per unit 20 / 4 / 1 3 sliders Sim + calib
Nurses on each roster 100 / 40 / 20 3 sliders Sim (risk weights in Tab 4 are Ready)
Year-to-year variation in death rate 25% Slider, 0–50% Sim + calib
Baseline for rules A–D Previous 3 years’ average, floor 0.5 — Fixed
History; years monitored 48 months; 10 years — Fixed

Tab 1 · Noise (Table 1)

Question: How many alarms does chance alone produce? Takeaway: Chance alone produces between 3 and 34 alarms a year in England and Wales, depending on the rule.

Assumption Paper value Control Needs
Monitoring rule A, B, C, D, E10, E50 Six-way choice Ready
Alarm line, rules A–C 2 SD (97.7%); B uses 3 SD Slider, 1.5–3.5 SD Sim
How often checked Yearly (A, B), monthly (C, D) Switch Sim
Rule D trigger Deaths doubled, at least 4 Sliders: factor 1.5–3, minimum 2–8 Sim
Rule E false-alarm target 1 in 10 / 1 in 50 unit-years Slider over the 11 swept settings inside Tab 4’s Figure 2 section Ready at the 11 points; otherwise Sim + calib
Rule E tuned to detect A doubling — Fixed

Graph: 175 dots grouped 45 / 85 / 45, with the expected number of alarming units in a typical year lit by type. Deterministic, labelled “expected count, a typical year”. Live table (Table 1): rows A–E50, with columns for what triggers the rule, alarm unit-years a year, episodes a year, and the share of unit-years alarmed in intensive care, local and special care units. The selected rule’s row is highlighted. Totals respond to unit counts. Working: alarm unit-years = Σ units_t × share alarmed_t.

Tab 2 · Named nurse (Table 2)

Question: How often does a rota search after a chance alarm produce a “significant” nurse? Takeaway: About half of chance alarms produce a “significant” nurse under the own-exposure test, and about one in forty under the maximum-adjusted test.

Assumption Paper value Control Needs
Rota test Average-exposure, own-exposure, maximum-adjusted Three-way choice Ready
Significance level 5% Slider, 1–10% Sim
Shift shares; staff mix 13 / 21 / 27% of shifts; 35 / 50 / 15% of staff Sliders Sim
Mechanism: roster size × deaths reviewed — Two sliders Ready (exact calculation; labelled “the mechanism”, not Table 2’s figure)
Attendance independent across nurses and deaths Yes — Fixed
Minimum deaths to review 2 — Fixed

Graph: alarms a year, then “significant” nurses a year, as two linked bars. The test switch visibly shrinks the second bar. The mechanism panel plots the chance that someone on the roster looks significant against roster size. Live table (Table 2): for the selected rule, rows for intensive care, local, special care and all alarms weighted. Columns: deaths reviewed (median), top nurse attended (median), and the three tests’ shares. A footer row gives nurses flagged a year under the selected test. (The paper’s Table 2 shows rule C; the explorer generalises it to the selected rule and says so.) Working: flagged a year = Σ units_t × reviewed alarms_t × share passing_t.

Mechanism check values (independent attendance, the model’s shift mix, 5%, every nurse tested): roster 20 and 3 deaths gives 38%; 40 and 8 gives 49%; 100 and 28 gives 89%; 200 and 28 gives 99%.

Tab 3 · Offender present (Table 3)

Question: When an offender is present, how often does the unit alarm, and how often does the rota point to them? Takeaway: No rule does both: rules that alarm often point to the wrong nurse, and rules that point correctly rarely alarm.

Assumption Paper value Control Needs
Offender’s extra deaths +4 or +7 expected Switch Ready; continuous 1–15 is Sim
Unit type highlighted Local Switch Ready
Offender’s shift share 21% Slider Sim
Length of offending 12 months Slider, 3–24 Sim
Present at every extra death; fair tie-break Yes — Fixed

Graph: detection by unit type for the selected rule, each bar marked with the background (no-offender) rate and with identification. Live table (Table 3): rows A–E50; columns for detection in intensive care, local and special care units at the selected effect, the local-unit identification rate, and the background rate. Working: detection includes alarms that would have happened anyway; the background column shows how much.

Tab 4 · The ratio (Table 4)

Question: For every offender-year detected, how many unit-years are falsely flagged? Takeaway: The base rate dominates: changing the rule moves the ratio about fourfold, while the rarity of offenders moves it by hundreds.

Assumption Paper value Control Needs
Base rate 0.1 and 1 (reference); 3, 10, 30 (stress tests) Log slider 0.1–30, presets Ready
Risk allocation In proportion to staff; equal as a check Switch Ready
Tune the idealised chart (Figure 2) 11 settings The Stage 1 explorer, collapsed by default Ready
Detection counts any alarm in an offender-year Yes — Fixed (background shown in Tab 3)

Graph: the ratio for each rule at the current base rate, as a bar chart on a log scale with the selected rule highlighted. Years between detections is shown as the secondary number. Live table (Table 4): rows A–E50; columns for the five printed base rates plus a “your rate” column. It responds to allocation and unit counts. Working: the formula in section 5, with the selected rule’s values substituted. Rule: never plot rules A–D on the Figure 2 axes. Figure 2 counts chart crossings, while Table 4 counts falsely flagged unit-years.

Tab 5 · Human cost (Table 5 and closing paragraph)

Question: What does this cost in people, and how likely is a flagged nurse to be the offender? Takeaway: q sets the volume of harm, not the odds: searching fewer alarms flags fewer innocent nurses and finds proportionally fewer offenders.

Assumption Paper value Control Needs
Share of alarms searched (q) 1, 0.5, 0.25, 0.1 Slider 0–1, presets Ready
Investigation length 18 months Slider, 1–36 Ready
When a nurse is flagged Own-exposure test passes Follows Tab 2 Ready
Chance a flagged nurse is the offender Own-exposure test Display Ready with the D1 export (exact, any base rate, +4 or +7); “not computed” for the maximum-adjusted test

Graph: nurses flagged a year and nurses off the wards at any one time, against q. Live table (Table 5): rows A–E50; columns for q = 1, 0.5, 0.25, 0.1 plus a “your q” column, and off-ward and posterior columns for the current settings. Working: flagged = Tab 2 flags × q; off wards = flagged × months ÷ 12; posterior as in section 5.

4. Reference data

Stage 2a added an optional --export PATH to the Python script. reference/reference.json was produced by the full default run (python thirlwall_statistical_model.py --export interactive/reference/reference.json). It contains, at full precision:

The export uses no random numbers and writes values already computed. A full run with --export produced output identical to example_output.txt apart from the timing line.

JSON keys:

Verification already done: the section 5 formulas applied to this file reproduce every line of blocks 4, 5 and 6 of example_output.txt character for character (30 of 30 lines).

5. Derived arithmetic (derive.js)

With unit counts n_t, staff s_t, base rate p (per unit-year, the slider value ÷ 10,000), and reference values from section 4:

risk weight w_t      = s_t × Σn ÷ Σ(n × s)            (or 1 if equal allocation)
false flagged/yr     = Σ n_t (1 − p w_t) alpha_t
detected/yr          = Σ n_t p w_t d_t                (d_t at +4 or +7)
ratio                = false flagged/yr ÷ detected/yr
years per detection  = 1 ÷ detected/yr
alarm unit-years/yr  = Σ n_t alpha_t
flags/yr (own-exp)   = Σ n_t flag_ep_t
flags/yr (max-adj)   = Σ n_t rev_t s_adj_t
flags/yr at q        = flags/yr × q
off wards            = flags/yr at q × months ÷ 12
posterior            = Σ n_t p w_t d_t flag_off_t
                       ÷ [Σ n_t p w_t d_t flag_any_t + Σ n_t (1 − p w_t) flag_ep_t]

These match false_true() and posterior() in the Python script. Note that q cancels out of the posterior, which is the point of Tab 5.

6. Validation and tests

7. Terminology

Use the paper’s terms:

“Detection” always means the unit alarms in an offender-year. It never means the offender is identified.

8. Stage 3: the simulation layer

8.1 Two modes

8.2 The port must be faithful

sim/ reimplements thirlwall_statistical_model.py sections 1–3 exactly as written, not approximately. The behaviour to reproduce (Python names in brackets):

8.3 Validation against the Python baseline (the gate for 3b)

With the paper’s inputs, the JavaScript simulation must agree with reference.json:

Exact equality with Python is impossible (different random generators) and is not a goal. The Python baseline is the guarantee; the simulation shows direction and size.

8.4 Engineering

8.5 Stage 3b controls (from D2) and what they change

Control Range Simulation inputs changed Tables affected
Expected deaths a year, by unit type 0.5–60 mean per type 1–5
Nurses on each roster, by type 10–200 staff per type; risk weights 2–5
Alarm line, rules A–C 1.5–3.5 SD, converted to the quantile q = Φ(SD), except that 2 SD and 3 SD use the paper’s exact 0.977 and 0.9987 (Φ gives 0.97725 and 0.99865) the quantile q 1–5 for A–C
Significance level 1–10% the rota tests’ threshold 2, 3 (identification), 5
Offender’s extra deaths 1–15 expected extra 3–5

Changing deaths a year triggers rule E recalibration for that unit type in the worker. The other rules update first; the rule E rows are dimmed until their new thresholds and simulated cells arrive.

Simulation mode can also compute what paper mode cannot: the chance that a flagged nurse is the offender under the maximum-adjusted test. Show it there, labelled simulated.

9. Open decisions

# Question Options Recommendation Decided
D1 Add the reference export to the Python script? Yes / No Yes Yes, 2026-10-06; done
D2 Stage 3 scope All 12 Sim controls / a shortlist Shortlist first Shortlist, 2026-10-06: deaths a year, roster size, alarm line, significance level, offender’s extra deaths
D3 Rule E when the world changes Recalibrate in the browser (a few seconds) / grey out Recalibrate Recalibrate, 2026-10-06, with a progress indicator; grey out the 1-in-5,000 and 1-in-10,000 settings
D4 Show paper values beside live values Yes / No Yes Yes, 2026-10-06: small and muted, only where they differ
D5 Where the explorer is hosted, and the paper link Repo only / a public page Decide before the repo goes public. —

10. Change log