Summarizing Clinical Notes with Span Citations
A clinical summary is only useful if it is faithful: every statement must
trace back to something the chart actually says. The failure mode for
note summarization is the confident hallucination — an invented dose, a
fabricated allergy, a discharge diagnosis that was never made. This skill
produces summaries where each line cites the source span that supports it,
so a clinician can verify in one glance and catch any fabrication.
Not a medical device. OpenMed and this skill assist documentation; they
do not diagnose, triage, or make autonomous clinical decisions. Every summary
is a draft for clinician review and editing. Surface that disclaimer in any
UI that renders these summaries.
When to use
- Drafting a discharge summary, transfer note, or SBAR/handoff from a long
encounter.
- Building a problem-oriented view (problem list with supporting evidence).
- Generating a "one-liner" (the single-sentence patient summary) for rounds.
- Chart abstraction where reviewers need quick, verifiable evidence pointers.
Quick start
De-identify before anything else, extract entities to anchor against, then
compose the summary with citations:
import openmed
note = """\
HPI: 68M with HTN, T2DM presents with 3 days of productive cough and fever to
38.9C. CXR shows RLL infiltrate. Started on ceftriaxone and azithromycin.
Hospital course: improved on IV antibiotics, transitioned to PO. Discharged on
amoxicillin-clavulanate. Follow up with PCP in 1 week.
"""
# 1) ALWAYS de-identify before summarizing or sending text anywhere.
deid = openmed.deidentify(note, method="replace", policy="hipaa_safe_harbor")
# 2) Extract entities; their offsets become your citation anchors.
ner = openmed.analyze_text(deid.text, output_format="dict")
spans = {
(e["start"], e["end"]): e["text"]
for e in ner["entities"]
}
# 3) Compose the summary. Every bullet references a (start, end) span so a
# reviewer can click back to the exact evidence.
def cite(start, end):
return f"[{start}:{end}] {deid.text[start:end]!r}"
# Example problem-oriented line, grounded in detected spans:
# "Community-acquired pneumonia (RLL infiltrate) — treated with ceftriaxone +
# azithromycin." with cite(...) anchors for each entity.
analyze_text returns entities as
{"text", "label", "confidence", "start", "end", "metadata"}; the
start/end offsets index the de-identified text, giving you exact,
verifiable citation anchors.
Workflow
- De-identify with
openmed.deidentify. Summaries are often shared or
logged; PHI must be gone before this stage. Keep the mapping
(keep_mapping=True) only if a downstream clinician must re-identify in a
controlled context — never persist the mapping with the summary.
- Extract grounding spans with
openmed.analyze_text (problems, meds,
labs, procedures). These define the allowed evidence set: a summary claim
that cannot point at a span is unsupported.
- Resolve context with
openmed.clinical (negation, temporality, subject)
so "no chest pain" and "father had MI" are not summarized as active patient
problems. See resolving-clinical-context.
- Compose by view:
- One-liner: age/sex + key chronic problems + reason for encounter.
- Hospital course: ordered problems → intervention → response, each line
citing the spans it summarizes.
- Problem-oriented: group entities into problems; attach supporting
med/lab/procedure spans under each.
- Enforce citation coverage. Reject or flag any output sentence with zero
span citations. This is the anti-hallucination gate — keep it strict.
- Mark it a draft. Render the medical-device disclaimer and require human
sign-off before the summary enters the record.
Hand-off to / from OpenMed
- From OpenMed: consumes
openmed.deidentify(...) output (de-identified
text + entity spans) and openmed.analyze_text(...) (PredictionResult
dict). Entity start/end offsets are the citation anchors.
- To OpenMed: the summary text itself can be re-run through
openmed.analyze_text for a coded problem list, or through openmed.eval
leakage gates to confirm no PHI leaked into the generated summary.
- Citation rendering:
analyze_text(..., output_format="html") produces a
span-highlighted view of the source — handy for a click-to-evidence UI.
Edge cases & gotchas
- Hallucination is the failure mode. If your summary backbone is an LLM,
constrain it to the entity/span set and require a citation per sentence; do
not let it introduce facts (doses, diagnoses, dates) absent from the spans.
- Negation & family history. Always run context resolution first; "denies",
"ruled out", "FH of" must not become patient problems.
- Copy-forward / note bloat. EHR notes carry stale copy-pasted blocks. Cite
the most recent supporting span and prefer the current encounter's text.
- Conflicting statements. When the chart contradicts itself (two different
discharge diagnoses), surface both with citations rather than silently
picking one.
- No autonomous action. Never auto-finalize, auto-sign, or auto-route a
summary; it is decision support, not a clinical decision.
- PHI in the summary. A summary can re-introduce identifiers the model
missed in the source. Run the output through
openmed.extract_pii or an
openmed.eval leakage gate before display or storage.
Standards & references
1---2name: summarizing-clinical-notes3description: Produces structured, citation-anchored summaries of clinical notes — one-liner, hospital course, and problem-oriented views — where every claim cites a source span so nothing is hallucinated. Use after de-identifying notes when the user wants a discharge summary draft, handoff/SBAR, problem list, or chart-abstraction summary. De-identify FIRST with openmed.deidentify, then anchor summary claims to entity spans from openmed.analyze_text. Trigger keywords: summarize note, discharge summary, hospital course, problem-oriented, one-liner, SOAP, SBAR, handoff, chart abstraction.4license: Apache-2.05---67# Summarizing Clinical Notes with Span Citations89A clinical summary is only useful if it is *faithful*: every statement must10trace back to something the chart actually says. The failure mode for11note summarization is the confident hallucination — an invented dose, a12fabricated allergy, a discharge diagnosis that was never made. This skill13produces summaries where **each line cites the source span** that supports it,14so a clinician can verify in one glance and catch any fabrication.1516> **Not a medical device.** OpenMed and this skill assist documentation; they17> do not diagnose, triage, or make autonomous clinical decisions. Every summary18> is a *draft for clinician review and editing*. Surface that disclaimer in any19> UI that renders these summaries.2021## When to use2223- Drafting a discharge summary, transfer note, or SBAR/handoff from a long24 encounter.25- Building a problem-oriented view (problem list with supporting evidence).26- Generating a "one-liner" (the single-sentence patient summary) for rounds.27- Chart abstraction where reviewers need quick, verifiable evidence pointers.2829## Quick start3031De-identify before anything else, extract entities to anchor against, then32compose the summary with citations:3334```python35import openmed3637note = """\38HPI: 68M with HTN, T2DM presents with 3 days of productive cough and fever to3938.9C. CXR shows RLL infiltrate. Started on ceftriaxone and azithromycin.40Hospital course: improved on IV antibiotics, transitioned to PO. Discharged on41amoxicillin-clavulanate. Follow up with PCP in 1 week.42"""4344# 1) ALWAYS de-identify before summarizing or sending text anywhere.45deid = openmed.deidentify(note, method="replace", policy="hipaa_safe_harbor")4647# 2) Extract entities; their offsets become your citation anchors.48ner = openmed.analyze_text(deid.text, output_format="dict")49spans = {50 (e["start"], e["end"]): e["text"]51 for e in ner["entities"]52}5354# 3) Compose the summary. Every bullet references a (start, end) span so a55# reviewer can click back to the exact evidence.56def cite(start, end):57 return f"[{start}:{end}] {deid.text[start:end]!r}"5859# Example problem-oriented line, grounded in detected spans:60# "Community-acquired pneumonia (RLL infiltrate) — treated with ceftriaxone +61# azithromycin." with cite(...) anchors for each entity.62```6364`analyze_text` returns entities as65`{"text", "label", "confidence", "start", "end", "metadata"}`; the66`start`/`end` offsets index the de-identified text, giving you exact,67verifiable citation anchors.6869## Workflow70711. **De-identify** with `openmed.deidentify`. Summaries are often shared or72 logged; PHI must be gone before this stage. Keep the mapping73 (`keep_mapping=True`) only if a downstream clinician must re-identify in a74 controlled context — never persist the mapping with the summary.752. **Extract grounding spans** with `openmed.analyze_text` (problems, meds,76 labs, procedures). These define the *allowed evidence set*: a summary claim77 that cannot point at a span is unsupported.783. **Resolve context** with `openmed.clinical` (negation, temporality, subject)79 so "no chest pain" and "father had MI" are not summarized as active patient80 problems. See `resolving-clinical-context`.814. **Compose by view:**82 - **One-liner:** age/sex + key chronic problems + reason for encounter.83 - **Hospital course:** ordered problems → intervention → response, each line84 citing the spans it summarizes.85 - **Problem-oriented:** group entities into problems; attach supporting86 med/lab/procedure spans under each.875. **Enforce citation coverage.** Reject or flag any output sentence with zero88 span citations. This is the anti-hallucination gate — keep it strict.896. **Mark it a draft.** Render the medical-device disclaimer and require human90 sign-off before the summary enters the record.9192## Hand-off to / from OpenMed9394- **From OpenMed:** consumes `openmed.deidentify(...)` output (de-identified95 text + entity spans) and `openmed.analyze_text(...)` (`PredictionResult`96 dict). Entity `start`/`end` offsets are the citation anchors.97- **To OpenMed:** the summary text itself can be re-run through98 `openmed.analyze_text` for a coded problem list, or through `openmed.eval`99 leakage gates to confirm no PHI leaked into the generated summary.100- **Citation rendering:** `analyze_text(..., output_format="html")` produces a101 span-highlighted view of the source — handy for a click-to-evidence UI.102103## Edge cases & gotchas104105- **Hallucination is the failure mode.** If your summary backbone is an LLM,106 constrain it to the entity/span set and require a citation per sentence; do107 not let it introduce facts (doses, diagnoses, dates) absent from the spans.108- **Negation & family history.** Always run context resolution first; "denies",109 "ruled out", "FH of" must not become patient problems.110- **Copy-forward / note bloat.** EHR notes carry stale copy-pasted blocks. Cite111 the most recent supporting span and prefer the current encounter's text.112- **Conflicting statements.** When the chart contradicts itself (two different113 discharge diagnoses), surface both with citations rather than silently114 picking one.115- **No autonomous action.** Never auto-finalize, auto-sign, or auto-route a116 summary; it is decision support, not a clinical decision.117- **PHI in the summary.** A summary can re-introduce identifiers the model118 missed in the source. Run the *output* through `openmed.extract_pii` or an119 `openmed.eval` leakage gate before display or storage.120121## Standards & references122123- HL7 C-CDA Discharge Summary / Continuity of Care Document section structure:124 https://www.hl7.org/ccdasearch/125- Joint Commission discharge summary required elements (CAMH / record of care):126 https://www.jointcommission.org/127- SBAR handoff communication (IHI):128 https://www.ihi.org/resources/tools/sbar-tool-situation-background-assessment-recommendation129- Weed LL, problem-oriented medical record (POMR) — the origin of130 problem-oriented summaries: N Engl J Med, 1968.131- FDA Clinical Decision Support Software guidance (device vs. non-device CDS):132 https://www.fda.gov/regulatory-information/search-fda-guidance-documents/clinical-decision-support-software