STEP 7 · SANITY CHECK

Generalization Sanity Check

Confirming, in writing, whether the same graph schema and the same toggle / lens / filter / output components handled T1D’s acute-spike pattern and T2D’s slow-branching pattern without special-casing either disease — and naming it plainly where they didn’t.

Verdict

Mostly confirmed, with two flagged exceptions. Every component from Step 2 (ingestion) through Step 6 (output view) uses one code path for both diseases, dispatching purely on generic node attributes (node_type, term, target, status) rather than on condition_type. The two places condition_type is read for anything beyond counting, filtering, or color-coding are Step 4/5’s pump-evidence proxy and Step 5’s per-disease intervention-timing filter — both are documented below as open issues rather than silently accepted.

7.1

Method

This check does not re-run the demo. It audits the actual source of step2_graph_builder.html through step6_clinical_output_view.html for every place condition_type, "T1D", or "T2D" appears, and classifies each occurrence as:

7.2

Component-by-component audit

ComponentDisease dispatchStatus
Step 2 — FHIR-to-graph ingestion Single ingestion function walks every patient’s FHIR-flavored bundle into the same five node types regardless of condition_type; the file’s own header note reads “same code path for T1D and T2D — no special-casing.” Generalized
Step 3 — Atemporal / temporal dashboard Mode A and Mode B rendering functions take a patient list and read only node_type, severity, and timestamp; condition_type is used solely by the disease filter dropdown and a comment confirms “same function for T1D and T2D.” Generalized
Step 4 — Pivot lens selector Disease-centered and intervention-centered lenses match on DiagnosisNode.term / InterventionNode.target, not on condition_type at all. The combo lens is generalized except for one sub-branch — see Issue 1 below. Mostly generalized
Step 5 — Comparator filter Outcome / facility / resource axes are fully generic. The intervention-timing axis explicitly branches on condition_type to decide which field to read — see Issue 2 below. Special-cased (by design)
Step 6 — Clinically-readable output view Evidence panel loops over ["T1D","T2D"] to build a side-by-side pair of cards, but the card-building function itself is one function called twice with different data, not two implementations. Variation and confounders panels don’t branch on disease at all. Generalized
7.3

Open issues

Issue 1 — pump-vs-injection is inferred from text, and only makes sense for T1D

patientHasPumpEvidence() (Step 4, reused in Step 5) scans OutcomeNode.reason for the substring “pump.” This function is called unconditionally, but it is only ever meaningful for T1D patients — the corpus has no pump/injection concept for T2D, so calling it on a T2D patient returns false by construction rather than a real negative. The combo lens in Step 4 works around this by only applying the pump/no-pump split when the selected intervention anchor matches /insulin/i, which is itself a T1D-flavored assumption baked into a general-purpose branch.

Why it exists: the underlying synthetic corpus (Step 1) never gave T1D delivery modality a first-class field, and never gave T2D one at all — there was no clean attribute to generalize over.

Disposition: flagged in-UI in both Step 4 and Step 5 (an amber assumption note), not hidden. A real implementation would need delivery-modality as a normalized field on the Intervention node for every disease it's expected to apply to, rather than inferring it from free text.

Issue 2 — intervention-timing filter reads a different field per disease

Step 5’s intervention-timing axis is explicitly disease-appropriate by spec (“early vs. delayed insulin initiation for T2D; pump vs. injection for T1D”), so rowFor() and filteredPatients() both contain a direct p.condition_type === "T2D" / "T1D" branch that reads time_to_insulin_initiation_years for one and calls patientHasPumpEvidence() for the other. This is a real branch in the filtering logic, not just a label difference.

Why it exists: the two diseases have genuinely different clinically meaningful timing questions (T2D: when insulin was added to an existing regimen; T1D: which delivery device is in use) — there is no single generic field that captures both, so the Section 6 spec asked for this axis to differ by design.

Disposition: treated as an accepted, intentional exception rather than a gap. It's isolated to one filter axis; every other axis in Step 5 (outcome, facility tier, resource status, population) is fully generic and identical for both diseases.

7.4

Conclusion

The generalization claim from Section 7b holds at the level it was meant to be tested: the graph schema, the ingestion pipeline, the atemporal/temporal toggle, the disease- and intervention-centered pivot lenses, and the evidence/variation/confounders output view all use one code path driven by generic node attributes, and would behave identically if a third disease were dropped into the same schema tomorrow. The two exceptions are both in the comparator layer, both are about picking which clinically relevant field to read rather than about rendering T1D and T2D differently once a field is chosen, and both are surfaced in-UI as flagged assumptions rather than papered over. That is the useful signal Section 7b's closing paragraph asked this step to produce.