NER finds that a condition was mentioned; it does not tell you whether the
patient has it. "Patient denies chest pain," "history of MI," and "rule out
PE" all surface entities that must not be recorded as active, present
findings. OpenMed's openmed.clinical ConText layer assigns three deterministic
axes to each span — negation, temporality, uncertainty — turning raw
mentions into clinically faithful assertions before they reach a problem list or
FHIR Condition.
extracting-clinical-entities, before grounding,
problem-list building, or analytics.verificationStatus /clinicalStatus
and need the upstream signal.import openmed
from openmed.clinical import (
resolve_span_context, assert_context_axes,
NEGATED, HISTORICAL, HYPOTHETICAL, UNCERTAIN,
)
note = "Patient denies chest pain. History of MI. Concern for PE; rule out DVT."
# 1) Extract entities (registry key, HF id, or local path).
ents = openmed.analyze_text(note, model_name="disease_detection_superclinical",
output_format="dict")
# 2) Assign ConText axes per entity. Pass the span text plus a window of cues.
for e in ents:
span = e["word"] # entity surface text
window = note # full sentence/note as modifier context
ctx = resolve_span_context(span, window)
print(span, "->", ctx.negation, ctx.temporality, ctx.certainty)
# "chest pain" -> negated recent certain (do NOT record as present)
# "MI" -> affirmed historical certain (past, not active)
# "PE" -> affirmed recent uncertain (hedged; flag, don't drop)
resolve_span_context returns a ClinicalContextResult(negation, temporality, certainty). For a downstream-grounding-shaped record use assert_context_axes,
which returns a ClinicalAssertion with a .to_dict() that omits unset axes.
analyze_text, take each
entity's surface text and the surrounding sentence (or the whole short note)
as the modifier window. The ConText helpers accept a string, a span mapping
with a text-like key, or any object exposing .text, plus optional
modifier_hits.resolve_negation(span, window) → AFFIRMED or
NEGATED. It uses a NegEx/ConText cue lexicon ("denies," "no evidence of,"
"without," "negative for"), masks pseudo-negation ("not ruled out,"
"cannot be excluded") so those don't refute the concept, and counts true cues
with even/odd parity so double-negation is deterministic.resolve_temporality(span, window) → RECENT
(default), HISTORICAL ("history of," "h/o," "s/p," "resolved," "PMH"), or
HYPOTHETICAL ("if," "should," "in case of"). A conditional span is treated
as hypothetical even if a historical cue is also present.resolve_uncertainty(span, window) → CERTAIN
or UNCERTAIN ("concern for," "suspicious for," "rule out," "probable,"
"vs," "r/o"). Uncertain spans are flagged, not dropped.NEGATED spans; route
HISTORICAL to inactive/resolved status; do not record HYPOTHETICAL spans
as present; mark UNCERTAIN spans provisional. Use the constants, not string
literals, so a vocabulary change doesn't silently break comparisons.extracting-clinical-entities: this skill consumes analyze_text
Disease/Finding entities. Without context resolution, every mention — including
negated and historical ones — would be (wrongly) treated as present.from openmed.clinical import resolve_negation, resolve_temporality, resolve_uncertainty, resolve_span_context, assert_context_axes, ClinicalAssertion and the NEGATED/AFFIRMED,
HISTORICAL/RECENT/HYPOTHETICAL, CERTAIN/UNCERTAIN constants.reconciling-problem-lists: feed each entity plus its
ClinicalContextResult so active vs. resolved vs. historical is decided
correctly and negated mentions are excluded.negation=negated → verificationStatus=refuted;
temporality=historical → inactive/resolved clinicalStatus;
certainty=uncertain → verificationStatus=provisional. The layer emits the
axis; it does not build the FHIR record.segmenting-clinical-sections) for long notes.clinicalStatus / verificationStatus:
https://hl7.org/fhir/R4/condition.html