# 搜索功能差距分析：与 PubMed 对比

> 审计日期：2026-07-24
> 数据基准：本地 PG 数据库 1,662 篇文献（seed_data: 110, pubmed_api: 1,552）
> 分析方法：9 Agent 审计 + 真实数据库字段覆盖率查询

---

## 1. 现状概览

当前搜索系统架构：

- **查询解析**：`backend/app/services/pubmed_query_parser.py` — 递归下降解析器，支持 11 个字段标签，AND/OR/NOT 布尔
- **搜索引擎**：`backend/app/services/search_engine.py` — PostgreSQL tsvector GIN 索引 + ILIKE 回退
- **前端入口**：`SearchView.vue`（主搜索页）+ `AdvancedSearchPanel.vue`（高级筛选面板）
- **API 端点**：`POST /features/search/advanced`（主要）+ `GET /literature/search`（死代码）

---

## 2. 数据充分性评估

### 2.1 充分字段（可直接支撑搜索）

| 字段 | 覆盖率 | 用途 |
|------|--------|------|
| `pmid` | 1,662/1,662 (100%) | `[PMID]` 精确搜索 |
| `title` | 1,652/1,662 (99.4%) | `[TI]` 全文搜索 |
| `abstract` | 1,550/1,662 (93.3%) | `[AB]` 全文搜索 |
| `doi` | 1,548/1,662 (93.1%) | `[DOI]` 精确搜索 |
| `authors` (JSON) | 1,659/1,662 (99.8%) | `[AU]` `[AD]` 搜索 |
| `pub_types` (JSON) | 1,557/1,662 (93.7%) | `[PT]` 筛选 |
| `journal` | 1,662/1,662 (100%) | `[TA]` 全称搜索 |
| `publication_status` | 1,552/1,662 (93.4%) | 状态筛选 |
| `pub_year` | 1,662/1,662 (100%) | `[DP]` 年份搜索 |
| `pub_date` | 1,148/1,662 (69.1%) | `[DP]` 日期搜索 |
| `language` | 1,644/1,662 (98.9%) | `[LA]` 搜索 |
| `search_tsv` | 1,662/1,662 (100%) | tsvector GIN 全文搜索 |
| `mesh_headings` (JSON) | 845/1,662 (50.8%) | MeSH 展示（搜索由 literature_tags 完成） |
| `literature_tags` | 997 条关联 | `[MH]`/`[MAJR]` 搜索 |
| `grants` (JSON) | 323/1,662 (19.4%) | `[GR]` 搜索 |
| `chemical_list` (JSON) | 508/1,662 (30.6%) | `[NM]` 搜索 |
| `study_design` (JSON) | 1,552/1,662 (93.4%) | 临床研究设计筛选 |

### 2.2 严重不足字段

| 字段 | 覆盖率 | 影响 | 优先级 |
|------|--------|------|--------|
| `journal_iso` | **0/1,662 (0%)** | `[TA]` 缩写期刊搜索完全不可用 | **P0** |
| `tree_numbers` | **0 行数据** | `_expand_mesh_tag_ids` INNER JOIN → 所有 `[MH]`/`[MAJR]` 查询返回 0 结果 | **P0** |
| `keywords` (JSON) | **0/1,662 (0%)** | `[OT]` 其他关键词搜索不可用 | **P1** |
| `pmc_id` | **9/1,662 (0.5%)** | XPath bug，`[PMC]` 搜索和 PMC OA 全文抓取不可用 | **P1** |
| `gene_symbols` (JSON) | **0%** | 基因符号搜索不可用 | P2 |
| `is_negative_result` | 仅 pubmed_api 来源有 | 构造函数遗漏 seed_data | **P1** |
| `retraction_details` | **0%** | 全库无撤稿记录 | P3 |
| `full_text_sections` | **0.4%** | PMC OA 全文解析进度 | P3 |

### 2.3 核心数据指标

```
总计:        1,662 篇
年份分布:    2025=58, 2026=1,604 (96.5% ∈ 2026)
标签:        99 个 (全部 manual, 28 个未关联任何文献)
文献-标签:   997 条 (946 major, 51 non-major)
每篇标签数:  P25=1, P50=1, P75=2, P90=5, P99=6
被引次数:    median=0, avg=0.6, max=491
撤稿:        0 篇
语言分布:    eng=1,496, en=110, chi=35, jpn=7, rus=4
citation_status: MEDLINE=502, Publisher=254, In-Process=64, PubMed-not-MEDLINE=59
publication_status: ppublish=544, epublish=530, aheadofprint=478
```

---

## 3. 与 PubMed 搜索功能差距矩阵

