# 中国药典 AI 解析系统 — 文字解析问题分析报告 > **版本**: v1.0 > **日期**: 2026-07-17 > **范围**: data-pipeline/ 全部解析模块 + backend-python/app/rag/ 检索链路 > **主线版本**: 2025 年版药典(主要解析目标) > **补充版本**: 2020 年版药典(历史数据兼容) --- ## 一、项目解析架构总览 数据源 解析层 入库层 ------------------------------------------------------------- 药典 PDF (2020/2025) -> pharmacopoeia.py -> ingest.py 药典 DOCX (2025) -> docx_ingest.py -> docx_ingest.py (内置入库) 维基百科 -> wiki_spider.py -> import_from_openclaw.py NMPA 公开数据 -> nmpa_spider.py -> ingest.py 考试大纲 -> exam_outline.py -> knowledge_chunker.py **当前状态**: 仅 DOCX 解析链路可用,PDF 解析器为空壳,考试模块未实现。 --- ## 二、2025 版药典解析(主线) ### 2.1 DOCX 解析模块 — docx_ingest.py **文件位置**: data-pipeline/docx_ingest.py **当前状态**: 可用,但存在多处健壮性问题 #### 2.1.1 解析流程 DOCX 文件 | 文件名 -> 药品名(display_name) | 段落遍历(doc.paragraphs) | Heading 1 -> 跳过(与文件名重复) 拼音行 -> 提取 pinyin 短行(<=10字符)-> section 标题匹配 【xxx】格式 -> 行内 section 标题 其他 -> 归入当前 section | 凡例/纲要 -> 按编号拆分(一、二、三...) | 输出结构化 dict #### 2.1.2 存在的问题 | 编号 | 问题 | 严重程度 | 说明 | |------|------|---------|------| | D-01 | **表格内容完全丢失** | 严重 | 只遍历 doc.paragraphs,未处理 doc.tables。药典中含量测定限度表、鉴别反应表等关键信息以表格形式存在 | | D-02 | **section 标题长度硬编码** | 中等 | len(text) <= 10 阈值过小,较长标题会被漏掉 | | D-03 | **拼音识别可能误判** | 中等 | is_pinyin_line() 可能将英文药品名或化学分子式误判为拼音 | | D-04 | **凡例/纲要拆分逻辑脆弱** | 中等 | 中文数字正则 {1,2} 无法匹配二十一等复合数字;模式优先级可能导致错误匹配 | | D-05 | **文件名依赖** | 轻微 | 药品名直接取文件名,不规范的文件名(含空格、编号前缀)会导致解析错误 | | D-06 | **旧版去重逻辑风险** | 中等 | dedup_old_versions() 将所有非 2025 版同名药品标记为 inactive,可能误删 2020 版独有的药品信息 | #### 2.1.3 section 标题映射表 SECTION_HEADERS = OrderedDict([ (处方, 处方), (制法, 制法), (性状, 性状), (鉴别, 鉴别), (检查, 检查), (浸出物, 浸出物), (含量测定, 含量测定), (含量, 含量测定), (功能与主治, 功能主治), (功能, 功能主治), (主治, 功能主治), (用法与用量, 用法用量), (用法, 用法用量), (用量, 用法用量), (注意, 注意事项), (注意事项, 注意事项), (规格, 规格), (贮藏, 贮藏), (类别, 类别), (制剂, 制剂), (附注, 附注), ]) **缺失的映射**: - 包装(部分条目) - 有效期(部分条目) - 核准日期(部分条目) - 修订日期(部分条目) #### 2.1.4 2025 版四部目录结构 根据 extract_catalog.py 提取的目录: 中国药典 2025 年版 四部 |-- 凡例 |-- 通则 | |-- 制剂通则 | |-- 通用检测方法 | +-- 指导原则 |-- 药用辅料 +-- 通用技术要求 --- ### 2.2 PDF 解析模块 — pharmacopoeia.py **文件位置**: data-pipeline/crawlers/pharmacopoeia.py **当前状态**: 完全未实现 #### 2.2.1 设计目标(注释中描述) 1. PyMuPDF 提取文本 + 坐标 2. 基于字体大小/位置识别标题层级 3. 正则匹配药品条目边界 4. 按条目切分 -> 结构化输出 #### 2.2.2 未实现的方法 | 方法 | 预期功能 | 当前返回 | |------|---------|---------| | parse() | 完整解析流程 | [] | | _extract_text_with_position() | 提取文本块及坐标 | [] | | _detect_entry_boundaries() | 检测条目边界 | [] | | _parse_entry_sections() | 解析各栏目 | 空字典 | #### 2.2.3 2020 版 PDF 解析的特殊挑战 | 挑战 | 说明 | |------|------| | **排版差异** | 2020 版 PDF 与 2025 版排版规则不同,字体大小、缩进量需要分别适配 | | **双栏布局** | 部分页面采用双栏布局,文本提取时可能出现跨栏混排 | | **化学结构式** | 含量测定等章节包含化学结构式图片,纯文本提取会丢失 | | **页码不连续** | 前言、目录、凡例的页码与正文不连续,需要分别处理 | --- ### 2.3 PDF 目录提取 — extract_catalog.py **文件位置**: data-pipeline/crawlers/extract_catalog.py **当前状态**: 部分可用 #### 2.3.1 存在的问题 | 编号 | 问题 | 说明 | |------|------|------| | C-01 | **层级判断靠坐标硬编码** | x < 50 为部级、40 <= x < 100 为章级、x >= 80 为节级,不同 PDF 版本排版不同时会失效 | | C-02 | **层级范围重叠** | 章级 40-100 与节级 >=80 重叠,x=90 同时满足两个条件 | | C-03 | **目录页定位不稳定** | 依赖目录关键词匹配,如果目录页格式不同会定位错误 | --- ## 三、2020 版药典解析(补充) ### 3.1 数据来源 | 来源 | 格式 | 状态 | |------|------|------| | data/pharmacopoeia_2020_volume1_toc.pdf | PDF | 无法解析(解析器未实现) | | data-pipeline/data/catalog_volume1.json | JSON | 已提取目录结构 | | data-pipeline/data/catalog_volume2.json | JSON | 已提取目录结构 | | data-pipeline/data/catalog_volume3.json | JSON | 已提取目录结构 | | data-pipeline/data/catalog_volume4.json | JSON | 已提取目录结构 | ### 3.2 2020 版与 2025 版的差异 | 差异项 | 2020 版 | 2025 版 | |--------|---------|---------| | **主要数据格式** | PDF | DOCX | | **药品品种数量** | 约 5911 种 | 新增 + 修订 | | **凡例内容** | 2020 版凡例 | 2025 版凡例(有更新) | | **通则编号** | 旧编号体系 | 可能有调整 | | **检测方法** | 2020 版方法 | 新增/修订方法 | ### 3.3 2020 版数据去重策略 当前 docx_ingest.py 中的去重逻辑: UPDATE drugs SET is_active = FALSE WHERE is_active = TRUE AND source_version != 2025年版 AND name IN ( SELECT name FROM drugs WHERE source_version = 2025年版 AND is_active = TRUE ) **问题**: - 同名药品在 2020 版和 2025 版可能有不同的检查标准,直接标记旧版为 inactive 会丢失历史标准信息 - 建议改为版本共存模式,查询时优先返回 2025 版,但保留 2020 版可查 --- ## 四、文本清洗模块 — cleaner.py **文件位置**: data-pipeline/processors/cleaner.py **当前状态**: 功能薄弱 ### 4.1 清洗规则冲突 self.replacements = [ (r(? 所有单换行被删除 -> **规则 3、4 永远无法匹配** - 正确顺序应该是:先处理断行连接(规则 3、4),再移除不当换行(规则 1) ### 4.2 OCR 纠错字典不足 ocr_fixes = { 鍜?: 鍟?, 鍞?: 鍞?, 鑼剁⒈: 鑼剁⒈, } **问题**: - 仅 3 条规则,远远不够覆盖药典 PDF 的 OCR 错误 - 部分映射本身看起来是编码问题产生的乱码 - 缺少药学专业术语的纠错(如苷与甙、馏与溜等) ### 4.3 缺失的清洗能力 | 缺失项 | 说明 | |--------|------| | 全角/半角规范化 | 中文逗号 vs 英文逗号、中文句号 vs 英文句号、中文括号 vs 英文括号 | | 化学分子式处理 | 上下标丢失问题(如 H2O 应为 H 下标 2 O) | | 中药拉丁名规范化 | 拉丁学名斜体标记丢失 | | 特殊符号处理 | 摄氏度、微克、毫升等单位符号的规范化 | | 段落合并逻辑 | 跨页段落的智能合并 | --- ## 五、智能切片模块 — chunker.py **文件位置**: data-pipeline/processors/chunker.py **当前状态**: 部分实现 ### 5.1 已实现:药品条目切片 def chunk_drug_entry(self, drug_entry: dict) -> list[Chunk]: # 按 section 切分,保持每个 section 完整 # 若单个 section 过长,再按段落细分 **问题**: - chunk_overlap 参数未使用,切片之间没有重叠 - 长 section 切分后,最后一段可能低于 min_chunk_size 但仍被加入 ### 5.2 未实现:凡例/通则切片 def chunk_regulation(self, text: str, metadata: dict) -> list[Chunk]: return [] # 空实现 ### 5.3 未实现:考试知识点切片 def chunk_exam_knowledge(self, knowledge_point: dict) -> list[Chunk]: return [] # 空实现 ### 5.4 切片内容格式问题 content=f銆恵drug_name} - {section_key}銆慭n{section_text} - 銆恵 和 銆慭n 是编码乱码(应为 【 和 】换行) - 说明 f-string 中的中文括号在某个环节被损坏 --- ## 六、考试知识点切片 — knowledge_chunker.py **文件位置**: data-pipeline/processors/knowledge_chunker.py **当前状态**: 完全未实现 class KnowledgeChunker: def process(self, knowledge_point: dict) -> list[dict]: return [] def build_chapter_tree(self, knowledge_points: list[dict]) -> dict: return {} **影响**: 考试模块的数据管道完全断裂,无法支持: - 章节化知识点学习 - AI 自动出题 - 刷题/错题集/模拟考试 --- ## 七、向量检索模块 — retriever.py **文件位置**: backend-python/app/rag/retriever.py **当前状态**: 部分实现 ### 7.1 意图分类问题 def classify_intent(query: str) -> str: # 基于关键词匹配的简单分类 | 编号 | 问题 | 说明 | |------|------|------| | I-01 | **关键词冲突** | 过敏同时出现在 safety_sections 和 symptom_keywords 中,优先级判断导致错误分类 | | I-02 | **无否定语义处理** | 这不是感冒会匹配到感冒关键词 | | I-03 | **无置信度判断** | 所有查询都被强制分到某个类别,没有不确定的兜底 | | I-04 | **无上下文理解** | 无法理解多轮对话中的指代关系 | ### 7.2 BM25 关键词检索未实现 注释写着混合检索器:pgvector 向量检索 + BM25 关键词检索,但实际只有向量检索。 **影响**: - 精确药品名查询(如阿莫西林)效果不如精确匹配 - 专业术语查询(如HPLC)向量检索可能不够精准 ### 7.3 数据库连接管理 engine = create_async_engine(DB_URL) try: # ... finally: await engine.dispose() 每次查询都创建新的 engine 并销毁,应该使用连接池复用。 --- ## 八、Reranker 模块 — reranker.py **文件位置**: backend-python/app/rag/reranker.py **当前状态**: 形同虚设 class Reranker: def _load_model(self): pass # 空实现 def rerank(self, query, documents, top_k=5): sorted_docs = sorted(documents, key=lambda d: d.get(score, 0), reverse=True) return sorted_docs[:top_k] # 只是按原始分数排序 **设计目标**: BGE-Reranker-v2-m3 Cross-Encoder 精排 **实际效果**: 仅按向量相似度分数排序取 Top-K,无精排能力 --- ## 九、Embedder 模块 — embedder.py **文件位置**: data-pipeline/processors/embedder.py **当前状态**: 本地模型未实现 def encode(self, texts: list[str], batch_size: int = 32) -> list[list[float]]: return [[0.0] * settings.embedding_dim] * len(texts) # 返回全零向量 **实际情况**: 入库时走 DashScope API(ingest.py 中),本地 BGE-M3 模型未加载。 --- ## 十、Wiki 爬虫解析 — wiki_spider.py **文件位置**: data-pipeline/crawlers/wiki_spider.py **当前状态**: 部分可用 ### 10.1 内容截断问题 sections[matched] = .join(current_text)[:3000] 每个 section 最多 3000 字符,超出部分直接截断,可能丢失关键的不良反应、禁忌等信息。 ### 10.2 分类推断过于简单 def infer_category(name, sections): if any(k in name for k in [草, 花, 叶, 根, 皮, 中, 散, 汤, 丸]): return 中药 仅靠药名中是否含特定汉字来判断,误判率高(如花旗参匹配花)。 --- ## 十一、数据 Schema 定义 ### 11.1 药品条目 Schema { drug_id: string, name: string, name_en: string, name_pinyin: string, category: 化学药 | 中药 | 生物制品 | 辅料, subcategory: string, sections: { 性状: string, 鉴别: string, 检查: string, 含量测定: string, 类别: string, 贮藏: string, 制剂: string, 用法与用量: string, 禁忌: string, 不良反应: string, 注意事项: string }, source: { version: 2020年版 | 2025年版, volume: 一部 | 二部 | 三部 | 四部, page: string } } ### 11.2 知识点 Schema { point_id: KP-{subject}-{chapter}-{seq}, subject: yao1 | yao2 | fagui | zonghe, chapter_id: string, title: string, content: string, difficulty: 1-5, frequency: 高频 | 中频 | 低频 | 未考, related_drugs: [drug_id], key_points: [string] } ### 11.3 题目 Schema { question_id: Q{yyyy}{mm}{seq}, question_type: A | B | X, subject: yao1 | yao2 | fagui | zonghe, content: string, options: [A. xxx, B. xxx, C. xxx, D. xxx], answer: A | AB | ABC, explanation: string, knowledge_point_ids: [point_id], frequency: 高频 | 中频 | 低频 | 未考 } --- ## 十二、问题汇总与优先级 ### 12.1 致命问题(阻塞核心功能) | 编号 | 问题 | 模块 | 影响 | |------|------|------|------| | F-01 | PDF 解析器完全未实现 | pharmacopoeia.py | 2020 版药典 PDF 无法解析 | | F-02 | 考试知识点切片为空 | knowledge_chunker.py | 考试模块数据管道断裂 | | F-03 | Reranker 未加载模型 | reranker.py | 检索结果无精排能力 | | F-04 | BM25 关键词检索未实现 | retriever.py | 精确查询效果差 | ### 12.2 严重问题(影响数据质量) | 编号 | 问题 | 模块 | 影响 | |------|------|------|------| | S-01 | DOCX 解析不处理表格 | docx_ingest.py | 含量测定限度表等丢失 | | S-02 | 文本清洗规则冲突 | cleaner.py | 英文/数字断行连接失效 | | S-03 | OCR 纠错字典不足 | cleaner.py | PDF 提取文本错误多 | | S-04 | 切片无 overlap | chunker.py | 上下文断裂 | | S-05 | 凡例/通则切片为空 | chunker.py | 通则类内容无法检索 | ### 12.3 中等问题(影响准确性) | 编号 | 问题 | 模块 | 影响 | |------|------|------|------| | M-01 | 意图分类关键词冲突 | retriever.py | 用户意图误判 | | M-02 | section 标题长度硬编码 | docx_ingest.py | 部分标题漏识别 | | M-03 | 凡例/纲要拆分逻辑脆弱 | docx_ingest.py | 编号拆分错误 | | M-04 | PDF 目录层级坐标硬编码 | extract_catalog.py | 不同 PDF 版本失效 | | M-05 | 旧版去重可能误删 | docx_ingest.py | 2020 版独有信息丢失 | ### 12.4 轻微问题(影响体验) | 编号 | 问题 | 模块 | 影响 | |------|------|------|------| | L-01 | Wiki 内容截断 | wiki_spider.py | 长内容不完整 | | L-02 | Wiki 分类推断粗糙 | wiki_spider.py | 分类误判 | | L-03 | 文件名依赖 | docx_ingest.py | 不规范文件名出错 | | L-04 | 数据库连接不复用 | retriever.py | 性能浪费 | --- ## 十三、改进建议 ### 13.1 短期(Phase 2 内) 1. **实现 PDF 解析器** - 使用 PyMuPDF 提取文本 + 坐标 - 基于字体大小识别标题层级 - 针对 2020 版和 2025 版分别适配 2. **修复文本清洗规则顺序** # 正确顺序 self.replacements = [ (r([a-z])\n([a-z]), r\1\2), # 先处理断行 (r(\d)\n(\d), r\1\2), (r(?95%) --- ## 十四、附录 ### 14.1 药典版本对照表 | 版本 | 部数 | 主要内容 | 数据格式 | |------|------|---------|---------| | 2020 年版 | 四部 | 中药、化学药、生物制品、通则 | PDF | | 2025 年版 | 四部 | 新增 + 修订品种 | DOCX | ### 14.2 考试科目代码 | 代码 | 科目名称 | |------|---------| | yao1 | 药学专业知识(一) | | yao2 | 药学专业知识(二) | | fagui | 药事管理与法规 | | zonghe | 药学综合知识与技能 | ### 14.3 题型说明 | 题型 | 说明 | |------|------| | A 型题 | 单项选择题 | | B 型题 | 配伍选择题 | | X 型题 | 多项选择题 | --- **文档结束**