feat(server): reconstruct the narrative#1369
Merged
Merged
Conversation
…for narrative reconstruciton
…t was also added to conftest
Contributor
🔒 Security Scan Results
|
| Severity | Total |
|---|---|
| 🟠 High | 12 |
| 🟡 Medium | 3 |
📦 refiner-app
✅ No vulnerabilities found
📦 refiner-lambda
| Severity | Count |
|---|---|
| 🟠 High | 4 |
📦 refiner-ops
| Severity | Count |
|---|---|
| 🟠 High | 8 |
| 🟡 Medium | 3 |
View detailed results: Security tab
Last updated: 2026-06-22 20:52:57 UTC
rogeruiz
approved these changes
Jun 22, 2026
rogeruiz
left a comment
Collaborator
There was a problem hiding this comment.
lgtm 🌈
Left a few comments on this. Great job on this @robertmitchellv!
Comment on lines
+1717
to
+1772
| <text> | ||
| <!-- Narrative reconstructed by the eCR Refiner from surviving clinical entries: machine-derived, not clinician-attested. --> | ||
| <table border="1"> | ||
| <thead> | ||
| <tr> | ||
| <th>Panel</th> | ||
| <th>Specimen</th> | ||
| <th>Test</th> | ||
| <th>Result</th> | ||
| <th>Interpretation</th> | ||
| <th>Date</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr> | ||
| <td>SARS-CoV+SARS-CoV-2 (COVID-19) Ag [Presence] in Specimen</td> | ||
| <td/> | ||
| <td>SARS-CoV+SARS-CoV-2 (COVID-19) Ag [Presence] in Specimen</td> | ||
| <td>Detected (qualifier value) (260373001)</td> | ||
| <td>Abnormal (A)</td> | ||
| <td>20201107</td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
| <footnote ID="ecr-refiner-30954-2-20260101000000"> | ||
| <paragraph> | ||
| <content styleCode="Bold">eCR Refiner — Jurisdiction Configuration</content> | ||
| </paragraph> | ||
| <table border="1"> | ||
| <thead> | ||
| <tr> | ||
| <th>Section (LOINC)</th> | ||
| <th>Section Name</th> | ||
| <th>Included</th> | ||
| <th>Action</th> | ||
| <th>Retain Narrative</th> | ||
| <th>Config Version</th> | ||
| <th>Source</th> | ||
| <th>Outcome</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr> | ||
| <td>30954-2</td> | ||
| <td>Results Section</td> | ||
| <td>Yes</td> | ||
| <td>refine</td> | ||
| <td>No</td> | ||
| <td>v9</td> | ||
| <td>Configured by jurisdiction</td> | ||
| <td>Refined; narrative reconstructed</td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
| </footnote> | ||
| </text> |
Comment on lines
+68
to
+85
| if el is None: | ||
| return "" | ||
|
|
||
| xsi_type = el.get(f"{{{_XSI}}}type") | ||
|
|
||
| # coded value (CD)--declared via xsi:type, or monomorphic (a coded | ||
| # element such as interpretationCode that carries @code with no xsi:type) | ||
| if xsi_type == "CD" or (xsi_type is None and el.get("code")): | ||
| disp, code = el.get("displayName"), el.get("code") | ||
| if disp and code: | ||
| return f"{disp} ({code})" | ||
| return disp or code or "" | ||
|
|
||
| # physical quantity (PQ)--declared via xsi:type, or monomorphic | ||
| # (doseQuantity and friends are PQ by the model) | ||
| if xsi_type == "PQ" or (xsi_type is None and el.get("unit") is not None): | ||
| val, unit = el.get("value"), el.get("unit") | ||
| return f"{val} {unit}".strip() if val else "" |
Collaborator
There was a problem hiding this comment.
This looks to be what I was expecting. The fallbacks are good and the cleverness around PQ is pretty neat.
Comment on lines
+303
to
+307
| # TODO: | ||
| # next we will need to add problems, immunizations, and medications | ||
| SECTION_RECONSTRUCTORS: dict[str, SectionReconstructor] = { | ||
| "30954-2": reconstruct_results, # Results | ||
| } |
Collaborator
There was a problem hiding this comment.
Thanks for this. I'll be migrating this over to refiner/app/services/ecr/policy.py so we're only defining these in one spot.
Collaborator
Author
There was a problem hiding this comment.
oh right i forgot i already added this!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔀 PULL REQUEST
💡 Summary
Note
narrative.pywas pulled out of thesectionmodule in theecr/service and is not its own module next to bothsection/andspecification/(some of this is just a clean move out of the originalnarrative.pyThe package is organized around a clean three-layer DRY seam, plus shared writers:
elements.py— low-level primitives. Namespace-qualified element builders (_make_element/_sub_element),<text>placement into the correct CDA R2xs:sequenceslot, comment scrubbing. Everything emitted must carry theurn:hl7-org:v3namespace or it silently failsNarrativeBlock.xsd.footnote.py— the per-section provenance footnote (configured‑vs‑actual, tied to the augmentation run timestamp viaxs:ID).writers.py— the three<text>swap operations, all routed through one_place_section_texthelper: removal notice, reconstruction swap, generic‑path restoration, plus thenullFlavor="NI"minimal‑section stub.reconstruction.py— the new work, structured as:render_typed_value— one place that branches over the closed CDA data‑type set (CD/PQ/ST/IVL_TS/PIVL_TS), handling bothxsi:type‑tagged and monomorphic values.extract_fields— reads a flatFieldSpeclist off one anchor.build_table— emits an XSD‑valid<text><table>with a block‑level "machine‑derived, not clinician‑attested" marker.FieldSpecmaps (PANEL_FIELDS,SPECIMEN_FIELDS,RESULT_FIELDS) — label, relative xpath, kind.reconstruct_results— the only section implemented so far. The row is each result observation, joined up to its organizer (panel) and sideways to a sibling procedure (specimen).SECTION_RECONSTRUCTORS, currently just30954-2Results). Adding a section is "one field map + one function + one dict entry."The narrative action (
"retain"/"remove"/"reconstruct", theDbNarrativeActiontype) is configured per‑section and threaded straight throughprocess_section→ the matching engines. Inentry_matching.pyit's applied after prune+enrich:"reconstruct"it calls the purereconstruct_narrative(section).The engine reports a
narrative_disposition(retained/removed/reconstructed) thatrefine._interpret_run_resultmaps to theREFINED_NARRATIVE_RECONSTRUCTEDoutcome.test_service_narrative_reconstruction.pycovid_results_reconstruction(full eICR/RR fixtures + trace +test_narrative_reconstruction.py), wired intoconftest.pyand the scenarioREPORT.md/build_report.py.Known Open Items (per your memory notes)
reconstruction.pyflags the TODO for problems/immunizations/medications reconstructors.🔗 Related Issue
Fixes #1159
✅ Acceptance Criteria
🧪 How to test
Integration tests
refiner/directoryrequirements.txtanddev-requirements.txtare installedpytest -vv tests/integration/scenarios/scenarios/snapshots/all_sections_covid_influenza/covid_results_reconstruction/to see the raw output of the results being reconstructedℹ️ Additional Information