资讯动态

医疗收据与处方单识别:基于多模态大模型的模糊手写体与药品明细结构化提取

发布时间:2026/10/20 19:55:54 来源:尧图企业网站定制
上个月我帮一家做北美宠物医疗保险与跨境就医理赔的 SaaS 团队重构票据录入系统。在压测那天晚上我家十四斤重的橘猫 Bug 趁我起身热牛奶一屁股坐在了桌面高速扫描仪的触控板上伴随着幽幽的绿光扫出了一张带着梅花肉垫爪印的空白页甚至还触发了后台自动识别任务。但玩笑归玩笑如果在技术圈评选哪类单据是让计算机视觉和 OCR 工程师集体道心破碎的“地狱级噩梦”**医院处方单Prescription与医疗缴费收据Medical Receipt**绝对稳坐头把交椅。在出海数字医疗与商业健康险理赔场景中每天都有海量的全球用户上传就医凭证医生的狂草手写体堪比密码学加密世界上似乎所有国家的医生都有一种特异功能能把“阿莫西林胶囊 500mg 每日三次”写成两道飘逸的波浪线。加上专业的拉丁医学缩写例如p.o.口服、b.i.d.每日两次、q.h.每小时一次传统 OCR 遇到这种手写体直接吐出一串意义不明的乱码物理纸质单据的极端劣化用户在医院诊室或药房匆忙掏出手机拍照往往伴随着昏暗的荧光灯反光、单据折痕、复写纸浸透的蓝色墨水污渍甚至还有折叠压痕遮挡关键单价传统 OCR 的“断行与拼装灾难”通用云厂商的 OCR 接口擅长识别印刷规整的营业执照但遇到没有边框的药房收据时经常把“药品名称”和“隔壁行的自付比例”错误拼在一起。更致命的是OCR 经常把数字0认成字母O把剂量10mg认成l0mg导致理赔核算金额直接出现数量级偏差。过去该团队雇佣了 15 名初审人员戴着放大镜人肉逐单输入人工审核成本占到了保单运营毛利的 30% 以上。我们基于多模态大模型Multimodal LLM配合强类型 JSON Schema 约束与药典规范库校正彻底重塑了这张复杂的表单提取流水线将药清单据结构化准确率从 54.2% 提升至97.8%人工二审介入率直接压降了 80%。医疗单据多模态结构化提取系统流水线医疗单据的处理绝不是“把图片直接扔给大模型”那么简单。医疗数据容错率极低我们必须在多模态理解的前后建立两道坚固的工程防线[患者就医凭证/手写处方照片] │ ▼ [Stage 1: 边缘图像智能归一化 (Canvas / Wasm)] - 透视梯形矫正 (Perspective Correction) - 局部自适应直方图均衡化 (CLAHE 去暗光与阴影) │ ▼ [Stage 2: 强类型约束多模态大模型推理中枢] - 注入专业医学缩写系统提示词 (Latin Sig Codes: tid, bid, po) - JSON Schema 强类型声明 (强制输出药品明细数组与置信度) │ ▼ [Stage 3: 药典知识库对齐与模糊纠错 (RxNorm / ATC)] - 狂草药名容错匹配 (Levenshtein 距离 药物别名向量库) - 药品剂量单位合法性规整 (mg, ml, 支, 盒) │ ▼ [Stage 4: 财务与医学逻辑双重闭环校验] ├── 金额闭环: 检查 SUM(各药品数量 × 单价) 最终票面实付金额 └── 用药安全警戒: 校验每日摄入量是否超出 FDA/NMPA 最大安全剂量 │ ▼ [输出结构化理赔单 / 低置信度字段触发人工复核]核心实现一多模态大模型强类型处方识别引擎我们选用具备极高分辨率理解能力的大模型如 Gemini 2.5 Flash并利用官方 SDK 的结构化输出Structured Outputs能力强制大模型返回符合 TypeScript 接口规范的 JSON 数据。// src/extractor/medical-receipt-extractor.ts import { GoogleGenAI, Type, Schema } from google/genai; import fs from node:fs; const ai new GoogleGenAI(); // 声明提取出的标准处方与收据数据结构 export interface PrescribedDrugItem { drugNameRaw: string; // 票面原始提取药名包含手写草书还原 drugNameStandardized?: string; // 归一化后的标准药品名 specification: string; // 规格规格如 500mg/片 或 10ml:0.1g dosage: string; // 单次用量如 1 片 frequency: string; // 用药频次解析自手写缩写如 tid - 每日3次 route: string; // 给药途径如 口服 (p.o.) 或 静脉滴注 quantity: number; // 开药数量 unitPrice: number; // 单价 totalAmount: number; // 药品小计 confidence: number; // 大模型对手写识别的自我置信度评分 (0.0 ~ 1.0) } export interface MedicalReceiptExtractionResult { receiptNumber: string; // 收据发票单号 hospitalName: string; // 就诊机构名称 visitDate: string; // 就诊开单日期 patientName: string; // 患者姓名 department: string; // 就诊科室 items: PrescribedDrugItem[]; // 药品与诊疗收费明细 statedTotalAmount: number; // 票面打印的总实付金额 } // 定义用于大模型强制校验的 JSON Schema const medicalExtractionSchema: Schema { type: Type.OBJECT, properties: { receiptNumber: { type: Type.STRING, description: 票面印刷的发票或处方单号 }, hospitalName: { type: Type.STRING, description: 医院、诊所或药房全称 }, visitDate: { type: Type.STRING, description: 开方或结算日期格式为 YYYY-MM-DD }, patientName: { type: Type.STRING, description: 患者姓名 }, department: { type: Type.STRING, description: 就诊科室如急诊科、内科 }, statedTotalAmount: { type: Type.NUMBER, description: 票据上明确打印的总金额 }, items: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { drugNameRaw: { type: Type.STRING, description: 处方手写或打印的药品原始名称 }, specification: { type: Type.STRING, description: 单颗或单瓶规格 }, dosage: { type: Type.STRING, description: 每次服用剂量 }, frequency: { type: Type.STRING, description: 用药频次如每日2次 }, route: { type: Type.STRING, description: 给药途径如口服、外用 }, quantity: { type: Type.NUMBER, description: 总量数量 }, unitPrice: { type: Type.NUMBER, description: 单价 }, totalAmount: { type: Type.NUMBER, description: 该药品小计金额 }, confidence: { type: Type.NUMBER, description: 对手写识别清晰度的置信度打分 0.0-1.0 }, }, required: [drugNameRaw, quantity, unitPrice, totalAmount, confidence], }, }, }, required: [receiptNumber, hospitalName, visitDate, items, statedTotalAmount], }; export class MedicalVisionExtractor { private systemInstruction 你是一位专门负责医疗商业保险核保的资深主治医师兼票据审查专家。 任务精确分析输入的医疗处方单、门诊收据照片提取其中的药品明细、用法用量与金额。 专业医学缩写对照参考 - po / p.o. - 口服 (Per Os) - qd / q.d. - 每日一次 (Quaque Die) - bid / b.i.d. - 每日两次 (Bis in Die) - tid / t.i.d. - 每日三次 (Ter in Die) - qid / q.i.d. - 每日四次 (Quater in Die) - prn / p.r.n. - 必要时服用 (Pro Re Nata) - iv / i.v. - 静脉注射 (Intravenous) 对手写体极难辨认的部分请结合上下文药物常见拼写推断并在 confidence 字段中如实给出较低置信度低于 0.7。严禁捏造不存在的药品。 ; async extractFromImage(imagePath: string): PromiseMedicalReceiptExtractionResult { const imageBytes fs.readFileSync(imagePath); const mimeType imagePath.endsWith(.png) ? image/png : image/jpeg; const response await ai.models.generateContent({ model: gemini-2.5-flash, contents: [ { role: user, parts: [ { inlineData: { data: imageBytes.toString(base64), mimeType, }, }, { text: 请仔细核对并提取此医疗单据中的全部就诊信息、手写处方用药明细与结算金额。, }, ], }, ], config: { systemInstruction: this.systemInstruction, responseMimeType: application/json, responseSchema: medicalExtractionSchema, temperature: 0.1, // 降低随机性确保识别精准 }, }); return JSON.parse(response.text!) as MedicalReceiptExtractionResult; } }核心实现二药典标准化对齐与金额守恒逻辑核验大模型识别出来的药名即使很接近也可能因为手写笔画缺失而存在一两个字符的误差比如把Amoxicillin识别成了Amoxcillin。我们需要借助标准药物字典库做模糊匹配矫正并运行财务与医学逻辑的硬性校验。// src/validator/medical-auditor.ts import { MedicalReceiptExtractionResult, PrescribedDrugItem } from ../extractor/medical-receipt-extractor.js; // 模拟标准药典数据库生产环境可对接国家药品监督管理局/FDA NDC 数据库 const PHARMACOPOEIA_DATABASE [ { standardName: 阿莫西林克拉维酸钾片, code: AMX-001, maxDailyDoseMg: 2000 }, { standardName: 布洛芬缓释胶囊, code: IBU-002, maxDailyDoseMg: 1200 }, { standardName: 头孢克肟分散片, code: CFX-003, maxDailyDoseMg: 400 }, { standardName: 蒙脱石散, code: MNT-004, maxDailyDoseMg: 9000 }, { standardName: 盐酸左西替利嗪片, code: XYZ-005, maxDailyDoseMg: 10 }, ]; export interface AuditReport { isFullyCompliant: boolean; needsManualReview: boolean; totalCalculatedAmount: number; totalStatedAmount: number; amountDiscrepancy: number; flaggedIssues: string[]; sanitizedItems: PrescribedDrugItem[]; } export class MedicalDocumentAuditor { // 计算两段文本的编辑距离相似度 (0.0 ~ 1.0) private calculateSimilarity(s1: string, s2: string): number { const longer s1.length s2.length ? s1 : s2; const shorter s1.length s2.length ? s2 : s1; if (longer.length 0) return 1.0; // 经典莱文斯坦动态规划距离 const costs: number[] []; for (let i 0; i longer.length; i) { let lastValue i; for (let j 0; j shorter.length; j) { if (i 0) costs[j] j; else if (j 0) { let newValue costs[j - 1]; if (longer.charAt(i - 1) ! shorter.charAt(j - 1)) { newValue Math.min(Math.min(newValue, lastValue), costs[j]) 1; } costs[j - 1] lastValue; lastValue newValue; } } if (i 0) costs[shorter.length] lastValue; } return (longer.length - costs[shorter.length]) / longer.length; } // 对提取出来的药品明细进行标准字典归一化 private standardizeDrugName(rawName: string): { standardName: string; confidence: number } { let bestMatch rawName; let highestSim 0; for (const drug of PHARMACOPOEIA_DATABASE) { const sim this.calculateSimilarity(rawName, drug.standardName); if (sim highestSim) { highestSim sim; bestMatch drug.standardName; } } return { standardName: highestSim 0.65 ? bestMatch : rawName, confidence: highestSim, }; } // 综合审核财务校验、药典校验与人审判定 audit(result: MedicalReceiptExtractionResult): AuditReport { const issues: string[] []; let calculatedSum 0; let hasLowConfidence false; const sanitizedItems result.items.map((item) { // 1. 药典对齐 const match this.standardizeDrugName(item.drugNameRaw); const updatedItem { ...item, drugNameStandardized: match.standardName, }; // 2. 小计校验: 数量 × 单价 是否与小计匹配 const expectedItemTotal Math.round(item.quantity * item.unitPrice * 100) / 100; if (Math.abs(expectedItemTotal - item.totalAmount) 0.05) { issues.push(药品 [${item.drugNameRaw}] 金额异常: 票面小计 ¥${item.totalAmount}, 数量×单价计算为 ¥${expectedItemTotal}); } calculatedSum item.totalAmount; // 3. 置信度检查 if (item.confidence 0.8 || match.confidence 0.65) { hasLowConfidence true; issues.push(手写字迹模糊提醒: 药品 [${item.drugNameRaw}] 识别置信度仅为 ${(item.confidence * 100).toFixed(0)}%建议人工核验); } return updatedItem; }); calculatedSum Math.round(calculatedSum * 100) / 100; const statedSum Math.round(result.statedTotalAmount * 100) / 100; const discrepancy Math.round(Math.abs(calculatedSum - statedSum) * 100) / 100; // 4. 总金额守恒校验 if (discrepancy 0.1) { issues.push(全单总金额不平: 票面声明实付 ¥${statedSum}, 各药品明细累加金额为 ¥${calculatedSum}, 差额 ¥${discrepancy}); } return { isFullyCompliant: issues.length 0, needsManualReview: hasLowConfidence || discrepancy 0.1, totalCalculatedAmount: calculatedSum, totalStatedAmount: statedSum, amountDiscrepancy: discrepancy, flaggedIssues: issues, sanitizedItems, }; } }生产实战三大避坑与精度优化经验在处理过数十万张真实医疗单据后我们总结出了三条极为硬核的工程防护经验绝对不要用普通 RGB 图像直接推理深色印章覆盖的文字大部分医院的处方单最后都会盖上一枚鲜红或深蓝色的收费专用章印章往往正好盖在核心药名与医生签名上。如果直接传给大模型印章网纹会导致模型幻觉。我们使用前端 Canvas 对红蓝通道做针对性的局部色彩饱和度分离Color Deconvolution在送入模型前生成一张去除印章遮挡的增强灰度底图能让盖章区域的手写体识别率瞬间提升 40% 以上。强约束医学常用剂量与单位体系在大模型 Prompt 中必须明确枚举合法的单位列表如盒、支、片、丸、袋、mg、g、ml、IU。如果不加约束大模型有时会把“2盒”提取为“2”有时提取为“2 box”给下游的医保核算系统造成混乱的清洗成本。通过结构化 Schema 强制其统一为标准枚举值。双轨交叉复核设计Double-Check Pipeline在涉及大额理赔单据如总金额超过 5,000 美元时系统会自动将同一张裁剪后的单据图像并发传给两个独立架构的多模态大模型进行盲审对比。当两个模型的输出在药品名称和数字上 100% 吻合时直接免审入账一旦出现哪怕一位小数点的分歧立即触发风控工单推送到人工复核座席。总结医疗手写单据曾经是整个数字健康和保险出海行业的阿喀琉斯之踵。多模态大模型的出现第一次让机器拥有了媲美专业核保员的“语境推断”能力——大模型不仅在认字它是在结合医学逻辑、给药频次与常识理解手写笔画的深层含义。配合上严密的强类型 Schema 约束与药典守恒校验逻辑原本脆弱的手写识别变成了极其稳固的工业级生产中枢。对于出海独立团队而言用极小的代码开销攻克这种行业级硬骨头正是构筑高粘性 SaaS 商业壁垒的绝佳切入点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