OpenMed 临床实体提取到 FHIR:从本地 NER 到确定性 R4 Bundle 的落地指南
【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed
本指南围绕 OpenMed 仓库中的技能文档 extract-clinical-entities-to-fhir 展开,讲解如何把合成或已脱敏文本中的临床实体,映射为确定性的 FHIR R4 资源并组装成 Bundle 提交给 FHIR 服务器。读完本文,你将掌握"抽取与编码分离"的管线设计、analyze_text到to_bundle的完整调用链、以及避免发明术语编码、保证隐私安全的工程要点。
核心思想:把"抽取"和"临床编码"彻底分离
OpenMed 的定位是 100% 本地运行的医疗 AI(临床 NER 与 HIPAA PII 脱敏),而 FHIR 导出是这条链路的"最后一公里"。技能文档开篇即给出原则:
Separate extraction from clinical coding. OpenMed finds spans and supplies the mechanical FHIR builders; the application decides which resource type and status are clinically appropriate.
也就是说:
- 抽取(extraction):由 OpenMed 完成——找出文本中的实体 span,并给出置信度、标签、偏移量;
- 编码(clinical coding):由应用层负责——决定每个 span 映射成哪种 FHIR 资源类型(Condition / MedicationStatement / Observation……)、赋予什么状态(active / confirmed / final……)、使用哪个术语编码(只能来自用户批准的映射或术语服务)。
OpenMed 提供的是一套"纯机械"的 FHIR 装配工具,从源码看,bundle.py 的模块注释明确写道:"The assembler is purely mechanical: it never synthesises resources (a Patient removed by de-identification stays absent) and does not validate profiles."——它从不凭空捏造资源,被脱敏移除的 Patient 不会复活,也不做 profile 校验(校验是独立的可选环节)。
六步操作流程
技能文档给出了标准流程,共六步:
- 保证数据安全:输入必须是合成文本,或在可信边界内先完成脱敏,再进行抽取;
- 运行分析模型:调用
openmed.analyze_text,使用任务匹配的临床模型; - 过滤预测结果:按 label 和置信度过滤,并把偏移量(offsets)保留在 PHI 安全的审计记录中;
- 映射资源类型:把每个被接受的 span 映射到正确的 FHIR 资源类型;
- 添加术语编码:只能来自用户批准的映射或术语服务,绝不发明编码;
- 组装与校验:用
to_bundle组装资源,并针对目标 profile 进行校验。
其中第 2 步是整个管线的入口。从源码看,analyze_text 是 OpenMed 顶层公开 API,默认模型为disease_detection_superclinical,关键参数包括:
model_name:注册表键、完整的 Hugging Face 模型 id 或本地模型路径;confidence_threshold:实体最低置信度(None表示保留全部),技能示例中使用0.5;aggregation_strategy:Hugging Face 聚合策略,默认"simple",设为None可拿原始 token 输出;output_format:"dict"(默认)、"json"、"html"或"csv";assert_context:默认关闭;开启后会给每个实体附加确定性的否定、不确定性、体验者(experiencer)与时间性标签,写入metadata["clinical_context"],这与技能文档"保留否定、时间性、体验者上下文"的要求直接相关;include_confidence/group_entities/cache_results等格式化相关开关。
可运行的合成示例
技能文档要求先安装模型运行时:
python -m pip install "openmed[hf]"随后即可运行完整示例(技能文档原文,保持可复制性):
import json from openmed import analyze_text from openmed.clinical.exporters.fhir import to_bundle note = "Assessment: type 2 diabetes mellitus is stable on metformin." result = analyze_text( note, model_name="disease_detection_superclinical", confidence_threshold=0.5, ) resources = [{"resourceType": "Patient", "id": "synthetic-patient"}] for index, entity in enumerate(result.entities, start=1): if entity.label.upper() not in {"CONDITION", "DIAGNOSIS", "DISEASE"}: continue resources.append( { "resourceType": "Condition", "id": f"condition-{index}", "clinicalStatus": { "coding": [ { "system": ( "http://terminology.hl7.org/CodeSystem/" "condition-clinical" ), "code": "active", } ] }, "verificationStatus": { "coding": [ { "system": ( "http://terminology.hl7.org/CodeSystem/" "condition-ver-status" ), "code": "confirmed", } ] }, # A text-only CodeableConcept is preferable to an invented code. "code": {"text": entity.text}, "subject": {"reference": "Patient/synthetic-patient"}, } ) if len(resources) == 1: raise RuntimeError("No condition spans met the label and confidence rules") bundle = to_bundle(resources, doc_id="synthetic-note-001") print(json.dumps(bundle, indent=2))这个示例有三个值得注意的工程细节:
- 状态编码使用 HL7 官方 CodeSystem URI:
condition-clinical与condition-ver-status分别承载clinicalStatus(active)与verificationStatus(confirmed); - 没有可用编码时用纯文本
CodeableConcept:"code": {"text": entity.text}比发明一个不存在的编码安全得多; - 空结果保护:如果没有 span 通过 label 与置信度过滤,直接抛出
RuntimeError,避免向下游提交空 Bundle 造成误导。
to_bundle:确定性装配的底层原理
示例中的核心装配函数是to_bundle,其完整签名(见 bundle.py):
def to_bundle( resources: Sequence[Mapping[str, Any]], *, doc_id: str = "openmed-document", bundle_type: str = "transaction", profile_check: Callable[[Mapping[str, Any]], Any] | None = None, ) -> dict[str, Any]参数语义:
resources:待包装的独立 FHIR 资源,按 Bundle 内顺序排列;每个资源必须含resourceType,带id的资源在 Bundle 内必须ResourceType/id唯一;doc_id:源文档的稳定标识,与资源索引共同决定每个条目的确定性urn:uuidfullUrl,同一输入永远产生字节级一致的输出;bundle_type:Bundle 的type,默认"transaction";对transaction/batch类型,每个条目会附带request块(method: POST、url: resourceType),服务器可直接据此创建资源;profile_check:可选回调,收到完成 Bundle 的深拷贝,作为合规或策略门禁;回调返回值被忽略,但异常会向上传播——这正对应流程第 6 步"validate against the target profile"。
to_bundle内部做了三件确定性工作(见 bundle.py):
- 生成稳定 fullUrl:每个资源通过
deterministic_fullurl(doc_id, index)得到urn:uuid; - 重写内部引用:把指向 Bundle 内已有资源的字面引用(如
"Patient/synthetic-patient")改写为对应urn:uuid,杜绝悬空内部引用;引用不到的资源(如被脱敏移除的 Patient)保持原样不动(references.py 中的deterministic_fullurl); - 隐私净化:在导出前调用
sanitize_india_health_identifiers,将 ABHA、UPI、ration-card 等印度健康标识符从 FHIRIdentifier和 PatientID 类字段中移除(见 privacy.py)。
to_bundle还会显式拒绝两类非法输入并抛ValueError:资源缺少resourceType,或两个资源共享相同resourceType与id——重复 id 会静默破坏内部引用映射,因此被显式拦截。对应的测试见 tests/unit/clinical/test_fhir_bundle.py,覆盖了事务 Bundle 每个资源一个 entry、fullUrl 唯一、collection 类型无 request 块、batch 类型保留 request 块、缺 resourceType 抛错、重复 id 抛错等场景。
面向断言感知的导出器:to_condition 与 to_observation
技能文档强调"不要把否定、假设、时间性、体验者上下文丢在一边就断言资源为 active/confirmed"。仓库为此提供了断言感知(assertion-aware)的导出器,它们基于AssertedGroundedSpan(见 assertion_grounding.py)工作。
to_condition
to_condition 把断言感知的 grounded span 物化为 FHIR R4Condition:
- 体验者过滤:span 属于非患者主体(家人/其他人)时,
to_condition直接返回None,根本不会产生患者 Condition——这是结构性的安全边界; - 状态仅来自受审地图:
clinicalStatus/verificationStatus只来自受审的 status 映射,一个被否定或假设的 span 永远不可能被编码为active+confirmed; - 无明确状态时省略:当状态不主张 active/inactive(如被否定或假设的发现)时,
clinicalStatus字段被省略,符合 FHIR 不声称该状态的不变式; - 两个 HL7 系统 URI 常量
CONDITION_CLINICAL_SYSTEM与CONDITION_VER_STATUS_SYSTEM与技能示例中的字符串完全一致。
to_observation
to_observation 与之对称:
- 被否定/假设的观察标记为
cancelled而不是作为最终结果呈现(_observation_status中对 refuted/hypothetical 返回"cancelled",否则"final"); - 数值用 FHIR
valueQuantity表达并保留可选的 UCUM 单位(system: http://unitsofmeasure.org),布尔用valueBoolean,其他值落为valueString; - 与
to_condition相同,非患者体验者的 span 返回None。
其他导出器与统一门面
- to_diagnostic_report:状态采用显式 allowlist(
registered、partial、preliminary、final、amended、corrected、appended、cancelled、entered-in-error、unknown),缺失/空值用"unknown",非法状态 fail-closed 拒绝;字段名按 R4/R5 并集 allowlist 过滤,且绝不从conclusion推断conclusionCode; - to_fhir:顶层门面,返回
FHIRBundle与FHIRExportSummary,是 grounded-span 导出的规范公共入口; - to_codeable_concept:受控的辅助型(assist-only)编码发射器,每个概念携带源证据偏移量,并附上显式的"非自主临床决策"免责声明扩展(
MEDICAL_DEVICE_ASSIST_ONLY_DISCLAIMER)。
安全检查清单
技能文档给出了不可妥协的安全底线,全部保留如下:
- 日志与追踪中禁止回显原始标识:原始标识符、源文本、可逆映射不得出现在日志、
OperationOutcome.diagnostics或 trace 元数据中; - 患者身份服务与临床事实分离:患者身份服务必须与抽取出的临床事实分开维护;
- 保留上下文再断言状态:在把资源断言为 active/confirmed 之前,必须保留否定(negation)、时间性(temporality)与体验者(experiencer)上下文;
- 无可用编码时用纯文本
CodeableConcept; - 按接收方要求校验:Bundle 必须针对接收方的 FHIR 版本与 profile 要求进行校验;
- 不捆绑受限术语表:受限术语(restricted terminologies)不要直接打进 Bundle,应使用用户自己的授权许可服务。
与此呼应,仓库内置的校验器 validate.py 提供了无依赖的 R4 结构校验:validate_bundle/validate_resource覆盖Condition、Observation、MedicationStatement、DiagnosticReport、Encounter等 9 类BASE_R4_RESOURCE_TYPES,产出 FHIRPath 风格的定位与"值无关"(value-free)消息——校验报告不会回显临床内容或标识符,天然适配审计场景;另有 profile_check.py 的check_bundle负责实现指南包(implementation guide)级别的 profile 评估。
仓库配套实战示例
技能文档推荐阅读并运行仓库中的端到端演练 examples/first_five_minutes_redact_extract_fhir.py,它以"前五分钟上手"为目标,展示了一条离线友好的完整链路:
- 脱敏:
deidentify(note, method="mask", confidence_threshold=0.5, use_safety_sweep=True)对内置合成病历做掩码脱敏(示例内通过自定义 loader 避免首次运行下载模型); - 抽取:
TextProcessor().extract_medical_entities(redacted_text)从脱敏文本中确定性抽取vital_signs、dosages等实体; - 组装:映射为最小 FHIR 资源集——
Patient+Encounter(status: finished)+ 每条生命体征一个Observation(status: final,valueString承载数值)+ 每个剂量一个MedicationStatement(status: active,medicationCodeableConcept.text与dosage[].text均用纯文本); - 导出:
to_bundle(resources, doc_id="first-five-minutes-synthetic")生成确定性事务 Bundle。
该示例的完整调用链与技能文档六步流程一一对应,可作为把本指南落地的起点。示例中所有文本均为合成数据,标识符(DEMO-001、demo.patient@example.test)均为占位符,可直接运行验证输出。
适用前提与限制
- 本管线面向合成或已脱敏文本;若输入含真实 PHI,必须先在自己的可信边界内完成脱敏(OpenMed 的
deidentify即为此设计),再进入抽取环节; to_bundle是机械装配器,不校验 profile,也不代替你决定资源类型与状态——这两者属于应用层职责;- 术语编码只能来自用户批准的映射或术语服务,OpenMed 的 grounded 导出器提供的是带证据与免责声明的辅助编码(assist-only),需要人工复核后使用;
- 若想在本机复现,请先
python -m pip install "openmed[hf]"安装模型运行时,并确认网络可拉取所选模型(或配置本地模型路径)。
【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考