[{"data":1,"prerenderedAt":1315},["ShallowReactive",2],{"post-\u002Fposts\u002Fmeddococr-interpreter-source-analysis":3,"all-posts-nav":1034},{"id":4,"title":5,"body":6,"categories":1016,"date":1018,"description":12,"draft":1019,"extension":1020,"hidden":1019,"meta":1021,"navigation":1022,"path":1023,"published":1019,"seo":1024,"stem":1025,"tags":1026,"__hash__":1033},"posts\u002Fposts\u002Fmeddococr-interpreter-source-analysis.md","MedDocOCR-Interpreter 源码导读：医疗文档 OCR、结构化抽取与报告解读原型",{"type":7,"value":8,"toc":994},"minimark",[9,13,16,20,23,29,32,43,46,49,54,57,64,70,73,90,93,99,102,108,115,119,126,132,135,141,144,172,176,182,185,217,224,238,242,248,251,257,264,267,277,280,286,289,295,298,304,307,310,320,326,329,343,346,383,389,392,395,401,410,416,419,435,438,444,447,451,454,459,466,511,514,535,542,546,553,559,562,578,591,594,600,606,619,625,699,708,711,717,720,726,732,799,802,805,811,817,820,831,834,837,840,847,854,861,868,871,874,906,909,912,915,953,956,959,962,982,985,990],[10,11,12],"p",{},"MedDocOCR-Interpreter 是一个面向医疗单据的 OCR 与报告解读原型项目。它的目标不是直接训练一个完整医疗大模型，而是先把“医疗文档从图片\u002F文本到结构化结果，再到可解释解读”的工程链路搭起来：输入可以是检验报告、处方、发票、住院清单等文档，系统会经过 OCR、版面检测、表格结构恢复、字段归一化、规则知识库检索和安全解读，最后输出异常项、风险说明、复查建议、证据和安全提示。",[10,14,15],{},"这篇文章按源码结构拆解这个项目：它解决什么问题、流水线怎么串起来、各个模块分别负责什么、为什么适合作为医疗多模态项目的工程原型，以及后续如果要从原型升级到真实系统，应该补哪些能力。",[17,18,19],"h2",{"id":19},"项目定位",[10,21,22],{},"从 README 看，这个项目的完整定位是：",[24,25,26],"blockquote",{},[10,27,28],{},"医疗文档 OCR 与报告解读多模态系统原型。",[10,30,31],{},"它覆盖的链路可以概括为：",[33,34,40],"pre",{"className":35,"code":37,"language":38,"meta":39},[36],"language-text","medical document image\u002Ftext\n  -> PaddleOCR \u002F OCR fallback\n  -> LayoutDetector\n  -> TableStructureRecognizer\n  -> FieldNormalizer\n  -> MedicalKnowledgeBase + ReportInterpreter\n  -> abnormalities \u002F risks \u002F suggestions \u002F evidence \u002F safety notes\n","text","",[41,42,37],"code",{"__ignoreMap":39},[10,44,45],{},"也就是说，它不是单点 OCR demo，而是一个“文档智能 + 医学规则解读”的端到端原型。核心价值在于把复杂医疗单据处理拆成了几个可替换的工程边界：OCR 可以从文本 fallback 换成 PaddleOCR，版面识别可以从规则启发式换成 LayoutLMv3，报告解读可以从 JSON 规则库升级成 RAG，生成式部分也预留了 Qwen2.5-VL、LLaVA、SFT、DPO、PPO 等训练路线。",[10,47,48],{},"如果用一句话概括源码架构：",[24,50,51],{},[10,52,53],{},"MedDocOCR-Interpreter 是一个 Python 实现的医疗文档结构化流水线：用 Pydantic 统一输入输出 schema，用可替换模块串起 OCR、版面、表格、字段和医学解读，再通过 CLI、FastAPI 和 Pytest 保证可运行、可集成、可验证。",[17,55,56],{"id":56},"仓库目录总览",[10,58,59,60,63],{},"项目目录比较清晰，主代码都在 ",[41,61,62],{},"meddococr_interpreter"," 下：",[33,65,68],{"className":66,"code":67,"language":38,"meta":39},[36],"meddococr_interpreter\u002F\n  api\u002F                 FastAPI 服务入口\n  interpret\u002F           医学知识库检索与报告解读\n  layout\u002F              版面识别模块\n  normalize\u002F           字段归一化模块\n  ocr\u002F                 OCR 适配器\n  synthetic\u002F           合成报告数据生成\n  table\u002F               表格结构恢复\n  training\u002F            训练 recipe 设计\n  cli.py               命令行入口\n  config.py            配置加载\n  pipeline.py          主流水线编排\n  schemas.py           统一数据结构\nconfigs\u002Fdefault.yaml   默认配置\ndata\u002Fkb\u002Fmedical_rules.json 医学规则知识库\ndocs\u002F                  项目说明和面试材料\nexamples\u002F              示例报告文本\ntests\u002F                 端到端测试\n",[41,69,67],{"__ignoreMap":39},[10,71,72],{},"这个结构有两个特点：",[74,75,76,84],"ol",{},[77,78,79,83],"li",{},[80,81,82],"strong",{},"模块边界很清楚","：OCR、layout、table、normalize、interpret 都是独立目录，后续替换真实模型时不需要推翻整体工程。",[77,85,86,89],{},[80,87,88],{},"原型可直接运行","：即使没有安装 PaddleOCR，也可以用文本 fallback 跑通 pipeline 和测试，这对演示、面试和迭代非常友好。",[17,91,92],{"id":92},"数据结构设计",[10,94,95,98],{},[41,96,97],{},"schemas.py"," 是整个项目的类型中心，里面定义了文档类型、OCR token、版面块、表格 cell、关键字段、异常项、流水线输入输出等模型。",[10,100,101],{},"主要 schema 如下：",[33,103,106],{"className":104,"code":105,"language":38,"meta":39},[36],"DocumentType    文档类型：lab_report \u002F prescription \u002F invoice \u002F inpatient_list \u002F unknown\nOCRToken        OCR 文本、bbox、置信度、行号\nLayoutBlock     版面块标签、bbox、文本、置信度\nTableCell       表格行列、文本、row_span、col_span\nKeyField        字段名、值、单位、参考范围、状态、置信度\nAbnormalItem    异常项、风险、建议、证据\nPipelineInput   输入路径\u002F原始文本\u002F文档类型\u002F上下文\nPipelineOutput  OCR、版面、表格、字段、解读、安全提示\n",[41,107,105],{"__ignoreMap":39},[10,109,110,111,114],{},"这里最重要的是 ",[41,112,113],{},"PipelineOutput","。它不是只返回最终一句医学建议，而是保留了中间结果：OCR tokens、layout blocks、table cells、key fields 都会出现在输出里。这种设计很适合文档智能系统，因为医疗场景尤其需要可追溯性：如果最终解读有问题，可以回看是 OCR 错了、表格恢复错了、字段归一化错了，还是医学规则匹配错了。",[17,116,118],{"id":117},"主流水线pipelinepy","主流水线：pipeline.py",[10,120,121,122,125],{},"主入口是 ",[41,123,124],{},"MedDocOCRPipeline","。它在初始化时组装五个模块：",[33,127,130],{"className":128,"code":129,"language":38,"meta":39},[36],"PaddleOCREngine\nLayoutDetector\nTableStructureRecognizer\nFieldNormalizer\nReportInterpreter\n",[41,131,129],{"__ignoreMap":39},[10,133,134],{},"运行逻辑非常直观：",[33,136,139],{"className":137,"code":138,"language":38,"meta":39},[36],"payload\n  -> ocr.recognize()\n  -> layout.detect()\n  -> table.recover()\n  -> normalizer.normalize()\n  -> interpreter.interpret()\n  -> PipelineOutput\n",[41,140,138],{"__ignoreMap":39},[10,142,143],{},"这段代码虽然不复杂，但体现了一个很好的工程习惯：主流水线只做编排，不把具体 OCR、表格解析、医学规则写在一起。这样后续升级时可以逐步替换模块，例如：",[145,146,147,154,160,166],"ul",{},[77,148,149,150,153],{},"把 ",[41,151,152],{},"PaddleOCREngine"," 换成云 OCR、PaddleOCR PP-Structure 或自研 VLM OCR。",[77,155,149,156,159],{},[41,157,158],{},"LayoutDetector"," 换成 LayoutLMv3、DocLayout-YOLO 或视觉语言模型。",[77,161,149,162,165],{},[41,163,164],{},"MedicalKnowledgeBase"," 从 JSON 规则升级为向量检索 + 医学知识库。",[77,167,149,168,171],{},[41,169,170],{},"ReportInterpreter"," 从规则模板升级为带引用约束的 LLM 解读器。",[17,173,175],{"id":174},"ocr-模块paddleocr-文本-fallback","OCR 模块：PaddleOCR + 文本 fallback",[10,177,178,181],{},[41,179,180],{},"ocr\u002Fpaddle_engine.py"," 实现了一个 PaddleOCR adapter，并提供了确定性的文本 fallback。",[10,183,184],{},"它支持两类输入：",[74,186,187,197,210],{},[77,188,189,190,192,193,196],{},"如果传入 ",[41,191,38],{},"，直接按行切分成 ",[41,194,195],{},"OCRToken","。",[77,198,189,199,202,203,202,206,209],{},[41,200,201],{},".txt","、",[41,204,205],{},".md",[41,207,208],{},".csv"," 文件，也按文本文件读取。",[77,211,212,213,216],{},"如果传入图片，则尝试加载 ",[41,214,215],{},"paddleocr.PaddleOCR","，输出文本、bbox 和 score。",[10,218,219,220,223],{},"这种 fallback 很实用。医疗 OCR 项目经常依赖较重的视觉库，如果每次 demo、单测都必须安装完整 OCR 环境，开发效率会很低。这里通过文本 fallback，可以把后续版面、表格、字段和解读模块先跑通；真正接图片时，再安装 ",[41,221,222],{},"[ocr]"," optional dependencies。",[10,225,226,227,230,231,234,235,237],{},"项目默认会过滤低置信度 OCR 结果，阈值来自配置中的 ",[41,228,229],{},"min_ocr_score","，默认是 ",[41,232,233],{},"0.3","。图片路径的 OCR 输出会统一封装成 ",[41,236,195],{},"，从而保证下游模块不依赖 PaddleOCR 的原始返回格式。",[17,239,241],{"id":240},"版面识别规则版-layoutdetector","版面识别：规则版 LayoutDetector",[10,243,244,247],{},[41,245,246],{},"layout\u002Flayoutlmv3_detector.py"," 当前是启发式实现，但命名上已经预留了 LayoutLMv3 替换边界。",[10,249,250],{},"它按关键词和分隔符把每一行分成几类：",[33,252,255],{"className":253,"code":254,"language":38,"meta":39},[36],"header        包含姓名、性别、年龄、报告、医院等\n table_header  包含项目、结果、参考、单位等\n table_row     包含 |、tab、逗号等表格分隔符\n footer        包含医师、审核、日期等\n text          其他普通文本\n",[41,256,254],{"__ignoreMap":39},[10,258,259,260,263],{},"这种规则当然不能覆盖复杂扫描件，但对原型很有价值：它让系统具备了“版面块”这一层抽象。后续如果换成真正的 LayoutLMv3、Donut、DocFormer 或 VLM，只需要保证输出还是 ",[41,261,262],{},"LayoutBlock","，后续表格和字段模块就能继续复用。",[17,265,266],{"id":266},"表格结构恢复",[10,268,269,272,273,276],{},[41,270,271],{},"table\u002Fstructure.py"," 的 ",[41,274,275],{},"TableStructureRecognizer"," 负责把 OCR 行恢复成表格单元格。",[10,278,279],{},"它通过正则识别三类分隔符：",[33,281,284],{"className":282,"code":283,"language":38,"meta":39},[36],"|\ntab\n连续逗号\n",[41,285,283],{"__ignoreMap":39},[10,287,288],{},"每一行被拆成若干列后，输出为：",[33,290,293],{"className":291,"code":292,"language":38,"meta":39},[36],"TableCell(row=行号, col=列号, text=单元格文本)\n",[41,294,292],{"__ignoreMap":39},[10,296,297],{},"例如：",[33,299,302],{"className":300,"code":301,"language":38,"meta":39},[36],"项目 | 结果 | 单位 | 参考范围\n白细胞 | 12.8 | 10^9\u002FL | 3.5-9.5\n",[41,303,301],{"__ignoreMap":39},[10,305,306],{},"会恢复出表头和数据行，每个 cell 都带有 row \u002F col 信息。这一步虽然仍是规则实现，但已经抽出了表格结构层，为后续计算 TEDS、接入表格检测模型或输出 HTML\u002FMarkdown 表格打好了接口。",[17,308,309],{"id":309},"字段归一化",[10,311,312,315,316,319],{},[41,313,314],{},"normalize\u002Ffields.py"," 是项目里非常关键的一层。它把不同格式的 OCR 文本和表格 cell 统一变成 ",[41,317,318],{},"KeyField","：",[33,321,324],{"className":322,"code":323,"language":38,"meta":39},[36],"name       指标名\nvalue      指标值\nunit       单位\nreference  参考范围\nstatus     normal \u002F low \u002F high \u002F abnormal\nconfidence 置信度\n",[41,325,323],{"__ignoreMap":39},[10,327,328],{},"字段抽取有两条路径：",[74,330,331,337],{},[77,332,333,336],{},[80,334,335],{},"优先从表格抽取","：如果一行有“项目、结果、单位、参考范围”这样的结构，就把它转成字段。",[77,338,339,342],{},[80,340,341],{},"再用正则补充","：对普通文本行匹配“字段名 + 数值\u002F阴性\u002F阳性\u002F正常\u002F异常 + 单位 + 参考范围”。",[10,344,345],{},"异常判断逻辑也在这里完成：",[145,347,348,360,371],{},[77,349,350,202,353,356,357,196],{},[41,351,352],{},"阳性",[41,354,355],{},"异常"," 会被标为 ",[41,358,359],{},"abnormal",[77,361,362,202,365,356,368,196],{},[41,363,364],{},"阴性",[41,366,367],{},"正常",[41,369,370],{},"normal",[77,372,373,374,202,377,380,381,196],{},"如果有数值和参考范围，会比较上下界，得到 ",[41,375,376],{},"low",[41,378,379],{},"high"," 或 ",[41,382,370],{},[10,384,385,386,388],{},"这层的意义是把格式各异的医疗报告变成统一字段。只要 ",[41,387,318],{}," 稳定，后面的医学解读就不需要关心原始报告到底是表格、冒号文本，还是 OCR 分行结果。",[17,390,391],{"id":391},"医学知识库与安全解读",[10,393,394],{},"医学解读分为两个文件：",[33,396,399],{"className":397,"code":398,"language":38,"meta":39},[36],"interpret\u002Frag.py       MedicalKnowledgeBase\ninterpret\u002Freport.py    ReportInterpreter\n",[41,400,398],{"__ignoreMap":39},[10,402,403,405,406,409],{},[41,404,164],{}," 当前读取的是 ",[41,407,408],{},"data\u002Fkb\u002Fmedical_rules.json","。规则库中包含白细胞、血红蛋白、C 反应蛋白、葡萄糖等指标，每个指标有三类内容：",[33,411,414],{"className":412,"code":413,"language":38,"meta":39},[36],"risk        可能风险\nsuggestion  复查\u002F就诊建议\nevidence    规则证据\n",[41,415,413],{"__ignoreMap":39},[10,417,418],{},"检索逻辑是基于字段名的简单包含匹配。如果没有命中具体规则，就返回通用安全规则。",[10,420,421,423,424,427,428,202,430,380,432,434],{},[41,422,170],{}," 只处理异常字段，也就是 ",[41,425,426],{},"status"," 为 ",[41,429,359],{},[41,431,376],{},[41,433,379],{}," 的字段。它会把字段值、单位和异常方向组合起来，再从知识库中取出风险说明、建议和证据。",[10,436,437],{},"另外它有一个很重要的安全保护：",[33,439,442],{"className":440,"code":441,"language":38,"meta":39},[36],"本系统仅用于报告整理和健康教育，不替代医生诊断。\n涉及危急值、明显不适、孕产妇、儿童或慢病患者时应及时就医。\n",[41,443,441],{"__ignoreMap":39},[10,445,446],{},"代码里还会过滤一些高风险表达，比如“确诊”“一定是”“无需就医”。如果建议里没有“医生”或“复查”，也会追加“建议结合临床情况咨询医生”。这体现了医疗 AI 项目最基本的安全边界：可以做信息整理和健康教育，但不能越界替代诊断。",[17,448,450],{"id":449},"cli-与-api","CLI 与 API",[10,452,453],{},"项目提供了两个集成入口。",[455,456,458],"h3",{"id":457},"cli","CLI",[10,460,461,462,465],{},"命令行入口在 ",[41,463,464],{},"cli.py","，可以直接运行示例报告：",[33,467,471],{"className":468,"code":469,"language":470,"meta":39,"style":39},"language-bash shiki shiki-themes github-dark-dimmed github-light","python -m meddococr_interpreter.cli examples\u002Flab_report.txt --document-type lab_report --age 45 --sex 男\n","bash",[41,472,473],{"__ignoreMap":39},[474,475,478,482,486,490,493,496,499,502,505,508],"span",{"class":476,"line":477},"line",1,[474,479,481],{"class":480},"sqRhv","python",[474,483,485],{"class":484},"swcJU"," -m",[474,487,489],{"class":488},"sXfbr"," meddococr_interpreter.cli",[474,491,492],{"class":488}," examples\u002Flab_report.txt",[474,494,495],{"class":484}," --document-type",[474,497,498],{"class":488}," lab_report",[474,500,501],{"class":484}," --age",[474,503,504],{"class":484}," 45",[474,506,507],{"class":484}," --sex",[474,509,510],{"class":488}," 男\n",[10,512,513],{},"也可以把结果写到 JSON 文件：",[33,515,517],{"className":468,"code":516,"language":470,"meta":39,"style":39},"python -m meddococr_interpreter.cli examples\u002Flab_report.txt --output outputs\u002Fsample.json\n",[41,518,519],{"__ignoreMap":39},[474,520,521,523,525,527,529,532],{"class":476,"line":477},[474,522,481],{"class":480},[474,524,485],{"class":484},[474,526,489],{"class":488},[474,528,492],{"class":488},[474,530,531],{"class":484}," --output",[474,533,534],{"class":488}," outputs\u002Fsample.json\n",[10,536,537,538,541],{},"CLI 支持 ",[41,539,540],{},"--raw-text","，因此可以直接把一段 OCR 文本作为输入，这对调试字段抽取和解读规则很方便。",[455,543,545],{"id":544},"fastapi","FastAPI",[10,547,548,549,552],{},"API 入口在 ",[41,550,551],{},"api\u002Fserver.py","，提供两个接口：",[33,554,557],{"className":555,"code":556,"language":38,"meta":39},[36],"GET  \u002Fhealth\nPOST \u002Finterpret\n",[41,558,556],{"__ignoreMap":39},[10,560,561],{},"启动方式：",[33,563,565],{"className":468,"code":564,"language":470,"meta":39,"style":39},"uvicorn meddococr_interpreter.api.server:app --reload\n",[41,566,567],{"__ignoreMap":39},[474,568,569,572,575],{"class":476,"line":477},[474,570,571],{"class":480},"uvicorn",[474,573,574],{"class":488}," meddococr_interpreter.api.server:app",[474,576,577],{"class":484}," --reload\n",[10,579,580,583,584,587,588,590],{},[41,581,582],{},"POST \u002Finterpret"," 的请求体就是 ",[41,585,586],{},"PipelineInput","，响应体就是 ",[41,589,113],{},"。这意味着 CLI、测试和 Web API 共用同一套核心流水线，不会出现多套逻辑不一致的问题。",[17,592,593],{"id":593},"配置与依赖设计",[10,595,596,599],{},[41,597,598],{},"pyproject.toml"," 把依赖分成了几组：",[33,601,604],{"className":602,"code":603,"language":38,"meta":39},[36],"基础依赖：pydantic, pyyaml\napi：fastapi, uvicorn, python-multipart\nocr：paddleocr, pillow\ntraining：torch, transformers, datasets, trl\ndev：pytest, ruff\n",[41,605,603],{"__ignoreMap":39},[10,607,608,609,612,613,615,616,196],{},"这种 optional dependencies 的设计比较合理：如果只是跑文本 demo 和测试，不需要安装庞大的 OCR、训练和服务依赖；如果要部署 API，再安装 ",[41,610,611],{},"[api]","；如果要接图片 OCR，再安装 ",[41,614,222],{},"；如果要做训练实验，再安装 ",[41,617,618],{},"[training]",[10,620,621,622,319],{},"默认配置在 ",[41,623,624],{},"configs\u002Fdefault.yaml",[33,626,630],{"className":627,"code":628,"language":629,"meta":39,"style":39},"language-yaml shiki shiki-themes github-dark-dimmed github-light","ocr_engine: auto\nlayout_engine: heuristic\nvlm_model: Qwen\u002FQwen2.5-VL-7B-Instruct\nrag_kb_path: data\u002Fkb\u002Fmedical_rules.json\nmin_ocr_score: 0.3\nenable_safety_guard: true\n","yaml",[41,631,632,645,656,667,678,688],{"__ignoreMap":39},[474,633,634,638,642],{"class":476,"line":477},[474,635,637],{"class":636},"shb1k","ocr_engine",[474,639,641],{"class":640},"ssh_m",": ",[474,643,644],{"class":488},"auto\n",[474,646,648,651,653],{"class":476,"line":647},2,[474,649,650],{"class":636},"layout_engine",[474,652,641],{"class":640},[474,654,655],{"class":488},"heuristic\n",[474,657,659,662,664],{"class":476,"line":658},3,[474,660,661],{"class":636},"vlm_model",[474,663,641],{"class":640},[474,665,666],{"class":488},"Qwen\u002FQwen2.5-VL-7B-Instruct\n",[474,668,670,673,675],{"class":476,"line":669},4,[474,671,672],{"class":636},"rag_kb_path",[474,674,641],{"class":640},[474,676,677],{"class":488},"data\u002Fkb\u002Fmedical_rules.json\n",[474,679,681,683,685],{"class":476,"line":680},5,[474,682,229],{"class":636},[474,684,641],{"class":640},[474,686,687],{"class":484},"0.3\n",[474,689,691,694,696],{"class":476,"line":690},6,[474,692,693],{"class":636},"enable_safety_guard",[474,695,641],{"class":640},[474,697,698],{"class":484},"true\n",[10,700,701,702,704,705,707],{},"配置项里已经出现了 ",[41,703,661],{}," 和 ",[41,706,650],{},"，说明这个项目当前虽然是轻量规则原型，但设计目标是能逐步迁移到真实多模态模型。",[17,709,710],{"id":710},"合成数据与训练路线",[10,712,713,716],{},[41,714,715],{},"synthetic\u002Fgenerator.py"," 可以随机生成检验报告文本，包括医院、姓名、性别、年龄、白细胞、血红蛋白、C 反应蛋白等字段，并转成 VLM SFT messages 格式。",[10,718,719],{},"这说明项目并不只考虑推理链路，也在为训练数据准备做铺垫。一个更完整的扩展方向是：",[33,721,724],{"className":722,"code":723,"language":38,"meta":39},[36],"模板渲染\n  -> 表格线、字体、印章、噪声、模糊、透视变换\n  -> 自动生成 OCR bbox \u002F 表格 cell \u002F key field 标签\n  -> 构造 VLM SFT JSONL\n",[41,725,723],{"__ignoreMap":39},[10,727,728,731],{},[41,729,730],{},"training\u002Frecipes.py"," 给出了四阶段训练设想：",[733,734,735,751],"table",{},[736,737,738],"thead",{},[739,740,741,745,748],"tr",{},[742,743,744],"th",{},"阶段",[742,746,747],{},"目标",[742,749,750],{},"指标",[752,753,754,766,777,788],"tbody",{},[739,755,756,760,763],{},[757,758,759],"td",{},"OCR continual pretrain",[757,761,762],{},"在医疗文档上继续训练 OCR\u002FVLM 能力",[757,764,765],{},"CER、layout block F1",[739,767,768,771,774],{},[757,769,770],{},"Document QA SFT",[757,772,773],{},"做字段抽取、表格问答、报告问答监督微调",[757,775,776],{},"field F1、TEDS、QA accuracy",[739,778,779,782,785],{},[757,780,781],{},"Preference DPO",[757,783,784],{},"偏好更安全、更可追溯的解读",[757,786,787],{},"safety win rate、refusal correctness",[739,789,790,793,796],{},[757,791,792],{},"Rule reward RL",[757,794,795],{},"优化格式合法性、证据覆盖和医学安全",[757,797,798],{},"format pass rate、medical advice safety",[10,800,801],{},"这个路线很适合作为项目答辩或面试时的技术叙事：当前仓库是可运行原型，真实训练需要数据、算力和标注闭环，但工程接口已经预留好了。",[17,803,804],{"id":804},"测试覆盖",[10,806,807,810],{},[41,808,809],{},"tests\u002Ftest_pipeline.py"," 是一个端到端测试。它构造了一段检验报告文本：",[33,812,815],{"className":813,"code":814,"language":38,"meta":39},[36],"项目 | 结果 | 单位 | 参考范围\n白细胞 | 12.8 | 10^9\u002FL | 3.5-9.5\n血红蛋白 | 118 | g\u002FL | 130-175\nC反应蛋白 | 35.6 | mg\u002FL | 0-10\n",[41,816,814],{"__ignoreMap":39},[10,818,819],{},"测试会验证三件事：",[74,821,822,825,828],{},[77,823,824],{},"OCR token 数量足够。",[77,826,827],{},"能抽取出白细胞、血红蛋白、C 反应蛋白三个关键字段。",[77,829,830],{},"能对这三个异常字段产生解读，并返回安全提示。",[10,832,833],{},"虽然测试数量不多，但它覆盖了项目最核心的主链路：文本输入、表格恢复、字段抽取、异常判断、知识库解释和安全提示。",[17,835,836],{"id":836},"项目亮点",[10,838,839],{},"我认为这个项目比较好的地方有四点。",[10,841,842,843,846],{},"第一，",[80,844,845],{},"工程边界清晰","。每个模块都有明确职责，主流水线只做编排，后续接真实模型时替换成本较低。",[10,848,849,850,853],{},"第二，",[80,851,852],{},"类型结构完整","。通过 Pydantic 把中间结果和最终结果都结构化，方便 API 返回、测试断言、前端展示和错误排查。",[10,855,856,857,860],{},"第三，",[80,858,859],{},"安全意识明确","。医疗 AI 最怕直接给诊断结论，这个项目在规则层和输出层都强调“健康教育，不替代医生诊断”。",[10,862,863,864,867],{},"第四，",[80,865,866],{},"原型可运行","。文本 fallback、CLI、FastAPI、Pytest 都已经具备，不是只停留在架构图，而是能跑出结构化结果。",[17,869,870],{"id":870},"当前局限",[10,872,873],{},"作为原型，它也有明显限制：",[74,875,876,882,888,894,900],{},[77,877,878,881],{},[80,879,880],{},"OCR 和版面识别仍偏 demo","：真实扫描件会有旋转、阴影、低清晰度、表格线断裂、多栏布局等问题，当前规则很难覆盖。",[77,883,884,887],{},[80,885,886],{},"表格恢复比较简单","：只处理分隔符文本，无法处理复杂合并单元格、跨页表格、无框线表格和错行 OCR。",[77,889,890,893],{},[80,891,892],{},"医学知识库很小","：当前规则只覆盖少量常见指标，且没有分年龄、性别、孕产、儿童等医学参考范围差异。",[77,895,896,899],{},[80,897,898],{},"缺少证据引用闭环","：输出中有 evidence 字段，但还不是严格的知识库引用、指南引用或原文 span 引用。",[77,901,902,905],{},[80,903,904],{},"没有真实评测集","：缺少真实医疗票据\u002F报告上的 OCR、字段抽取、表格恢复和安全解读 benchmark。",[10,907,908],{},"这些限制不影响它作为原型的价值，但如果要做生产级系统，必须重点补齐。",[17,910,911],{"id":911},"后续优化方向",[10,913,914],{},"如果继续完善，我会按下面顺序迭代：",[74,916,917,923,929,935,941,947],{},[77,918,919,922],{},[80,920,921],{},"增强输入层","：支持 PDF、多页图片、拍照件自动纠偏、图像增强和方向检测。",[77,924,925,928],{},[80,926,927],{},"升级版面模型","：接入 LayoutLMv3、DocLayout-YOLO 或 VLM，把规则版 layout detector 替换成模型推理。",[77,930,931,934],{},[80,932,933],{},"改进表格恢复","：增加 cell bbox、reading order、跨行跨列合并和 HTML 表格输出，并用 TEDS 评测。",[77,936,937,940],{},[80,938,939],{},"扩展医学知识库","：引入更系统的指标规则、参考范围、危急值规则和人群差异规则。",[77,942,943,946],{},[80,944,945],{},"加强可追溯性","：让每个字段和解读都能回链到 OCR token、表格 cell、原始 bbox 和知识库来源。",[77,948,949,952],{},[80,950,951],{},"建立评测闭环","：分别评测 OCR CER、字段 F1、表格 TEDS、解读安全率和 JSON schema 通过率。",[17,954,955],{"id":955},"总结",[10,957,958],{},"MedDocOCR-Interpreter 是一个很适合作为医疗多模态工程项目的原型：它没有试图一上来就“端到端大模型解决一切”，而是把医疗文档处理拆成 OCR、版面、表格、字段、知识库和安全解读几个阶段，每个阶段都有清晰输入输出，也都预留了从规则实现升级到模型实现的空间。",[10,960,961],{},"从学习和展示角度看，这个项目可以体现三种能力：",[74,963,964,970,976],{},[77,965,966,969],{},[80,967,968],{},"文档智能工程能力","：知道 OCR、layout、table、field extraction 怎么串成可运行系统。",[77,971,972,975],{},[80,973,974],{},"医疗 AI 安全意识","：知道报告解读必须有边界、证据和安全提示。",[77,977,978,981],{},[80,979,980],{},"多模态训练规划能力","：知道如何从可运行原型进一步走向 VLM SFT、偏好优化和规则奖励训练。",[10,983,984],{},"如果要在面试中介绍这个项目，可以这样总结：",[24,986,987],{},[10,988,989],{},"我实现了一个医疗文档 OCR 到报告解读的端到端原型。系统支持图片或文本输入，先通过 PaddleOCR adapter 得到 OCR token，再进行版面识别、表格恢复和字段归一化，把不同格式的检验指标统一成结构化字段。对于异常字段，系统会检索医学规则知识库，输出风险说明、复查建议、证据和安全提示，并通过规则避免过度诊断。工程上使用 Pydantic 保证 schema 一致，用 FastAPI 和 CLI 提供集成入口，用 Pytest 覆盖主链路，同时预留了 Qwen2.5-VL、LayoutLMv3、SFT、DPO 和规则奖励 RL 的升级路线。",[991,992,993],"style",{},"html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .shb1k, html code.shiki .shb1k{--shiki-default:#8DDB8C;--shiki-light:#22863A}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}",{"title":39,"searchDepth":647,"depth":658,"links":995},[996,997,998,999,1000,1001,1002,1003,1004,1005,1009,1010,1011,1012,1013,1014,1015],{"id":19,"depth":647,"text":19},{"id":56,"depth":647,"text":56},{"id":92,"depth":647,"text":92},{"id":117,"depth":647,"text":118},{"id":174,"depth":647,"text":175},{"id":240,"depth":647,"text":241},{"id":266,"depth":647,"text":266},{"id":309,"depth":647,"text":309},{"id":391,"depth":647,"text":391},{"id":449,"depth":647,"text":450,"children":1006},[1007,1008],{"id":457,"depth":658,"text":458},{"id":544,"depth":658,"text":545},{"id":593,"depth":647,"text":593},{"id":710,"depth":647,"text":710},{"id":804,"depth":647,"text":804},{"id":836,"depth":647,"text":836},{"id":870,"depth":647,"text":870},{"id":911,"depth":647,"text":911},{"id":955,"depth":647,"text":955},[1017],"技术","2026-05-22 10:30:00",false,"md",{},true,"\u002Fposts\u002Fmeddococr-interpreter-source-analysis",{"title":5,"description":12},"posts\u002Fmeddococr-interpreter-source-analysis",[1027,1028,1029,1030,1031,1032],"OCR","多模态","医疗 AI","RAG","Python","源码分析","hTD1D4qdxeH8yQTkSU7T1XjCOKJ0T4gKNDvbti55NJU",[1035,1047,1060,1067,1080,1090,1100,1110,1114,1123,1133,1145,1158,1170,1179,1189,1202,1213,1223,1231,1242,1248,1254,1260,1268,1277,1285,1290,1298,1306],{"slug":1036,"path":1037,"title":1038,"date":1039,"tags":1040,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1046},"multimodal-rag-from-scratch","\u002Fposts\u002Fmultimodal-rag-from-scratch","从零实现多模态 RAG：BM25、Dense 检索、RRF 融合、MMR 重排全部手写","2026-06-30 18:00:00",[1030,1028,1041,1042,1043,1044,1045],"AI Infra","BM25","向量检索","混合检索","实习求职",9,{"slug":1048,"path":1049,"title":1050,"date":1051,"tags":1052,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1059},"mini-llm-engine-deep-dive","\u002Fposts\u002Fmini-llm-engine-deep-dive","讲透 mini-llm-engine：从显存碎片到六大推理优化","2026-06-30 14:00:00",[1053,1041,1054,1055,1056,1057,1058,1045],"LLM","vLLM","PagedAttention","KV Cache","推理优化","投机解码",11,{"slug":1061,"path":1062,"title":1063,"date":1064,"tags":1065,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1066},"mini-llm-engine-from-scratch","\u002Fposts\u002Fmini-llm-engine-from-scratch","从零实现 LLM 推理引擎：深挖 vLLM 的六大核心优化","2026-06-30 10:00:00",[1053,1041,1054,1055,1057,1058,1045],10,{"slug":1068,"path":1069,"title":1070,"date":1071,"tags":1072,"description":1078,"draft":1019,"hidden":1019,"published":1022,"readingTime":1079},"bytedance-recommendation-architecture-intern-interview","\u002Fposts\u002Fbytedance-recommendation-architecture-intern-interview","字节推荐架构实习生 Data 面试准备：推荐系统、实时特征与高并发八股","2026-06-09",[1073,1074,1075,1076,1077],"面试","推荐系统","后端架构","实时计算","字节跳动","面向字节跳动推荐架构团队实习岗位的八股准备清单，覆盖推荐系统链路、实时数据、特征服务、高并发后端、分布式系统与项目包装。",18,{"slug":1081,"path":1082,"title":1083,"date":1071,"tags":1084,"description":1088,"draft":1019,"hidden":1019,"published":1022,"readingTime":1089},"high-concurrency-rate-limiting-algorithms","\u002Fposts\u002Fhigh-concurrency-rate-limiting-algorithms","高并发限流算法：固定窗口、滑动窗口、漏桶与令牌桶",[1085,1086,1075,1087,1073],"高并发","限流","系统设计","面试和工程都常见的限流算法总结，讲清楚固定窗口、滑动窗口、漏桶、令牌桶、并发数限制以及分布式限流如何落地。",12,{"slug":1091,"path":1092,"title":1093,"date":1071,"tags":1094,"description":1098,"draft":1019,"hidden":1019,"published":1022,"readingTime":1099},"kafka-producer-broker-consumer","\u002Fposts\u002Fkafka-producer-broker-consumer","Kafka 入门：生产者、Broker、消费者和“消费”到底是什么意思",[1095,1096,1097,1073,1074],"Kafka","消息队列","后端","用推荐系统里的用户行为日志为例，讲清楚 Kafka 的作用、Producer、Broker、Consumer、Topic、Partition、Offset 和消费语义。",8,{"slug":1101,"path":1102,"title":1103,"date":1071,"tags":1104,"description":1109,"draft":1019,"hidden":1019,"published":1022,"readingTime":690},"leetcode-lru-merge-k-reverse-list","\u002Fposts\u002Fleetcode-lru-merge-k-reverse-list","链表与缓存高频题：LRU Cache、合并 K 个有序链表、反转链表",[1105,1106,1107,1108,1073],"算法","链表","LRU","LeetCode","面试高频算法题速记，整理 LRU Cache、合并 K 个有序链表、反转链表的核心思路、复杂度和 C++ 代码。",{"slug":1111,"path":1023,"title":5,"date":1018,"tags":1112,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1113},"meddococr-interpreter-source-analysis",[1027,1028,1029,1030,1031,1032],16,{"slug":1115,"path":1116,"title":1117,"date":1118,"tags":1119,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1066},"cet6-writing-model-essays","\u002Fposts\u002Fcet6-writing-model-essays","六级写作范文背诵包：10 个高频话题","2026-05-20 09:00:00",[1120,1121,1122],"English","CET6","Writing",{"slug":1124,"path":1125,"title":1126,"date":1127,"tags":1128,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1132},"claude-code-context-management","\u002Fposts\u002Fclaude-code-context-management","Claude Code 上下文管理机制：从 Microcompact 到 Auto Compact","2026-05-19 10:00:00",[1129,1130,1053,1041,1131],"Claude Code","Agent","上下文工程",27,{"slug":1134,"path":1135,"title":1136,"date":1137,"tags":1138,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1144},"nanotron-llm-pretraining-framework-analysis","\u002Fposts\u002Fnanotron-llm-pretraining-framework-analysis","Nanotron 项目详解：Hugging Face 的大模型预训练框架怎么做分布式训练","2026-05-10 12:10:00",[1053,1139,1140,1141,1142,1041,1143],"大模型训练","分布式训练","Nanotron","Hugging Face","PyTorch",19,{"slug":1146,"path":1147,"title":1148,"date":1149,"tags":1150,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1157},"gofoundry-go-backend-foundation-framework","\u002Fposts\u002Fgofoundry-go-backend-foundation-framework","GoFoundry 项目详解：基于 Go 的后端基础框架套件设计","2026-05-10 11:20:00",[1151,1152,1153,1154,1155,1096,1156],"Go","后端框架","ORM","分布式缓存","分布式锁","项目架构",25,{"slug":1159,"path":1160,"title":1161,"date":1162,"tags":1163,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1132},"cloudvault-go-cloud-storage-system","\u002Fposts\u002Fcloudvault-go-cloud-storage-system","CloudVault 项目详解：基于 Go 的云端存储与网盘系统架构设计","2026-05-10 10:30:00",[1151,1164,1165,1166,1156,1167,1168,1169],"云存储","网盘系统","分布式系统","Redis","RabbitMQ","Elasticsearch",{"slug":1171,"path":1172,"title":1173,"date":1174,"tags":1175,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1178},"openclaw-source-code-analysis","\u002Fposts\u002Fopenclaw-source-code-analysis","OpenClaw 源码导读：个人 AI 助手的网关、通道、插件与运行时架构","2026-05-08 16:30:00",[1176,1130,1041,1177,1032],"OpenClaw","TypeScript",20,{"slug":1180,"path":1181,"title":1182,"date":1183,"tags":1184,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1066},"flow-matching-generative-models","\u002Fposts\u002Fflow-matching-generative-models","Flow Matching：从噪声到数据的连续流生成模型","2026-05-07 00:00:00",[1185,1186,1187,1188],"生成模型","Diffusion","Flow Matching","深度学习",{"slug":1190,"path":1191,"title":1192,"date":1193,"tags":1194,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1201},"database-ai-week4","\u002Fposts\u002Fdatabase-ai-week4","Week 4：数据库速成——从 Storage、Index、Query Optimization 到 Vector DB 与 RAG","2026-05-05 12:00:00",[1195,1196,1197,1030,1198,1199,1200],"数据库","CMU 15-445","Vector DB","LLM Memory","Query Optimization","Caching",15,{"slug":1203,"path":1204,"title":1205,"date":1206,"tags":1207,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1113},"distributed-systems-week3","\u002Fposts\u002Fdistributed-systems-week3","Week 3：分布式系统速成——MapReduce、Raft、容错与 Distributed KV Store","2026-05-05 11:00:00",[1166,1208,1209,1210,1211,1212,1130],"MIT 6.824","MapReduce","Raft","KV Store","Ray",{"slug":1214,"path":1215,"title":1216,"date":1217,"tags":1218,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1201},"gpu-inference-acceleration-week2","\u002Fposts\u002Fgpu-inference-acceleration-week2","Week 2：GPU 与推理加速——从 Kernel、算子融合到 LLM Serving","2026-05-05 10:00:00",[1188,1219,1220,1053,1221,1054,1222],"GPU","推理加速","CMU 10-414","TensorRT",{"slug":1224,"path":1225,"title":1226,"date":1227,"tags":1228,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1079},"dl-framework-autograd-mini","\u002Fposts\u002Fdl-framework-autograd-mini","Week 1：DL 框架与 Autograd——从计算图、反向传播到 Mini Autograd 实现","2026-05-05 09:00:00",[1188,1229,1143,1221,1230],"Autograd","Mini Framework",{"slug":1232,"path":1233,"title":1234,"date":1235,"tags":1236,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1089},"lock-free-concurrency-notes","\u002Fposts\u002Flock-free-concurrency-notes","无锁并发入门：从 CAS 到 Atomic Ring Buffer","2026-04-25",[1237,1238,1239,1240,1241],"C++","并发","无锁编程","性能优化","量化开发",{"slug":1243,"path":1244,"title":1245,"date":1246,"tags":1247,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":680},"agent-memory","\u002Fposts\u002Fagent-memory","Agent 对话记忆化：从原理到实现","2026-04-24",[1053,1130,1030,1073],{"slug":1249,"path":1250,"title":1251,"date":1246,"tags":1252,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":690},"llm-context-compression","\u002Fposts\u002Fllm-context-compression","LLM 上下文五层压缩机制详解",[1053,1130,1253,1073],"上下文压缩",{"slug":1255,"path":1256,"title":1257,"date":1258,"tags":1259,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1046},"cpp-concurrency-basics","\u002Fposts\u002Fcpp-concurrency-basics","C++ 并发编程入门：从数据竞争到线程池","2026-04-15",[1237,1238,1073,1241],{"slug":1261,"path":1262,"title":1263,"date":1264,"tags":1265,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":477},"travel-in-shenzhen","\u002Fposts\u002Ftravel-in-shenzhen","XCPC 深圳游记","2026-04-13",[1266,1237,1267],"XCPC","比赛",{"slug":1269,"path":1270,"title":1271,"date":1272,"tags":1273,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1276},"backend-stack-deep-dive","\u002Fposts\u002Fbackend-stack-deep-dive","后端五件套：FastAPI \u002F Node.js \u002F SQLAlchemy async \u002F PostgreSQL \u002F Docker 面试速通","2026-04-07",[1097,545,1274,1275,1073],"PostgreSQL","Docker",7,{"slug":1278,"path":1279,"title":1280,"date":1281,"tags":1282,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":690},"deepscientist-tech-stack","\u002Fposts\u002Fdeepscientist-tech-stack","DeepScientist 技术栈全解析：一个 AI 科研平台的架构设计","2026-04-06",[1283,545,1284,1274,1073],"全栈","Next.js",{"slug":1286,"path":1287,"title":1288,"date":1281,"tags":1289,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":1099},"minicode-source-analysis","\u002Fposts\u002Fminicode-source-analysis","MiniCode 源码解析：用 5000 行 TypeScript 实现一个 AI 编程助手",[1177,458,1053,1032,1073],{"slug":1291,"path":1292,"title":1293,"date":1281,"tags":1294,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":680},"nova-theme-implementation","\u002Fposts\u002Fnova-theme-implementation","我是怎么从零实现 Nova 主题的",[1295,1296,1297],"Hexo","前端","开源",{"slug":1299,"path":1300,"title":1301,"date":1302,"tags":1303,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":647},"git-cheatsheet","\u002Fposts\u002Fgit-cheatsheet","Git 常用操作备忘","2026-04-05 14:00:00",[1304,1305],"Git","工具",{"slug":1307,"path":1308,"title":1309,"date":1310,"tags":1311,"description":39,"draft":1019,"hidden":1019,"published":1022,"readingTime":658},"github-actions-intro","\u002Fposts\u002Fgithub-actions-intro","GitHub Actions 入门：自动化你的工作流","2026-04-04 09:00:00",[1312,1313,1314],"GitHub Actions","CI\u002FCD","自动化",1782796012451]