OpenMed HL7 v2 叙述文本提取:将 ADT/ORU/ORM 消息转为去标识化临床 NLP 文本并保留字段溯源
发布时间:2026/9/18 13:57:27 作者:尧图编辑部 阅读量:1,286

OpenMed HL7 v2 叙述文本提取将 ADT/ORU/ORM 消息转为去标识化临床 NLP 文本并保留字段溯源【免费下载链接】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/openmedHL7 v2 管道式消息pipe-delimited message是医院信息系统中最常见的互联格式但 ADT、ORU、ORM 这类消息的字段结构对下游 NLP 或人工审阅并不友好且其中携带大量 PHI。本文讲解 OpenMed 提供的extract_hl7v2_narrative叙述提取器它复用仓库既有的 HL7 v2 解析器与结构化脱敏层把常见 ADT、ORU、ORM 消息渲染为可读、已去标识化的叙事文本narrative同时为最终文本中的每个可见值保留精确的字符区间与 HL7 坐标溯源segment 出现位置 字段号。读完本文你将掌握该 API 的两种渲染模式、双层本地隐私管线、全部可调参数以及基于最终文本偏移量做溯源查询的完整用法。设计定位不是第二个解析器而是既有脱敏链路的叙述化视图叙述提取器在架构上明确选择“复用而非重写”它构建在 HL7 v2 解析器与结构化脱敏器openmed/interop/hl7v2.py之上而不是独立实现一套 HL7 解析逻辑也不做完整的 HL7 一致性校验conformance validation。从源码可以看到extract_hl7v2_narrative内部依次调用redact_hl7v2(...)按规则处理结构化字段患者标识、姓名、日期、地址、电话等parse_hl7v2(...)解析脱敏后的消息文本级 PII 管线把完整渲染文本再统一过一遍openmed.core.pii.deidentify默认methodmask。这样设计的好处是职责清晰结构化字段的规则化脱敏与自由文本的 PII 掩盖各司其职叙述提取器只负责“排版 溯源投影”因此也不存在第二套解析器带来的行为漂移。快速开始入口函数位于 openmed/interop/hl7v2_narrative.py接受消息文本、UTF-8 文件路径或已解析的HL7Message对象三种输入from openmed.interop.hl7v2_narrative import extract_hl7v2_narrative result extract_hl7v2_narrative(synthetic_oru.hl7) print(result.text) for span in result.spans_for(OBX, 5): print(span.source.path, result.text_for(span))其中result.text是去标识化后的完整叙述文本result.spans_for(OBX, 5)返回所有来自 OBX 段第 5 字段观察值的区间result.text_for(span)取出该区间在最终文本中的实际可见内容。关于坐标约定源码中有明确注释HL7V2FieldSource.segment_index是段在整条消息中的零基位置而segment_occurrence是同一三字母段名的一基出现序号。path属性拼接出的紧凑路径如OBX[2]-5含义是“第二条 OBX 段的第 5 字段”。这一点在测试tests/unit/interop/test_hl7v2_narrative.py中有直接断言OBX出现在消息第 4 个位置segment_index 3其第一次出现即OBX[1]-5。值得强调的是溯源记录里只保留偏移量、固定标签和 HL7 坐标绝不保留原始字段值——这是脱敏设计的安全底线任何原始 PHI 都只存在于处理前的中间态。两种叙事模式flat 与 sectioned模式由mode参数控制可选值限定为flat和sectioned源码中以Literal[flat, sectioned]约束传入其他值会立即抛出ValueError测试test_invalid_mode_is_rejected_before_processing验证了这一点。flat 模式默认输出紧凑的、句子式文本适合直接喂给下游 NLPflat extract_hl7v2_narrative(message, modeflat)渲染规则见_render_items各条目标以label: value形式输出条目之间以单个空格连接若值的末尾不是.、!、?之一自动补一个句号不同 section 之间也以空格衔接。例如对 ORU 消息渲染出的文本形如Message type: ORU R01. Observation 1: Clinical note (NOTE). Result 1: Patient [PERSON] called from [PHONE] about [ID_NUM]. Observation 2: Glucose (GLU). Result 2: 7.1. Units 2: mmol/L. Note 1: Follow-up email [EMAIL] belongs to [PERSON].sectioned 模式输出稳定的 Markdown 风格小标题每条字段单独一行适合人工审阅界面sectioned extract_hl7v2_narrative(message, modesectioned) print(sectioned.text)典型 section 固定为Message、Patient、Encounter、Orders、Observations、Notes源码中的_SECTION_ORDER常量空 section 会被整体省略两个模式都保证 section 内保持消息原始顺序并可通过result.sections拿到每个 section 在最终文本中的start/end偏移。sectioned 模式输出的头部形如## Message Message type: ADT A01 HL7 version: 2.5 ## Patient Patient ID: [ID_NUM] Patient name: [PERSON] ...测试test_sectioned_adt_has_stable_patient_and_encounter_sections验证了 sectioned 输出对同一输入是完全确定性的两次调用结果相等且result.sections中的每个区间都能在result.text中切出以## section名开头的真实内容。双层本地隐私管线叙述提取包含两层完全在本机执行的隐私处理结构化层openmed.interop.hl7v2.redact_hl7v2按配置处理结构化字段——患者标识、姓名、日期、地址、电话号码等。默认字段映射DEFAULT_FIELD_MAP覆盖PID、PD1、NK1、GT1、IN1、IN2、OBX、NTE等段的常见直接标识字段动作包括clear清空、hash确定性哈希令牌、surrogate标签感知的假值、date-shift统一日期偏移、redact_text自由文本走 PII 管线。其中OBX-5仅当OBX-2为TX/FTDEFAULT_NOTE_VALUE_TYPES时才作为自由文本处理。叙述层完整渲染后的叙述文本再整体过一次openmed.core.pii.deidentify默认methodmask。这一层兜底覆盖自由文本与任何其他渲染值——包括结构化层未覆盖的字段保证返回前所有内容都已脱敏。一个关键实现细节是在结构化脱敏阶段叙述提取器故意把自由文本钩子设为恒等函数_identity_deidentifier让OBX/NTE的原文先保留到叙述层再由第二遍管线统一处理。测试test_deidentifier_receives_complete_narrative_once证实完整叙述文本只经过一次最终脱敏调用。偏移量重投影脱敏后溯源依然精确第二遍脱敏可能改变文本长度例如Jane Roe变成[PERSON]因此提取器在两次处理之间用difflib.SequenceMatcher计算新旧文本的 opcodes再把每个字段区间和 section 区间投影project到最终文本上见_project_range/_project_boundary。这意味着spans、sections里的所有偏移量都索引最终返回的去标识化文本即使占位符改变了长度也依然成立投影后空区间start end会被过滤掉。自定义最终文本管线用deidentify_kwargs配置叙述层的最终文本管线。为了支持确定性的离线测试可以传入一个可调用对象它返回字符串或返回带deidentified_text属性的对象/映射result extract_hl7v2_narrative( message, deidentify_kwargs{policy: hipaa_safe_harbor}, )def fake_deidentifier(text: str, **kwargs): return text.replace(Jane Roe, [PERSON]) # 返回 str def fake_deidentifier_obj(text: str, **kwargs): return {deidentified_text: text} # 返回映射_deidentified_text会依次尝试字符串、含deidentified_text键的映射、含deidentified_text属性的对象三种形态都不满足时抛出TypeError。测试中使用的fake_deidentifier就是返回SimpleNamespace(deidentified_text...)的典型离线替身。参数详解extract_hl7v2_narrative的完整签名以源码为准def extract_hl7v2_narrative( message_or_path: str | Path | HL7Message, *, mode: NarrativeMode flat, field_map: Mapping[FieldKey | str, Any] | None None, deidentifier: TextDeidentifier | None None, deidentify_kwargs: Mapping[str, Any] | None None, date_shift_days: int 30, lang: str en, locale: str | None None, seed: int | None 0, ) - HL7V2Narrative参数默认值作用modeflatflat句子式或sectionedMarkdown 小节非法值抛ValueErrorfield_mapNone使用DEFAULT_FIELD_MAP转发给redact_hl7v2的结构化脱敏规则键可用(PID, 5)或PID-5两种写法deidentifieropenmed.core.pii.deidentify叙述层脱敏函数离线测试可传确定性的替身deidentify_kwargs{}叙述层脱敏的额外关键字参数默认并入methodmask与langdate_shift_days30结构化层统一的日期偏移天数默认 30 天多次运行结果稳定seed恒定时langen结构化脱敏与叙述脱敏共享的语言localeNone结构化假值surrogate的可选 Faker localeseed0结构化假值生成的确定性种子date_shift_days、lang、locale、seed与可选的field_map都会转发给既有 HL7 脱敏层。注意两点默认 30 天偏移在多次运行间稳定而叙述中的日期如 OBR 的请求时间、OBX 的观察时间仍会随完整叙述一起经过最终隐私管线。在redact_hl7v2层不传date_shift_days时则会随机选择非零偏移_random_nonzero_shift从 ±1~365 中随机取所以叙述提取器显式给 30 天默认值正是为了可复现。溯源查询从最终文本反查原始坐标叙述结果HL7V2Narrative提供三组查询入口offset result.text.index(7.1) for span in result.provenance_at(offset): print(span.source.segment) # OBX print(span.source.field_position) # 5 print(span.source.path) # OBX[2]-5provenance_at(offset)返回覆盖该最终文本偏移量的所有HL7V2FieldSpan区间端点start offset end即左闭右开越界返回空元组spans_for(segment, field_position, *, segment_occurrenceNone)按段名/字段号可加出现序号反查区间段名会自动转大写text_for(span)取出区间对应的最终文本内容spans与显式别名field_mappings等价的完整区间元组测试断言两者恒等。HL7V2FieldSpan每个区间提供start/end最终叙述文本中的偏移end为排他端点section/label固定的叙述上下文如Observations/Result 2source.segment三字母段名source.segment_index该段在完整消息中的零基位置source.segment_occurrence同名段的一基出现序号source.field_position一基 HL7 字段号。测试里result.text.index(7.1)得到的偏移其溯源结果恰好是[(OBX[2]-5, Result 2)]即“第二条 OBX 的第 5 字段”与文档中OBX[2]-5的语义完全一致。支持的渲染范围渲染器覆盖 ADT、ORU、ORM 流程中最常用的上下文具体到“段 → 渲染字段”的对应关系如下实现见_collect_itemsSegmentRendered fieldsMSH消息类型_message_code取前两个组件如ORU R01与 HL7 版本MSH-12EVN事件类型EVN-1与事件记录时间EVN-2日期格式化PID患者 IDPID-3、姓名PID-5XPN 组件重排、出生日期PID-7、管理性别PID-8PV1患者类别PV1-2、就诊位置PV1-3组件拼接、主治医生PV1-7、入院/出院时间PV1-44/PV1-45ORC医嘱控制ORC-1、placer/filler 医嘱号ORC-2/ORC-3OBR申请检查项目OBR-4编码组件渲染为显示名 (代码)与申请时间OBR-7OBX观察项目OBX-3、结果OBX-5、单位OBX-6、参考范围OBX-7、异常标志OBX-8、结果状态OBX-11、观察时间OBX-14NTE备注文本NTE-3数值型枚举字段会渲染为“标签 (代码)”形式源码内置了多组标签映射性别_SEX_LABELSA→Ambiguous、F→Female、M→Male、N→Not applicable、O→Other、U→Unknown、患者类别_PATIENT_CLASS_LABELSE→Emergency、I→Inpatient、O→Outpatient、P→Preadmit、R→Recurring patient、结果状态_RESULT_STATUS_LABELSC→Corrected、F→Final、I→Specimen in lab、P→Preliminary、R→Entered not verified、S→Partial、X→Cannot obtain。这就是测试断言Administrative sex: Male (M)、Patient class: Inpatient (I)的来源。日期字段HL7 的YYYYMMDD[HHMM[SS]]紧凑格式会被格式化为YYYY-MM-DD HH:MM:SS风格_decode_escapes负责还原 HL7 转义序列\F\、\S\、\R\、\T\、\E\、\.br\换行等保证渲染出的文本可读。OBR、OBX、NTE的多条记录会通过计数器order_number/observation_number/note_number生成Requested test 1、Observation 2、Note 1这样的稳定标签。未知段如Z开头的自定义段不会被叙述渲染器渲染但它们在底层解析器中仍然可见并且可以被自定义field_map覆盖脱敏再由最终叙述层兜底。另外文档与实现都明确把消息映射为 FHIR 资源不属于本工具的职责范围。实测样例与测试依据仓库提供了两条真实可跑的合成消息作为固定测试夹具tests/unit/interop/fixtures/synthetic_phi_oru.hl7ORU^R01 消息含自由文本型OBX|1|TX|NOTE^Clinical note^L||Patient Jane Roe called from 555-0199 about MRN67890.、数值型OBX|2|NM|GLU^Glucose^L||7.1|mmol/L以及NTE备注tests/unit/interop/fixtures/synthetic_phi_adt.hl7ADT^A01 消息含EVN、PIDDOE^JOHN^A、19800101、NK1、PV1I患者类别、ER^01^01位置。单元测试 tests/unit/interop/test_hl7v2_narrative.py 覆盖了本主题的全部关键行为test_flat_oru_is_coherent_safe_and_maps_results_to_source_fieldsflat 模式输出连贯、Jane Roe/邮箱/MRN67890全部消失、spans_for(OBX, 5)溯源到OBX[1]-5、provenance_at精确命中OBX[2]-5、文本中不会出现..双句号test_sectioned_adt_has_stable_patient_and_encounter_sectionssectioned 输出确定性稳定、日期被统一偏移1980-01-01→1980-01-31即 30 天默认偏移、section 顺序与区间正确test_deidentifier_receives_complete_narrative_once完整叙述只经过一次最终脱敏test_all_field_mappings_index_nonempty_final_text所有field_mappings区间均为非空且落在最终文本范围内field_mappings与spans恒等test_parsed_message_and_mapping_result_are_supported直接传入HL7Message对象亦可test_extractor_calls_public_hl7_parser通过 monkeypatch 证实提取器只调用公开的parse_hl7v2没有第二套解析逻辑test_invalid_mode_is_rejected_before_processing非法模式在真正处理前即被拒绝。适用边界该工具是纯本地、机械式的处理链路不启动 MLLP 监听、不做完整 HL7 一致性校验、不调用任何网络服务符合 OpenMed“患者数据不出网络”的定位结构化脱敏 叙述脱敏的双层设计保证了返回文本中不残留原始字段值但若你的工作流需要更强的统计匿名性如 k-匿名/重识别风险评估请结合仓库的 重识别风险 相关文档进一步评估若你需要结构化输出而非叙述文本或需要把脱敏结果映射为 FHIR 资源请使用 HL7 v2 结构化脱敏 及 FHIR 相关集成文档而不是在本工具范围内扩展。综合来看extract_hl7v2_narrative的价值在于把“HL7 管道消息 → 可读叙述 精确字段溯源”这条链路封装成一次调用结构化脱敏、叙述渲染、文本 PII 掩盖、偏移量重投影四步无缝衔接最终交付的既是适合临床 NLP/人工审阅的安全文本又是可编程查询的溯源数据模型。【免费下载链接】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),仅供参考