OpenMed 患者时间线构建实战:从 analyze_text 临床事件到按日期排序的纵向病史 📅 发布时间:2026/9/19 22:37:04 👁 浏览次数: OpenMed 患者时间线构建实战从 analyze_text 临床事件到按日期排序的纵向病史【免费下载链接】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患者时间线patient timeline是将诊断、用药、手术、就诊等临床事件按时间先后组织起来、并为每个事件挂上归一化日期的一类结构化产出。本篇指南以 OpenMed 的building-patient-timelines技能为核心讲解如何把analyze_text抽取出的实体事件与临床时态标注current / historical / hypothetical组装成可排序、可去重、可直接投喂 FHIR 或 OMOP CDM 的时间线全程在设备端本地完成无需把患者数据发送到云端。读完本文你将掌握抽取事件 → 解析绝对/相对日期 → 按粒度排序 → FHIR 导出的完整闭环并理解 OpenMedopenmed/clinical/timeline/模块底层的实现原理。什么是患者时间线OpenMed 提供什么一条患者时间线本质上是一个带日期的临床事件列表每个条目对应一次诊断diagnosis、用药medication、手术procedure或就诊encounter并携带一个归一化后的日期。OpenMed 为构建时间线提供两块基础能力事件本体通过openmed.analyze_text(...)从临床文本中抽取实体实体自带字符偏移start/end临床时态通过openmed.clinical的上下文判定标记每个提及是当前current、历史historical还是假设hypothetical见 resolving-clinical-context。时间线技能把这些输入组装成一个排序结果。所有步骤都在设备端本地运行on-device如果源笔记包含 PHI应当先执行openmed.deidentify(...)再进入时间线流程并且不要让原始标识符进入日志。何时使用这个技能当你已经从一份或多份笔记中抽取了实体、希望把它们按时间排序时使用本技能典型场景包括重建纵向病史longitudinal history或病程经过course of illness视图为摘要卡片summary card提供事件流作为FHIR 导出前的预处理步骤。如果只需要做实体抽取本身使用 extracting-clinical-entities如果只需要对单个提及做否定/时态判定使用 resolving-clinical-context。快速开始从一条出院小结提取事件以下代码演示了时间线构建的第一步——抽取事件并锚定时间参照系import datetime as dt import openmed note ( Discharge summary, 2024-03-12. Patient admitted 2024-03-08 with chest pain. History of type 2 diabetes diagnosed in 2019. Started on metformin two days after admission. Cardiac catheterization performed yesterday. ) # 1) Extract clinical events (entities carry char offsets: start/end) result openmed.analyze_text(note, output_formatdict) events result[entities] # each: {text, label, confidence, start, end} # 2) Normalize the temporal frame: an explicit document/anchor date drives # resolution of relative expressions (two days after, yesterday). anchor dt.date(2024, 3, 12) # parsed from the note header or document metadataanalyze_text返回的AnalyzeResult包含{text, entities, model_name, timestamp, ...}每个实体为{text, label, confidence, start, end}。利用start/end可以把每个事件定位回原文并找出与之最近的日期表达式。analyze_text 的签名细节从源码看analyze_text的完整签名位于 openmed/init.py几个对时间线构建有直接影响的关键参数参数默认值说明model_namedisease_detection_superclinical模型注册键、Hugging Face 模型 ID 或本地模型路径时间线应按目标实体类型选择模型见 choosing-openmed-modelsoutput_formatdictdict/json/html/csv时间线构建建议保持dict以直接读取结构化实体confidence_threshold0.0低于阈值的实体被过滤None表示保留全部assert_contextFalse置为True时会在每个实体的metadata[clinical_context]下附加确定性的否定、不确定、体验者experiencer与时态标注aggregation_strategysimpletoken 到实体聚合策略simple/first/average/max置None可拿到原始 token 输出sentence_detectionTrue开启句子切分后预测 span 会被切成句内片段并携带sentence_*元数据group_entitiesFalse合并相邻同标签实体底层返回类型AnalyzeResult定义在 openmed/core/results.py字段包括text、entities、model、timestamp、processing_time、metadata其to_dict()保留了 REST 服务使用的历史载荷结构text、entities、model_name、timestamp、processing_time、metadatamodel_name是model的向后兼容别名。完整工作流7 步从文本到排序时间线1. 按需脱敏如果笔记携带 PHI先执行openmed.deidentify(...)或者让时间线以稳定的内部 ID 作为键——绝不要把原始姓名/MRN 写进日志。2. 抽取事件调用openmed.analyze_text(note)抽取 conditions、drugs、procedures 等实体按目标实体类型选择模型。3. 解析临床时态对每个事件使用resolving-clinical-context标记为current/historical/hypothetical并剔除否定提及negated、假设提及hypothetical与家族史提及family-history——这些都不应该出现在患者本人的时间线上。在源码层面这些标签定义于 openmed/clinical/context.pyNEGATED、HYPOTHETICAL、FAMILY_EXPERIENCER并有resolve_temporalitycontext.py与assert_contextcontext.py等函数支撑。4. 归一化日期把每个事件映射到一个日期绝对日期absolute如2024-03-08、March 2019→ 直接解析。同时记录粒度日/月/年——只有年份的事件应落入粗粒度桶而不是被伪装成Jan 1相对日期relative如two days after admission、yesterday、on POD 2→ 需要对照锚点anchor解析文档日期、入院日期或先前某个事件的日期。没有锚点时相对表达式无法解析——应当标记出来而不是猜测。5. 构建事件记录每个事件一条记录(date, granularity, label, surface_text, char_span, temporality, confidence, source_note_id)。6. 排序与去重按(date, granularity)排序跨笔记合并同一事件的重复提及相同 label 日期重叠。7. 输出产出供 UI 使用的排序列表或直接导出 FHIR 资源见下文衔接部分。工作示例events → 排序时间线def to_timeline(events, *, anchor, note_id): events: list of {text,label,start,end,confidence}. anchor: date. Returns sorted [(date, granularity, label, text, confidence)]. timeline [] for e in events: date, gran resolve_event_date(e, notenote, anchoranchor) # your resolver if date is None: continue # undated/unresolvable: route to an undated bucket, dont drop silently timeline.append((date, gran, e[label], e[text], e[confidence])) # year-only (Y) sorts before month (M) before day (D) on ties order {Y: 0, M: 1, D: 2} return sorted(timeline, keylambda r: (r[0], order[r[1]])) # resolve_event_date handles: ISO dates, March 2019 (granM), # yesterday/two days after admission (relative to anchor/admission), POD-n, etc.这段代码体现了时间线排序的两个核心约定日期为主键粒度是次级排序键(date, granularity)字典序排序粒度越粗Y M D在同年同日并列时越靠前避免2019被当成2019-01-01后与真实的一月事件混排不可解析即归入 undated 桶resolve_event_date返回None时不应静默丢弃而是路由到专门的未定日期分组保证信息不丢失。底层原理openmed/clinical/timeline 模块源码剖析OpenMed 仓库中与时间线直接相关的实现集中在openmed/clinical/timeline/目录公共 API 见 openmed/clinical/timeline/init.py由四个子模块组成timex.py确定性时间表达式检测openmed/clinical/timeline/timex.py 提供detect_timexes与TemporalExpression数据类。识别结果带timex_typeDATE/DURATION/SET、原文偏移start/end、可直接表达的归一化值如YYYY-MM-DD或P3W、数值amount、单位unit以及关键的direction——RelativeDirection枚举覆盖past、future、after_previous、before_previous、since、after_anchor、before_anchor、postop_day等相对方向。它刻意采用词典与正则驱动不依赖墙钟时间或远程服务保证在离线/设备端环境中的确定性。resolver.py相对日期解析与事件锚定openmed/clinical/timeline/resolver.py 是时间线解析的核心TimelineEvent由时间表达式派生的事件携带timex、interval归一化区间、temporality、reference_date_dependent及锚点来源NormalizedIntervalISO 日级区间带start/end、lower_bound/upper_bound、precision和uncertainty_days——粗粒度或近似表达如 about会以对称的不确定天数显式表达而不是伪装成精确日期_ANCHOR_TERMS内置锚点词表把admissionadmitted/hospitalization、surgeryoperative/post-op、procedure、discharge、visitclinic、onsetstarted/began等词识别为文档内锚点顶层函数anchor_events、order_events、resolve_timeline分别完成锚定、排序与整体解析evaluate_timeline_gold支持用金标准做评测。该模块还依赖 openmed/clinical/temporal_normalizer.py 提供的 TIMEX3 风格归一化同样是规则驱动、无墙钟依赖只有当调用方提供文档参照时间时才会解析相对表达式否则结果保持显式未锚定unanchored。assembler.py隐私安全的事件装配层openmed/clinical/timeline/assembler.py 是刻意设计的交接层它不抽取实体、不归一化时间、不推断断言而是通过稳定的 ID 与源码偏移把上层提供的输入拼接成ClinicalEvent字段entity、event_kind、normalized_time、section、assertion、source_span、provenance。值得注意的隐私设计ClinicalEvent不保存原文表面字符串source_span与 provenance 中的语义label足以支撑审查时的回联。模块还导出了CLINICAL_EVENT_TIMELINE_ADVISORY声明强调时间线是供审查与下游组织用的确定性辅助标注不是临床决策、诊断或治疗建议并声明CLINICAL_EVENT_TIMELINE_SCHEMA_VERSION 1作为输出格式版本号。与 OpenMed 其他能力的衔接上游输入analyze_text实体见 extracting-clinical-entities与clinical上下文标签见 resolving-clinical-context是时间线的输入笔记含 PHI 时上游先跑deidentify。下游FHIR 导出把排序好的带日期事件送入 FHIR 导出openmed.interop见 exporting-to-fhir映射关系如下时间线事件FHIR R4 资源/字段入院/出院事件Encounter诊断日期Condition.onsetDateTime用药开始MedicationStatement.effectiveDateTime手术/操作Procedure.performedDateTime这些映射在仓库的 FHIR 实现中都有对应支撑例如 openmed/interop/fhir_operations.py、openmed/interop/fhir/bulk.py 等模块对Encounter、Condition、onsetDateTime等资源与字段的处理。更下游同一份时间线还可以喂给 etl-to-omop-cdm映射到condition_occurrence/drug_exposure的开始/结束日期以及临床摘要卡片。边界情况与注意事项没有锚点就没有相对日期。Two days later、POD 2、yesterday 在没有参照日期时毫无意义。先解析文档日期/入院日期解析不到就把事件留在undated桶而不是编造一个日期。保留粒度。不要把 2019 强转成2019-01-01再参与排序——它会压过真实的 1 月事件。携带粒度标记让粗粒度日期保守排序参考上文order {Y: 0, M: 1, D: 2}与NormalizedInterval.precision。剔除错误的人和错误的时态。否定no prior MI、假设would consider surgery if…、家族史family-history提及绝不能出现在患者本人的时间线上——这正是时态判定这一步要解决的。时区与两位年份有歧义。临床时间线应归一化为日期date而非带时间的 datetime除非确实有时间戳dd/mm与mm/dd要根据文档语言环境locale判定而不是猜测。未来/计划事件是真实存在的但应放入单独的planned轨道不要与已发生的事件交错混排。日志中禁止出现原始 PHI。按label offset note id记录时间线事件绝不记录患者姓名或原始笔记文本——这与ClinicalEvent不保存原文表面字符串的设计一脉相承。标准与参考FHIR R4Encounter、ConditiononsetDateTime、recordedDate、MedicationStatement.effectiveDateTime、Procedure.performedDateTime是时间线导出的目标模型ISO 8601日期/时间表达的基础规范时间线输出的归一化日期与区间均遵循该表示法TimeML / i2b2 2012 时序关系评测任务临床时间表达式归一化的行业背景。OpenMed 的timex.py、temporal_normalizer.py与resolver.py正是以 TIMEX3 风格进行确定性归一化并显式处理before/after/overlap等时序关系类型。最后再次强调时间线构建的输出是确定性辅助标注用于审查与下游数据组织不构成临床决策、诊断或治疗建议也不替代临床人员核验——仓库中CLINICAL_EVENT_TIMELINE_ADVISORY与TIMELINE_ASSISTIVE_DISCLAIMER的措辞即为此而设。【免费下载链接】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),仅供参考