| 搜索功能 | PubMed | 当前系统 | 差距 |
|---------|--------|---------|------|
| 纯文本搜索 (title/abstract) | tsvector + 权重 | ✅ tsvector + GIN | 无 setweight |
| `[TI]` 标题字段 | 精确 + 词干 | ✅ | — |
| `[AB]` 摘要字段 | 精确 + 词干 | ✅ | — |
| `[AU]` 作者字段 | 精确 + 自动截词 | ✅ (ILIKE) | exact 参数被忽略 |
| `[TA]` 期刊字段 | 全称 + 缩写 | ❌ journal_iso 0% | **未实现** |
| `[MH]` MeSH 字段 | tree_number 展开 + 子树 | ❌ INNER JOIN 0 结果 | **Bug** |
| `[MAJR]` 主要 MeSH | 同上 + major | ❌ 同上 | **Bug** |
| `[MH:NoExp]` 不展开 | 精确 MeSH | ❌ 解析器不支持 | **未实现** |
| `[PT]` 文献类型 | 精确匹配 | ✅ JSONB contains | — |
| `[DP]` 出版日期 | 范围 + 格式灵活 | ✅ 年份/日期 | 单值年份 bug |
| `[LA]` 语言 | 2 字母代码 | ❌ 解析器无映射 | **未实现** |
| `[AD]` 机构 | 地址文本 | ❌ 解析器无映射 | **未实现** |
| `[EDAT]` 入库日期 | 精确日期 | ❌ 字段不存在 | **未实现** |
| `[OT]` 其他关键词 | 关键词文本 | ❌ keywords 0% | **未实现** |
| `[GR]` 基金号 | 基金信息 | ❌ 解析器无映射 | **未实现** |
| `[NM]` 化学物质 | 化学物质名 | ❌ 解析器无映射 | **未实现** |
| `[TW]` 文本词 | 全字段文本 | ❌ 解析器无映射 | **未实现** |
| `[ALL]` 全字段 | 等价于无标签 | ❌ 被静默丢弃 | **Bug** |
| `[PMC]` PMCID | 精确匹配 | ❌ XPath bug 数据不足 | **Bug** |
| 通配符 `*` | 单/多字符 | ❌ plainto_tsquery 不支持 | **未实现** |
| 精确短语 `"..."` | phraseto_tsquery | ❌ ILIKE 回退 | **未实现** |
| AND/OR/NOT 布尔 | 从左到右优先级 | ⚠️ AND>OR 优先级 | Bug |
| `:NoExp` 后缀 | MeSH 不展开 | ❌ 解析器不支持 | **未实现** |
| `[Title/Abstract]` 长标签 | 标题+摘要 | ❌ 仅 TIAB | **未实现** |
| **ATM** 自动术语映射 | MeSH + Journal + Author 翻译 | ❌ **完全缺失** | **P0** |
| **Entry Terms** 入口词 | ~200k 同义词 | ❌ **完全缺失** | **P1** |
| **搜索历史 #1 AND #2** | 会话级历史链 | ❌ **完全缺失** | P2 |
| **搜索建议/自动补全** | MeSH + Author + Journal | ❌ **完全缺失** | P2 |
| **搜索结果排序** | 相关度 + 日期 + 被引 | ⚠️ best_match 权重公式错误 | Bug |
| **ts_rank 权重** | title(A) > abstract(B) | ❌ 全部相同 | **未实现** |
| **Faceted 筛选** | 文献类型 + 日期 + 语言 | ❌ **完全缺失** | P2 |
| **Spell correction** | "Did you mean" | ❌ **完全缺失** | P3 |
| **搜索高亮** | 匹配词高亮 | ❌ **完全缺失** | P3 |

---

## 4. 关键 Bug 清单（按严重性排列）

### 🚨 CRITICAL（7 个）

| # | Bug | 位置 | 描述 |
|---|-----|------|------|
| 1 | `recent_subq` 破坏历史搜索 | `search_engine.py:314-319` | `sort=date` 时预取最近 2000 篇 ID（无条件），早期文献搜索 0 结果 |
| 2 | OR 布尔被 AND 替代 | `search_engine.py:387-414` | 所有 field_map 条件 `and_()` 合并，`result.boolean_operator` 未被消费 |
| 3 | 多 [MH] 词用 OR 而非 AND | `search_engine.py:516-519` | 所有 mesh term 同一 `or_()` 子查询 |
| 4 | Date Sort Optimization 无条件 | `search_engine.py:314` | 无 80% 匹配率门槛，固定 2000 篇限制 |
| 5 | PMC/trial_reg XPath 上下文错误 | `pubmed_api.py:440,447` | 在 `MedlineCitation/Article` 下搜 `ArticleIdList`（实际在 `PubmedData` 下） |
| 6 | 5 字段构造函数遗漏 | `pubmed_api.py:914-944` | `pubmed_revised`/`citation_status`/`date_completed`/`article_date`/`suppl_mesh_list` |
| 7 | `_expand_mesh_tag_ids` INNER JOIN | `search_engine.py:518` | `global_tag_tree_numbers` 0 行 → 全部 [MH] 查询 0 结果 |

