fix: 第五轮PubMed搜索审计 — 4项边缘案例修复 + 第5轮审计文档

Parser边缘案例修复:
- P5-1: 尾部NOT导致IndexError降级(_parse_not_expr添加EOF守卫)
- P5-2: 单数字日期格式不被识别(YYYY-M-D→YYYY-MM-DD归一化)
- P5-3: is_pubmed_syntax不处理全角字符(增加NFKC归一化)
- P5-4: extract_pubmed_query_for_prisma不能处理[Title/Article]

同时将在前几轮修复的F1-F8([MH:noexp]顶层支持、NOT De Morgan、
NOT NOT递归、Custom Range日期、HomeView恢复、precision_mode清理、
is_oa注释、N+1批量优化)补充文档核对验证。
This commit is contained in:
34047007@qq.com
2026-07-27 11:55:03 +08:00
parent 2979de6f9c
commit 275a9f6511
5 changed files with 146 additions and 28 deletions
+65 -10
View File
@@ -2,7 +2,7 @@
> 本文档按修复轮次详细记录所有搜索功能合规性修复的背景、根因分析和修改内容。
>
> **累计**4 轮,59 项修复,50+ 字段标签注册,127 项测试覆盖
> **累计**5 轮,63 项修复,50+ 字段标签注册,214 项测试覆盖
> **时间跨度**2026-07-24 ~ 2026-07-27
> **核心文件**`pubmed_query_parser.py`~620 行)→ `search_engine.py`~1250 行)
@@ -14,7 +14,8 @@
2. [第二轮:第二轮审计修复(8 项)](#第二轮第二轮审计修复)
3. [第三轮:第三轮审计修复(14 项)](#第三轮第三轮审计修复)
4. [第四轮:字段补全与语义优化(11 项)](#第四轮字段补全与语义优化)
5. [遗留限制](#遗留限制)
5. [第五轮:第 5 轮全面审计修复(4 项)](#第五轮第-5-轮全面审计修复)
6. [遗留限制](#遗留限制)
---
@@ -324,15 +325,69 @@
---
## 第五轮:第 5 轮全面审计修复
**提交**`pending`
**日期**2026-07-27
**数量**4 项
**触发**5 Agent 并行深度审计 + 第 2 轮规划核对
### 背景
第 4 轮后启动第 5 轮全面审计,5 个 agent 并行扫描 parser、engine、前端、文档。首先与第 2 轮审计计划(F1-F8)逐项核对,确认以下 8 项已在之前轮次完成:
| 计划 ID | 项目 | 完成轮次 | 状态 |
|---------|------|---------|------|
| F1 | `[MH:noexp]` 顶层支持 | 第 3/4 轮 | 已修复 |
| F2 | 组内 NOT De Morgan 律 | 第 2 轮 | 已验证正确 |
| F3 | `_parse_not_expr` NOT NOT 递归 | 第 4 轮 | 已修复 |
| F4 | SearchView Custom Range 日期 | 第 3 轮 | 已工作正常 |
| F5 | HomeView restoreFromUrl | 第 3 轮 | 已工作正常 |
| F6 | precision_mode 死代码 | 此前轮次 | 已移除 |
| F7 | is_oa 死字段 | 此前轮次 | 已注释 `unused` |
| F8 | `_expand_mesh_tag_ids` N+1 | 第 3/4 轮 | 已批量优化 |
实际在第 5 轮修复的 4 项均为 parser 边缘案例:
### P5-1: 尾部 NOT 导致 IndexError 降级
- **文件**`pubmed_query_parser.py` `_parse_not_expr` L498
- **问题**`cancer NOT` — 解析器 `_is_primary_start` 包含 `NOT`,隐式 AND 逻辑尝试将 NOT 作为新词开始,但 NOT 之后无 token → `peek()` 越界抛 `IndexError` → 整个查询降级为纯文本拆分,`NOT` 作为字面搜索词(影响极小但产生异常)
- **根因**`_parse_not_expr` 不检查是否已到 EOF
- **修复**:消费 NOT token 后立即检查 `peek().type == TokenType.EOF`,直接返回 `[]` 静默忽略
### P5-2: 单数字日期格式不被识别
- **文件**`pubmed_query_parser.py` `parse_pubmed_query` L679
- **问题**`2024-1-1[DP]` — tokeniser 的 DATE 模式只匹配 `\d{4}-\d{2}-\d{2}`,单数字月/日被解析为 `WORD('2024-1-1')` → `_dispatch_term` 设置 `date_from='2024-1-1'` → `date.fromisoformat()` 抛 `ValueError` → 日期条件被静默丢弃
- **根因**tokeniser 前缺少单数字日期归一化
- **修复**:在 `parse_pubmed_query` 的 NFKC 归一化后增加 `YYYY-M-D → YYYY-MM-DD` 正则替换
### P5-3: `is_pubmed_syntax` 不处理全角字符
- **文件**`pubmed_query_parser.py` `is_pubmed_syntax` L652
- **问题**:全角括号 `TI` 不被 `\[...\]` 识别 → 检出失败 → 走纯文本路径(解析器内 `parse_pubmed_query` 做 NFKC 但已不会进入)
- **根因**`is_pubmed_syntax` 未做 NFKC 归一化、与 `parse_pubmed_query` 行为不一致
- **修复**:函数开头增加 `query = unicodedata.normalize('NFKC', query)`
### P5-4: `extract_pubmed_query_for_prisma` 不能处理 `[Title/Article]`
- **文件**`pubmed_query_parser.py` `extract_pubmed_query_for_prisma` L706
- **问题**:归一化 regex `\[([\w:]+)\]` 不含 `/``[Title/Article]` 不被匹配、保持原文
- **根因**regex 字符类不含 `/`
- **修复**`[\w:]` → `[\w/:]`
---
## 各轮变更摘要
| 维度 | 第一轮 | 第二轮 | 第三轮 | 第四轮 |
|------|--------|--------|--------|--------|
| 修复数 | 34 | 8 | 14 | 11 |
| 后端文件变更 | 全部 | engine + parser | engine + parser + api | engine + parser |
| 前端文件变更 | SearchView + HomeView + Card | SearchView + HomeView + Panel | SearchView + HomeView | 0 |
| 测试变更 | 新增 | +6 项 | 已有覆盖 | 0 |
| 新功能 | `[MH]`/`[EDAT]`/`[AD]`/`[LA]` 等 | — | — | `[GEN]`/`[PMC]`/`[Title/Abstract]` |
| 维度 | 第一轮 | 第二轮 | 第三轮 | 第四轮 | 第五轮 |
|------|--------|--------|--------|--------|--------|
| 修复数 | 34 | 8 | 14 | 11 | 4 |
| 后端文件变更 | 全部 | engine + parser | engine + parser + api | engine + parser | parser |
| 前端文件变更 | SearchView + HomeView + Card | SearchView + HomeView + Panel | SearchView + HomeView | 0 | 0 |
| 测试变更 | 新增 | +6 项 | 已有覆盖 | 0 | 0 |
| 新功能 | `[MH]`/`[EDAT]`/`[AD]`/`[LA]` 等 | — | — | `[GEN]`/`[PMC]`/`[Title/Abstract]` | — |
| 性质 | 从零搭建 | 审计修复 | 深度审计修复 | 字段补全 |
---
@@ -360,4 +415,4 @@
| `test_pubmed_query_parser.py` | ~40 | tokeniser、解析器、语法正确性 |
| `test_pubmed_search_integration.py` | ~60 | 字段映射、API 集成、前端格式 |
| `test_comprehensive_verify.py` | ~27 | 字段完整、NOT 语义、括号、日期 |
| **合计** | **127** | 全部通过 |
| **合计** | **214** | 全部通过 |