以需求驱动,借AI解决工作痛点问题一例:Lab2FHIR项目开发实战

一、引言:堆积如山的PDF报告

医院病理科每月产生超过400份病理/化验PDF报告------DNA倍体分析、TCT液基细胞学、HPV分型检测、胃镜病理、外科病理......这些报告以PDF文件形式散落在文件服务器上,无法检索、无法统计、无法与HIS/EHR系统互通

病理科想要按疾病类型统计发病率,临床医生想快速查找某个病人的历史报告,信息科想做数据分析和互联互通------但面对一堆非结构化的PDF文件,这些都无从谈起。

这就是我启动Lab2FHIR项目的直接原因:把非结构化的化验单/病理PDF报告,自动转换为国际医疗数据交换标准HL7 FHIR R4格式,让数据真正"活"起来。

二、需求分析:到底要解决什么问题?

跟病理科聊完,需求其实很明确。

覆盖6种常见报告:DNA倍体分析(65份)、TCT液基细胞学(77份)、HPV基因分型(79份)、胃镜病理(87份)、外科病理(78份)、非妇科细胞学(13份),共计404份真实样本。每种报告有不同的关键字段------DNA要提取DI值、SPF值和染色体状态,胃镜病理要提取慢性炎症、活动性、萎缩、肠化四项评分。

输出采用 HL7 FHIR R4 标准格式(DiagnosticReport + Observation Bundle),配 LOINC 编码映射,支持按姓名、病理号、诊断关键词全文检索。

部署上医院有自己的规矩:内网环境,不能连外网,服务器跑的是 Windows Server 2019,越轻越好,别给信息科添麻烦。

三、技术选型:够用就好

需求明确了,选型没有纠结。

后端用 FastAPI ,异步高性能,自带接口文档。PDF提取用 pdfplumber ,因为404份样本全是文本型PDF,根本用不着OCR。数据库选 SQLite + FTS5 ,单文件零配置,内置全文搜索,查一次只要6毫秒。前端用 Vue 3 + Element Plus ,拖拽上传、三栏对照布局很快就能搭出来。PDF预览靠 PDF.js ,浏览器端直接渲染,支持缩放翻页。认证用 JWT + bcrypt ,区分病理科管理员和临床医生两个角色。部署用 nginx + nssm 注册成 Windows 服务,开机自启。

为什么不选Docker?医院服务器在内网,拉镜像不方便。Python + nginx 直接跑在Windows上,信息科同事更容易理解和维护。

四、开发过程:AI辅助下的4天实战

整个项目从零到上线,4天42次提交。AI编码助手(Claude Code)全程参与架构设计、代码编写、测试和文档。

第一步:跑通核心链路。 用pdfplumber把PDF文字提取出来,患者姓名、性别、年龄、病理号、诊断结论都能准确获取。

python 复制代码
import pdfplumber

def extract_text(file_path):
    with pdfplumber.open(file_path) as pdf:
        return "\n".join(
            page.extract_text() or "" for page in pdf.pages
        )

第二步:报告类型自动识别。 不同报告格式差异巨大,必须先识别类型再调对应解析器。这里有个坑:胃镜病理和外科病理的报告抬头都写"病理诊断报告单",如果先匹配"病理诊断",胃镜报告就被错误归类了。解决办法很简单------胃镜特征词(慢性炎症+活动性+萎缩+肠化)优先检查,病理诊断兜底匹配。

python 复制代码
PARSER_PRIORITY = [
    ("胃镜病理", check_gastric),      # 优先检查胃镜特征词
    ("HPV分型",   check_hpv),
    ("TCT细胞学", check_tct),
    ("DNA倍体",   check_dna),
    ("细胞学",    check_cytology),
    ("病理诊断",   check_pathology),   # 兜底匹配
]

第三步:6种专用解析器。 以DNA倍体分析为例,需要从表格提取DI值、SPF值、五条染色体倍体状态,最终结构化存入JSON。

第四步:FHIR R4标准化输出。 将结构化数据映射到国际标准格式,生成含LOINC编码的Bundle。这是整个项目对外价值的核心------有了FHIR,数据才能对接HIS、参与互联互通。

json 复制代码
{
  "resourceType": "Bundle",
  "type": "collection",
  "entry": [{
    "resource": {
      "resourceType": "DiagnosticReport",
      "code": {
        "coding": [{
          "system": "http://loinc.org",
          "code": "47527-7",
          "display": "Cytology report"
        }]
      }
    }
  }]
}

第五步:三栏对照前端。 解析结果、PDF原文、FHIR JSON三栏并排展示,用户边看原文边核对数据,一目了然。

五、关键挑战与解决思路

挑战一:同名不同类。 DNA和TCT报告共用F系列病理号。去重不能只看病理号,必须用"病理号+报告类型"做复合键。

挑战二:跨页诊断。 部分外科病理的诊断结论跨越多页。策略是先把所有页的文本拼起来,再整体匹配,不是逐页解析。

挑战三:Unicode兼容。 报告里有全角中文引号、特殊字符µm³。解析器要同时兼容半角和全角变体,不然正则匹配直接漏掉。

挑战四:Windows离线部署。 生产环境是Windows Server 2019内网服务器。放弃Docker,改用nssm把FastAPI注册成Windows服务,nginx反向代理前端,开机自启,零外网依赖。

六、成果展示

404份PDF样本,诊断提取成功率100%(404份全部成功)。覆盖6种报告类型,7个API端点。FTS5全文搜索6毫秒响应,单份PDF解析约200毫秒。前端5个页面:首页仪表盘、批量导入、报告列表、详情三栏对照、病人时间线。6个后端测试加1个E2E测试保障质量。

部署到Windows服务器后,病理科和临床医生在浏览器里就能搜索病人的历史报告,按疾病类型筛选统计。FHIR标准输出也为后续对接HIS系统做好了准备。

七、结语:需求驱动,AI加速

回顾整个Lab2FHIR的开发历程,几点体会:

从痛点出发,不是从技术出发。 需求明确后,选型自然水到渠成------pdfplumber够用就不上OCR,SQLite够用就不上PostgreSQL,Windows能跑就不用Docker。

AI是加速器,不是替代品。 Claude Code在架构设计、代码生成、测试编写、文档输出上大幅提升了效率。4天42次提交从零到上线,以前至少需要几周。但领域知识------比如病理报告的业务逻辑、LOINC编码规则------仍然需要人来把关。

先跑通,再完善。 第一阶段只做DNA+TCT+HPV三种报告,验证核心链路可行后再扩展。100%的提取率说明规则解析在医疗文本领域完全行得通。

真实场景验证最宝贵。 404份真实PDF样本是最好的测试集。真实数据中才会遇到跨页诊断、全角半角混用、同名不同类这些边界情况。


本项目代码已开源(MIT协议):github.com/is81/Lab2FHIR

产品页:is81.net/lab2fhir