### 🟡 HIGH（12 个）

| # | Bug | 位置 | 描述 |
|---|-----|------|------|
| 8 | `ArticleTitle.text` | `pubmed_api.py:409` | 跳过嵌套 `<i>` 等标签 |
| 9 | `[TA]` 不搜 journal_iso | `search_engine.py:476` | 缩写期刊名零结果 |
| 10 | `_field_condition("all")` exact 不搜 abstract | `search_engine.py:482-486` | 精确短语只在 title 内搜 |
| 11 | `_field_condition` exact 被 author/journal 忽略 | `search_engine.py:473-478` | exact 参数传递中断 |
| 12 | 中文 → English tsquery 零结果 | `search_engine.py:123-149` | 中文不经过词干分析 |
| 13 | `best_match` 权重被淹没 | `search_engine.py:556-558` | ts_rank*0.3 vs recency+5, ln(cited)*2 |
| 14 | tsvector 无 setweight | 迁移文件 | title/abstract/author/affiliation 权重相等 |
| 15 | 多 affiliation 仅取第一个 | `pubmed_api.py:427` | `find` 而非 `findall` |
| 16 | CommentsCorrections 仅撤稿 | `pubmed_api.py:484-497` | 仅 `RetractedBy`/`RetractionOf`，缺 ~18 个 RefType |
| 17 | Entry Terms 无法导入 | `scripts/import_mesh_tags.py` | mtrees.bin 无 entry terms，需 asc 解析器 |
| 18 | PMC_ID 格式不一致 | `pubmed_api.py:264,442` | Europe PMC 带 "PMC" 前缀，XML 路径纯数字 |
| 19 | Europe PMC 7 字段 None | `pubmed_api.py:170-299` | `pubmed_revised`/`citation_status`/`date_completed`/`trial_reg`/`retraction_details`/`grants`/`article_date` |

### 🟠 MEDIUM（9 个）

| # | Bug | 位置 | 描述 |
|---|-----|------|------|
| 20 | 搜索端点无 try/except | `features.py:68` | 异常 → 500 |
| 21 | DISMISS_THRESHOLD 从未检查 | `literature.py:23` | 已读/忽略过滤失效 |
| 22 | SynonymExpander 零引用 | `backend/app/services/synonym_expander.py` | 312 行死代码 |
| 23 | `import re` 在函数内 | `search_engine.py:113` | 每次调用重新 import |
| 24 | 前端 field:all 硬编码 | `SearchView.vue:83` | AdvancedSearchPanel 形同虚设 |
| 25 | `[ALL]` 标签静默丢弃 | `pubmed_query_parser.py:40,69` | 不在 FIELD 集合 |
| 26 | `[Title/Abstract]` `[MeSH]` 不被识别 | `pubmed_query_parser.py:40,69` | 只识别短标签 |
| 27 | exact 被 MeSH/Author 丢弃 | `search_engine.py:474,516` | ILIKE 模糊匹配 |
| 28 | 查询构建代码重复 | `search_engine.py:225-250,273-288` | 两段完全相同 |

---

## 5. 结论

**总体判断**：当前数据可支撑基础的纯文本搜索（title/abstract/author），但以下搜索功能完全阻塞：

1. **MeSH 搜索** — `tree_numbers` 空表 + INNER JOIN → 0 结果
2. **期刊缩写搜索** — `journal_iso` 覆盖率 0%
3. **ATM 自动术语映射** — Entry Terms 缺失、SynonymExpander 未集成
4. **精确短语** — `phraseto_tsquery` 未使用，`setweight` 未配置
5. **搜索排名** — `best_match` 权重公式错误，导致 ts_rank 几乎不影响排序

修复路径：先解决数据导入 Bug（journal_iso、keywords、PMC_ID）+ 搜索 Bug（tree_number、布尔逻辑），再逐步补充字段标签和功能。

详细实施步骤见 [12-搜索功能实施计划.md](12-搜索功能实施计划.md)。
数据统计详情见 [13-文献数据质量报告.md](13-文献数据质量报告.md)。
