fix: 第六轮全面搜索审计修复 — 12项
Parser: Title/Abstract大小写匹配、末尾OR空Term保护、tokeniser gap字符恢复、 混合日期范围反转支持 Engine: field标签正则含/、ATM括号剥离、name_zh LIMIT 100、 year_from/ year_to is not null检查 Docs: 更新修复全记录至第六轮
This commit is contained in:
+100
-11
@@ -2,9 +2,9 @@
|
||||
|
||||
> 本文档按修复轮次详细记录所有搜索功能合规性修复的背景、根因分析和修改内容。
|
||||
>
|
||||
> **累计**:5 轮,63 项修复,50+ 字段标签注册,214 项测试覆盖
|
||||
> **累计**:6 轮,75 项修复,50+ 字段标签注册,248 项测试覆盖
|
||||
> **时间跨度**:2026-07-24 ~ 2026-07-27
|
||||
> **核心文件**:`pubmed_query_parser.py`(~620 行)→ `search_engine.py`(~1250 行)
|
||||
> **核心文件**:`pubmed_query_parser.py`(~730 行)→ `search_engine.py`(~1320 行)
|
||||
|
||||
---
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
3. [第三轮:第三轮审计修复(14 项)](#第三轮第三轮审计修复)
|
||||
4. [第四轮:字段补全与语义优化(11 项)](#第四轮字段补全与语义优化)
|
||||
5. [第五轮:第 5 轮全面审计修复(4 项)](#第五轮第-5-轮全面审计修复)
|
||||
6. [遗留限制](#遗留限制)
|
||||
6. [第六轮:第 6 轮全面审计修复(12 项)](#第六轮第-6-轮全面审计修复)
|
||||
7. [遗留限制](#遗留限制)
|
||||
|
||||
---
|
||||
|
||||
@@ -377,18 +378,106 @@
|
||||
- **根因**:regex 字符类不含 `/`
|
||||
- **修复**:`[\w:]` → `[\w/:]`
|
||||
|
||||
### P5-5: Tokeniser 缺口字符被静默丢弃(BUG-7)
|
||||
|
||||
- **文件**:`pubmed_query_parser.py` `tokenise()` L150
|
||||
- **问题**:`finditer` 只输出匹配到的片段,`$`、`@` 等匹配不到的字符无声丢失
|
||||
- **根因**:`_TOKEN_PATTERNS` 未覆盖所有可能字符,且无 fallback
|
||||
- **修复**:在 `tokenise()` 中检测相邻 match 间的 gap,将非空 gap 作为 WORD 加入 token 流
|
||||
|
||||
---
|
||||
|
||||
## 第六轮:第 6 轮全面审计修复(12 项)
|
||||
|
||||
**日期**:2026-07-27
|
||||
**数量**:12 项(6 项已在前轮中应用 + 6 项新增)
|
||||
**触发**:4 Agent 并行审计(Parser/Engine + API/Validation + Frontend + Integration)
|
||||
|
||||
### P6-1: Title/Abstract 字段标签大小写不匹配
|
||||
|
||||
- **文件**:`pubmed_query_parser.py` `_FIELD_TAG_MAP` L54
|
||||
- **问题**:`_FIELD_TAG_MAP` 只有 `"Title/Abstract"` 键,但解析器 `.upper()` 产生 `"TITLE/ABSTRACT"`,导致查表失败,该字段标签退化到 `plain_terms`(走 "all" 路径,结果正确但掩盖了 bug)
|
||||
- **根因**:初始化 `FieldTagMapping` 时只写了原始大小写
|
||||
- **修复**:增加 `"TITLE/ABSTRACT"` 大写键值对映射到 "all"
|
||||
|
||||
### P6-2: 末尾 OR 产生空 Term(BUG-2)
|
||||
|
||||
- **文件**:`pubmed_query_parser.py` `_parse_or_expr()` L466
|
||||
- **问题**:`cancer OR ` 末尾操作符导致解析器尝试读取空 token,生成 `Term(text="")`,引起 `plainto_tsquery("english", "")` 报错
|
||||
- **根因**:OR 后无表达式时,解析器仍尝试调用 `_parse_and_expr`,最终生成空 term
|
||||
- **修复**:在 `_parse_or_expr` 中,advance 后检查 EOF 并 break
|
||||
|
||||
### P6-3: PubMed 降级路径 field 标签 regex 未覆盖 `/`(BUG-11)
|
||||
|
||||
- **文件**:`search_engine.py` L222
|
||||
- **问题**:`re.sub(r'\[[\w-]+\]', '', query)` —— `[\w-]` 不含 `/`,`[Title/Abstract]` 不会被擦除,留在降级查询中作为普通文本
|
||||
- **根因**:字符类缺 `/`
|
||||
- **修复**:`[\w-]` → `[\w/-]`
|
||||
|
||||
### P6-4: ATM 展开未剥离括号(Flat Text BUG 5)
|
||||
|
||||
- **文件**:`search_engine.py` L284
|
||||
- **问题**:`_atm_query` 仅执行 `replace('"', '').replace("'", '')`,未去除 `(` 和 `)`。`(lung cancer)` 作为 ATM 查询导致 `expand_atm` 搜索到 ` (lung cancer)` 而非 `lung cancer`,可能零匹配
|
||||
- **修复**:增加 `replace('(', '').replace(')', '')`
|
||||
|
||||
### P6-5: 中文 MeSH name_zh 查询无 LIMIT(Flat Text BUG 4)
|
||||
|
||||
- **文件**:`search_engine.py` L229-235
|
||||
- **问题**:`GlobalTag.name_zh.ilike(...)` 可能返回大量匹配(如 "癌"),导致 subquery 膨胀
|
||||
- **修复**:追加 `.limit(100)` 限制
|
||||
|
||||
### P6-6: year_from / year_to 使用 falsy 检测(BUG-15)
|
||||
|
||||
- **文件**:`search_engine.py` L312-315
|
||||
- **问题**:`if year_from:` 使 `year_from=0` 被当作假值跳过(0 不是有效年份,但语义上 "0" 应被忽略;改为 `is not None` 更安全)
|
||||
- **根因**:Python falsy 检测对于 int 含 0
|
||||
- **修复**:`if year_from:` → `if year_from is not None:`
|
||||
|
||||
### P6-7: `_parse_range` 混合类型日期范围无反向交换(BUG-8)
|
||||
|
||||
- **文件**:`pubmed_query_parser.py` _parse_range L601-606
|
||||
- **问题**:`2026:2024-01-01[DP]` 不触发任何 swap(一个纯数字一个不是),导致 `year_from=2026`, `date_to=2024-01-01`(空范围)
|
||||
- **根因**:反向 swap 条件只处理两端同类型
|
||||
- **修复**:增加第三条件 `_start_is_digit and not _end_is_digit and int(start_val) > int(end_val[:4])`
|
||||
|
||||
### P6-8: Tokeniser 内部 gap 字符恢复(BUG-7 补充)
|
||||
|
||||
- **文件**:`pubmed_query_parser.py` tokenise L150
|
||||
- **问题**:无 gap 处理时部分特殊字符丢失
|
||||
- **修复**:记录 `last_end`,gap 中的非空白字符作为 WORD 输出
|
||||
|
||||
### P6-9: `[MH:noexp]` 顶层支持(F1)
|
||||
|
||||
- **文件**:`search_engine.py` _pubmed_conditions L660-674
|
||||
- **状态**:已在第一阶段实现,审计确认正确(按 `_noexp` 标志分组处理)
|
||||
|
||||
### P6-10: 组内 NOT 语义 De Morgan(F2)
|
||||
|
||||
- **文件**:`search_engine.py` _pubmed_conditions L922-941
|
||||
- **状态**:已在第二阶段实现,审计确认正确(`all_not` 标志 → `not_(combined)` 包裹)
|
||||
|
||||
### P6-11: `_parse_not_expr` 递归支持(F3)
|
||||
|
||||
- **文件**:`pubmed_query_parser.py` L498-509
|
||||
- **状态**:已在第五阶段实现,审计确认正确(递归调用 `_parse_not_expr`)
|
||||
|
||||
### P6-12: 前端日期发送 + restoreFromUrl(F4+F5)
|
||||
|
||||
- **文件**:`SearchView.vue` L300-302、`HomeView.vue` L364-367
|
||||
- **状态**:已在第五阶段实现,审计确认正确(custom range 已发送年/月参数、restoreFromUrl 已覆盖 sort/field/retracted/negative)
|
||||
|
||||
---
|
||||
|
||||
## 各轮变更摘要
|
||||
|
||||
| 维度 | 第一轮 | 第二轮 | 第三轮 | 第四轮 | 第五轮 |
|
||||
|------|--------|--------|--------|--------|--------|
|
||||
| 修复数 | 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]` | — |
|
||||
| 性质 | 从零搭建 | 审计修复 | 深度审计修复 | 字段补全 |
|
||||
| 维度 | 第一轮 | 第二轮 | 第三轮 | 第四轮 | 第五轮 | 第六轮 |
|
||||
|------|--------|--------|--------|--------|--------|--------|
|
||||
| 修复数 | 34 | 8 | 14 | 11 | 4 | 12 |
|
||||
| 后端文件变更 | 全部 | engine + parser | engine + parser + api | engine + parser | parser | engine + parser |
|
||||
| 前端文件变更 | SearchView + HomeView + Card | SearchView + HomeView + Panel | SearchView + HomeView | 0 | 0 | 0 |
|
||||
| 测试变更 | 新增 | +6 项 | 已有覆盖 | 0 | 0 | 0 |
|
||||
| 新功能 | `[MH]`/`[EDAT]`/`[AD]`/`[LA]` 等 | — | — | `[GEN]`/`[PMC]`/`[Title/Abstract]` | — | — |
|
||||
| 性质 | 从零搭建 | 审计修复 | 深度审计修复 | 字段补全 | 审计修复 | 深度审计修复 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user