一、引言:堆积如山的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