Two deliverables for mimic_mortality_harness.html: (A) the exact ICES Data & Analytic Services (DAS) holdings that reproduce — and exceed — the reporting the harness currently derives from the harmonized NHANES 1988–2018 data, and (B) a bounded-memory modification that ingests 3,000,000+ records by incremental batching instead of loading all rows into memory.
The harness consumes seven NHANES variable domains. Each maps to one or more ICES holdings from the DAS third-party-research catalogue. The linkage spine changes from NHANES SEQN to the ICES IKN (encrypted key number); the harness's SEQN-merge logic maps 1:1 onto IKN-merge.
| Harness domain (NHANES) | Use in harness | ICES dataset(s) — DAS library code | Notes & caveats |
|---|---|---|---|
Demographics — SEQN, RIDAGEYR, RIAGENDR | Cohort spine; age; sex; §05 period | RPDB (Registered Persons Database); denominator/eligibility via CAPE, CONTACT, PCPOP | RPDB is the spine — IKN, age, sex, DOB, fact/date of death to Jul 2026. This is the "demographics + partial mortality" module. |
Race / ethnicity — RIDRETH1 | Descriptive strata | SURNAMES (surname-based ethnicity); area-level ONMARG, CENSUS | No self-reported race in admin data. Surname + neighbourhood proxies only; interpret cautiously. |
Survey cycle — SDDSRVYR | §05 per-period breakdown | fiscal-year field on any claims dataset (DAD/OHIP service date) | Replace survey cycle with service/discharge fiscal year; key the §05 buckets on that. |
Self-report conditions — MCQ*/DIQ010/KIQ022 → oracle routing | Route each person to a mortality oracle | Validated ICES-derived cohorts: ODD (diabetes), CHF, COPD, ASTHMA, HYPER, DEMENTIA, ORAD (RA), HIV, OCCC (Crohn's/colitis); registries OCR (cancer), OMID (MI), ORRS/CORR (renal/ESRD); general Dx via DAD, NACRS, OHIP, SDS | Better than NHANES: whole-population, longitudinal, validated algorithms rather than self-report → less misclassification. Spirometry (GOLD) has no admin source → use the COPD cohort as the pulmonary router. |
Linked mortality — MORTSTAT, PERMTH_EXM | Observed outcome; follow-up survival | ORGD (Vital Statistics – Deaths) + RPDB; overdose deaths DDARD | ORGD fact of death to Mar 2024, cause to Dec 2022 (cause lags ~2–3 y); RPDB fact-of-death is current. Follow-up months = index → death/censor. |
Medications — RXDDRUG/RXDDRGID + drug dictionary | §06 doctor-prescribed vs Pareto | ODB (Ontario Drug Benefit) + NMS (Narcotics Monitoring System) + NDFP (IV cancer drugs); code lookups DIN/DPD | ODB is age-restricted (65+, ODSP/OW/LTC/Trillium) — under-65 non-narcotic coverage is incomplete. NMS captures controlled substances all-payer. DIN/DPD replace dictionary_drug_codes.csv. |
| Response-module labs — creatinine, A1c, glucose, AST/ALT, platelets, HDL, TG, urate, TSH/T4, Hgb, ACR, LDL → staging | §04b/§04c KDIGO · FIB-4 · ADA staging; §08 LDL | OLIS (Ontario Laboratories Information System); legacy SW-Ontario CERNER | OLIS starts Oct 2007; community-lab completeness rises over time. Delivers creatinine→eGFR/KDIGO, A1c/glucose→ADA, AST/ALT/platelets→FIB-4, lipids→LDL. |
Blood pressure BPX*; anthropometry BMXBMI/BMXWAIST | Metabolic-syndrome / HTN staging | HYPER cohort (validated HTN); measured BP/BMI via survey OHS, CLSA or primary-care EMR CHC | Not in administrative data. Use the validated HTN cohort, or linked survey/EMR for measured vitals. |
PHQ-9 DPQ* → §08 depression | Non-mortality endpoint | OMHRS (inpatient psychiatry) + OHIP/DAD psych Dx + ODB antidepressants; PROMs ESAS, PROMS; population mood OHS/CLSA | No population PHQ-9; substitute a validated depression algorithm or a linked-survey score. |
| Behavioural — smoking, alcohol, diet (NHANES questionnaire) | Risk-factor context | Surveys: OHS (Ontario Health Study), CLSA, HCES; (StatCan CCHS is linkable) | Self-reported behaviours live in the linkable survey holdings, not admin data. |
Core: RPDB · ORGD · DAD · NACRS · OHIP · OLIS · ODB · NMS · OCR
Routing cohorts: ODD · CHF · COPD · ASTHMA · HYPER · DEMENTIA · ORAD · HIV · OCCC · ORRS
Optional (to exceed NHANES): OHS/CLSA (measured BMI/BP + behaviours) · ESAS/PROMS (PROMs) · ONMARG/CENSUS (SES) · CIC (immigration) · NDFP (cancer drugs) · DDARD (overdose deaths) · DIN/DPD (drug lookups)
The stock harness already streams each file, but it then (1) pushes every kept row into an in-memory rows[], (2) merges all persons on SEQN into five maps DATA.nhanes.{demo,cond,mort,lab,rx}, and (3) builds a per-person RESULTS[] in runAll(). Peak memory is O(N persons) — fine at NHANES scale (~10⁵), fatal at 3×10⁶.
harness_batch.js)Process records in fixed-size batches; fold each person directly into O(#oracles × #stages) running aggregates; then discard the raw rows before the next batch. Peak memory = O(BATCH_SIZE + aggregates), independent of total record count. The fold reuses the harness's own routeNHANES, computeStage and survivalLE/lifeYearsGained on a self-contained person record, so the numbers are identical to the stock path.
Verified by smoke_batch.mjs: 3M synthetic joined rows streamed in 60 batches; peak retained rows never exceeded the batch size; every aggregate (per-oracle counts, life-years, observed deaths, KDIGO/ADA stage breakdown, PHQ-9/LDL non-mortality endpoints) matched an independent single-pass reference exactly; all cells <6 suppressed. A naive all-in-memory pass on 3M rows would hold multiple GB (3M row-objects + five SEQN maps + RESULTS[]) and crash a browser tab.
HarnessBatch.streamFold(source, {
batchSize, // rows held at once (default 50 000)
keepFn, // column whitelist (reuse the harness nhanesKeep)
onBatch(rows), // fold callback; rows are DISCARDED after it returns
onProgress(bytes,n) // UI progress
}) -> {rows, bytes, batches, peakBatchRows} // peakBatchRows <= batchSize (guaranteed)
HarnessBatch.makeAggregator({route, stage, maxRR, lifeYears})
.foldPerson(personRecord) // O(1); no per-person retention
.aggregateRows() // §04 rows, <6 suppressed
.stageRows() // §04b/§04c stage breakdown, <6 suppressed
.nonMort() // §08 depression + LDL
// source = browser File (uses File.stream()) OR a Node async-iterable of chunks.
mimic_mortality_harness.html (four edits)<script src="harness_batch.js"></script> (flat root — respects the bundle's no-subdirectory rule).BATCH_SIZE input + a progress bar to §01. Auto-enable when a single file's .size exceeds a threshold (e.g. 200 MB).streamExtractColumns → ingestNHANES → runAll path with:
const agg = HarnessBatch.makeAggregator({route:routeNHANES, stage:stageInline, maxRR:oracleMaxRR, lifeYears:lifeYearsGained}); await HarnessBatch.streamFold(file, {batchSize, keepFn:nhanesKeep, onBatch:rows=>rows.forEach(r=>agg.foldPerson(toPerson(r)))});renderAgg, §04b/§04c, §08) at agg.aggregateRows() / agg.stageRows() / agg.nonMort() instead of iterating DATA.nhanes.demo. §03 per-person rows are already withheld under enclave rules, so nothing there changes.A single-pass fold needs each row to be a complete person. Two supported modes:
O(batch) memory. In an ICES extract this join is trivial to produce inside the enclave (SAS/R) before export.Map<IKN, packedRecord> that holds only the ~15 kept fields as a single delimited string (not a JS object per column); pass 2 iterates the map, folds, discards. Peak memory = O(#persons × ~15 small fields) — for 3M that is a few hundred MB (feasible), versus multiple GB for the stock full-object merge. Document the trade-off and prefer joined mode past ~1–2M.SEQN→IKN in nhanesKeep, sid(), and the merge keys (one-line changes).routeICES(ikn) reads pre-computed cohort flags (ODD/CHF/COPD/…) carried as columns on the joined extract — so routing stays an O(1) per-row lookup, exactly like routeNHANES.LBXSCR, LBXGH, …) in the extract, so computeStage is unchanged.harness_batch.js (module) · smoke_batch.mjs (3M-row test). ICES dataset names/date ranges from the ICES Data Dictionary (DAS – Third Party Research), retrieved for this note. Effect on release rules: none — small-cell suppression preserved.