fix: 第21轮搜索审计修复 — OR模式NOT回退/子组虚条目/日期OR模式等10项
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

Bug-R21-1 (CRITICAL): OR模式NOT分离过度 — A OR NOT B语义错误,恢复or_(*conditions)
Bug-R21-2 (CRITICAL): 双重嵌套括号sub_group_refs虚条目 — 仅当_ungrouped非空时写入
Bug-R21-3 (HIGH): negated_date_ranges被覆盖 — =改为|=
Bug-R21-4 (MEDIUM): 日期/PMID/DOI/PMC条件在OR模式始终AND — 合并为or_(*conditions)
Bug-R21-5 (MEDIUM): _parse_atom无条件消费任何词符 — 类型验证守卫
Bug-R21-6 (MEDIUM): 模态框双重goToPage(1) — 按钮只关弹窗,搜索由watcher触发
Bug-R21-7 (MEDIUM): page.value失败后不回退 — 捕获异常恢复前一页
Bug-R21-8 (LOW): 空引号""[TI]产生空Term — text为空时跳过
Bug-R21-9 (LOW): 冗余函数内import re — 使用模块级导入
This commit is contained in:
34047007@qq.com
2026-07-28 16:52:31 +08:00
parent 53cf1d6b70
commit c68aa060f0
5 changed files with 110 additions and 29 deletions
+78 -1
View File
@@ -2,7 +2,7 @@
> 本文档按修复轮次详细记录所有搜索功能合规性修复的背景、根因分析和修改内容。
>
> **累计**20 轮,224 项修复,80+ 字段标签注册,1007 项测试覆盖,7 项已知限制
> **累计**21 轮,235 项修复,80+ 字段标签注册,1007+ 项测试覆盖,7 项已知限制
> **时间跨度**2026-07-24 ~ 2026-07-29
> **核心文件**`pubmed_query_parser.py`~850 行)→ `search_engine.py`~1360 行)
@@ -1508,6 +1508,83 @@
| L7 | 字段标签 REF/ISBN 未注册 | 低使用频率或数据缺失 | 低 |
| L8 | GIN 索引缺失(基因/chemicals 等) | 需 DBA 操作,生产数据量大 | 中(大表性能) |
| L9 | `_dispatch_term` 回归不可见 | 需字段级测试。第 15 轮修复了 `__RANGE_*` 组内回退 | 低 |
| L10 | `_has_or` 深度盲区 | `_parse_primary` 在父组范围内搜索 OR token,不限制括号深度。`(A AND (B OR C))` 中父组操作符错误为 "or" | 极低(仅在复杂嵌套触发) |
---
## 第二十一轮:第 21 轮审计修复(10 项)
**日期**2026-07-29
**提交**`53cf1d6..`(第 20 轮后追加)
**数量**10 项(2 CRITICAL + 1 HIGH + 4 MEDIUM + 3 LOW
**触发**:用户第 16 次要求全面检查(第 21 轮,4 并行审计 agent)
**测试**1007+ 全部通过 + 前端 build 通过
### Bug-R21-1 (CRITICAL): OR 模式 NOT 分离过度 — `A OR NOT B` 语义错误
- **文件**`search_engine.py:1265-1278`
- **根因**R20 将 OR 模式的 `or_(*term_conditions)` 改为分离 UnaryExpression NOT 后独立 AND。`cancer OR NOT review` 被编译为 `cancer AND NOT review`(仅检索 cancer 且不是 review 的文献),而非正确的 PubMed 语义 `cancer OR NOT review`(所有 cancer 文献 + 所有非 review 文献)。
- **影响**:OR+NOT 组合查询结果严重过窄。违反"搜索功能必须与 PubMed 完全一致"硬性要求。
- **修复**:恢复为 `conditions.append(or_(*term_conditions))`。
### Bug-R21-2 (CRITICAL): 双重嵌套括号 `sub_group_refs` 虚条目 — 搜索词被丢弃
- **文件**`pubmed_query_parser.py:791-798`
- **根因**`_parse_primary` 在 `_ungrouped` 为空(所有词已在前一层分好组)时仍写入 `sub_group_refs`,导致 `sub_group_refs` 比 `groups` 多一项。组索引 0 出现在虚条目的子列表中 → 引擎 `_is_child` 为 True → 整个组被 `continue` 跳过。
- **触发**:任何双重嵌套括号 `((cancer[MH]))` 或外层括号内全部是已分组内容的表达式。
- **修复**:仅当 `_ungrouped` 非空(即真正创建父组)时才写入 `sub_group_refs`。
### Bug-R21-3 (HIGH): `negated_date_ranges` 被覆盖而非合并
- **文件**`pubmed_query_parser.py:461`
- **根因**:第 461 行 `=` 直接覆盖集合,`_dispatch_term` 中单日期 NOT 的 `add()` 被丢弃。`NOT "2024-01-01"[DP]` → 引擎误以正日期过滤。
- **修复**`=` 改为 `|=`。
### Bug-R21-4 (MEDIUM): 日期/PMID/DOI/PMC 条件在 OR 模式下始终 AND
- **文件**`search_engine.py:1377-1380`
- **根因**`_pubmed_conditions` 末尾所有日期/ID 条件 `conditions.append()` → `and_(*conditions)` 强制 AND。`cancer OR 2000:2020[DP]` 实际等同 `cancer AND pub_date in 2000-2020`。
- **修复**OR 模式时 `conditions = [or_(*conditions)]`。
### Bug-R21-5 (MEDIUM): `_parse_atom` 无条件消费任何词符
- **文件**`pubmed_query_parser.py:836-839`
- **根因**`self.advance()` 不验证类型。`cancer OR OR lung` → 第二个 OR 被当作 WORD。
- **修复**:读取前验证 `self.peek().type` 是原子类型。
### Bug-R21-6 (MEDIUM): 模态框触发两次 `goToPage(1)`
- **文件**`SearchView.vue:961/981/997`
- **根因**:按钮 `@click` 同时设置 `showModal = false`(触发 watcher)和直接 `goToPage(1)`。
- **修复**:按钮只设置 `showModal = false`,搜索由 watcher 触发。
### Bug-R21-7 (MEDIUM): `page.value` 失败后不回退
- **文件**`usePagination.ts:23-26`
- **根因**`goToPage` 在 `fetchFn` 前设置 `page.value = n`。
- **修复**:捕获异常后恢复 `page.value`。
### Bug-R21-8 (LOW): 空引号 `""[TI]` 产生空 Term
- **文件**`pubmed_query_parser.py:843-846`
- **根因**`""` 被 tokeniser 匹配为 QUOTED`strip('"')` 后为空。
- **修复**`if not text: return []`。
### Bug-R21-9 (LOW): 冗余函数内 `import re`
- **文件**`pubmed_query_parser.py:997`、`search_engine.py:358/372`
- **修复**:移除冗余函数级导入,使用模块级 `import re`。
### 审计结果汇总
| 审计维度 | 结果 |
|---------|------|
| R20 回归(OR NOT 分离) | ✅ 已 revert |
| R20 回归(sub_group_refs 虚条目) | ✅ _ungrouped 守卫 |
| 搜索引擎代码 | ✅ negated_date_ranges、OR 模式日期条件、冗余导入 |
| 解析器/分词器 | ✅ _parse_atom 类型验证、空引号守卫 |
| 前端集成 | ✅ 双重 goToPage、page 回滚 |
| 已知限制更新 | L4 已修复移除、新增 L10 _has_or 深度盲区 |
---