Cohort summary report¶
cohort_eda.py reads dashboard_data.json and writes one self-contained HTML report describing the cohort, how much data each participant contributed, which vocabularies the records use, and which ALS markers appear. The report loads nothing from the internet, so it opens the same way on a laptop with no network connection. It holds counts rather than records, but it still describes your participants: review it in full before it leaves the approved environment.
Open the live demo Module source (cohort_eda.py)
Running it¶
run_all.py writes the report to reports/cohort_eda_report.html with k = 5 and the data cut-off as the reference date. To run it on its own:
python pipeline/cohort_eda.py --bundle dashboard_data.json \
--out reports/cohort_eda_report.html --k 5 --reference-date 2026-10-31
From Python:
import cohort_eda
cohort_eda.generate_report(
bundle_path='dashboard_data.json',
out_path='reports/cohort_eda_report.html',
k=5, # counts below k are held back
cohort_name='Registry Forge ALS cohort',
reference_date='2026-10-31', # ages are computed at this date
)
| Option | Default | Effect |
|---|---|---|
--bundle |
./dashboard_data.json |
Bundle written by run_pipeline.py |
--out |
./cohort_eda_report.html |
Report file |
--k |
5 | Smallest participant count that is shown |
--cohort-name |
Registry Forge ALS cohort |
Name in the report header |
--reference-date |
latest record date | Date at which ages are computed; use the data cut-off |
Live demo¶
The demo below is the report generated from the invented ALS cohort that ships with Registry Forge (examples/make_synthetic_cohort.py). No real person is in it.
Tabs in the report¶
| Tab | What it shows |
|---|---|
| Cohort | Sex, 10-year age band at the reference date (or at death, when earlier), race and ethnicity. Ages over 89 are grouped as 90+. |
| Data volume | Records and distinct codes by category, participants by banded number of records, and records by source format (FHIR, C-CDA, PDF, RTF, HTML). |
| Observation period | Participants by the time between their first and last dated record, in bands from under 1 year to 20+ years. |
| Activity over time | Records per calendar year for each category. No month or day is shown. |
| Vocabularies | Coded records by code system, using the friendly names described below. |
| ALS markers | Participants grouped by the earliest motor neuron disease code in their problem list, and participants with each marker. |
| Top codes | The 25 most frequent codes in each vocabulary, with record and participant counts and a category label. |
| Participants | One row per participant: pseudonym, banded record count, observation period band, categories with data, source formats and the number of distinct ALS markers. Demographics and codes are not shown per participant. |
The markers counted on the ALS markers tab are:
| Marker | Where it comes from |
|---|---|
| Motor neuron disease group | ICD-10-CM G12.20 to G12.29, ICD-9-CM 335.20 to 335.29, SNOMED CT 86044005 (ALS) and 230258005 (motor neuron disease) in the problem list. The earliest dated code sets the group. |
| ALSFRS-R total | LOINC 82953-1 recorded as a coded observation |
| Forced vital capacity (FVC) | LOINC 19868-9 and 19870-5 (volume) and 19872-1 (percent predicted) recorded as coded observations |
| Frontotemporal dementia code | ICD-10-CM G31.01 or G31.09 |
| ALS medicines | Medication names matching riluzole, edaravone, tofersen or sodium phenylbutyrate-taurursodiol, including brand names |
| Diagnostic report | Any FHIR DiagnosticReport (pathology, radiology or laboratory) |
| Death date | A death date in the Patient resource |
LOINC has no codes for the four ALSFRS-R domain subscores. Scores written in clinical notes are not counted here; note extraction finds them.
Privacy controls¶
- Pseudonyms. Each participant gets a
PT-NNNNidentifier assigned in sorted order. The mapping is not written to disk. - Ages. Dates of birth become 10-year bands at the reference date, with ages over 89 grouped as 90+ in line with the HIPAA Safe Harbor rule, 45 CFR 164.514(b)(2)(i)(C).
- Dates. Observation periods are durations in bands, and activity over time is shown by year.
- Small counts. Every participant count below k is shown as "held back for cell size". Where categories add up to a published total, a second category is also held back when needed so the first cannot be worked out by subtraction.
- Per-participant values. Records per participant and observation years are binned in Python before the HTML is written, so the report holds no per-participant list of values or demographics.
- No free text. Note narratives and comments are never included.
For a small or rare cohort, raise k to 10 or 15. Record counts (as opposed to participant counts) in the Data volume and Top codes tabs are not suppressed.
Vocabulary names¶
EHR exports often use many different strings for the same few code systems, which would otherwise fill the Vocabularies tab with near-duplicate bars. The report maps them to friendly names.
Code systems and the identifiers recognized for each
| Friendly name | Recognized from |
|---|---|
| SNOMED CT | 2.16.840.1.113883.6.96, http://snomed.info/sct |
| RxNorm | 2.16.840.1.113883.6.88 |
| LOINC | 2.16.840.1.113883.6.1 |
| ICD-10-CM and ICD-10-PCS | 2.16.840.1.113883.6.90 and 2.16.840.1.113883.6.4, kept separate because one codes diagnoses and the other inpatient procedures |
| ICD-9-CM and ICD-9-PCS | kept separate for the same reason |
| CPT | 2.16.840.1.113883.6.12 |
| HCPCS Level II | 2.16.840.1.113883.6.14 |
| CVX | 2.16.840.1.113883.12.292 |
| NDC | 2.16.840.1.113883.6.69 |
| CDC Race and Ethnicity | the CDC race and ethnicity code system OIDs |
| HL7 v3 code systems | ActCode, RoleCode, Confidentiality, ObservationInterpretation, AdministrativeGender |
| Third-party terminologies | IMO ProblemIT, Medi-Span, First Databank NDDF (matched by name) |
EHR vendor local codes. When an OID, URL or label matches a known EHR vendor's enterprise root, the record is shown under a per-vendor "local" label. Counting still uses the raw string, so codes from different vendor tables do not merge; only the label is combined. The prefixes and patterns live in OID_FRIENDLY_NAMES, URL_FRIENDLY_NAMES, STRING_ALIASES and SUBSTRING_TO_LABEL near the top of cohort_eda.py, and you can extend them for the vendors in your data.
Fallbacks. An OID that matches no known prefix is shown as Unmapped OID (...); it may be a private registry, a regional code system or a standard OID missing from the list, so it is not assumed to be local. A bare number is shown as HL7 v2 table <N>, which you can look up in the HL7 v2 specification.
Help adapting vocabulary names to your EHR
The mappings reflect the exports we have worked with, and exports vary a great deal between vendors and sites. If you would like help mapping vocabulary strings from your own export, email dboyce@als.net with a de-identified list of the strings.
Sharing the report¶
The report is a single HTML file with its styles and charts built in, so it opens in any browser without a network connection. Before anyone outside the study team sees it:
- Open every tab and read it in full.
- Check the Top codes tab for local codes or labels that name a site or clinic.
- Confirm that k suits the size of the cohort and the rarity of its subgroups.
- Follow the sharing rules in your protocol and data use agreement.