# 中国药典 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(?<!\n)\n(?!\n), ),           # 规则1: 移除单换行
    (r\x00, ),                         # 规则2: 移除空字符
    (r([a-z])\n([a-z]), r\1\2),     # 规则3: 英文断行连接
    (r(\d)\n(\d), r\1\2),         # 规则4: 数字断行连接
    (r\n{3,}, \n\n),                # 规则5: 多换行压缩
    (r\t,  ),                         # 规则6: 制表符替换
    (r {2,},  ),                       # 规则7: 多空格压缩
]

**冲突分析**:
- **规则 1 先执行** -> 所有单换行被删除 -> **规则 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(?<!\n)\n(?!\n), ),         # 再移除不当换行
       ...
   ]

3. **实现表格解析**
   - 遍历 doc.tables 提取表格内容
   - 将表格转换为结构化文本或 JSON

4. **实现 Reranker 模型加载**
   - 加载 BGE-Reranker-v2-m3
   - 实现 Cross-Encoder 精排

### 13.2 中期（Phase 3）

1. **实现 BM25 混合检索**
   - 使用 pg_trgm 或 Elasticsearch
   - 向量检索 + 关键词检索融合排序

2. **优化意图分类**
   - 使用小模型（如 BERT）进行意图分类
   - 解决关键词冲突问题

3. **实现考试知识点切片**
   - 按大纲章节组织知识点
   - 生成可向量化的 chunk

### 13.3 长期（Phase 4+）

1. **多版本数据共存**
   - 2020 版和 2025 版数据共存
   - 查询时优先返回最新版，但可切换版本

2. **增量更新机制**
   - 药典更新时自动检测变更
   - 增量解析和入库

3. **质量评估体系**
   - 解析准确率评测
   - 检索召回率评测
   - AI 回答准确率评测（目标 >95%）

---

## 十四、附录

### 14.1 药典版本对照表

| 版本 | 部数 | 主要内容 | 数据格式 |
|------|------|---------|---------|
| 2020 年版 | 四部 | 中药、化学药、生物制品、通则 | PDF |
| 2025 年版 | 四部 | 新增 + 修订品种 | DOCX |

### 14.2 考试科目代码

| 代码 | 科目名称 |
|------|---------|
| yao1 | 药学专业知识（一） |
| yao2 | 药学专业知识（二） |
| fagui | 药事管理与法规 |
| zonghe | 药学综合知识与技能 |

### 14.3 题型说明

| 题型 | 说明 |
|------|------|
| A 型题 | 单项选择题 |
| B 型题 | 配伍选择题 |
| X 型题 | 多项选择题 |

---

**文档结束**
