fix: 第29轮搜索审计修复 — 缺失导入/中文精确短语ILIKE/子组日期标记/开区间范围/NULL安全等8项
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

This commit is contained in:
34047007@qq.com
2026-07-29 06:16:11 +08:00
parent 2b085d3445
commit 475d6bb80c
3 changed files with 234 additions and 7 deletions
+82
View File
@@ -2155,3 +2155,85 @@ if _PARTIAL_DATE_RE.match(end_val):
- ✅ **1007 tests passed, 0 failed**
- 全部搜索测试通过(parser 36 + search engine 集成测试)
- 无回归
## R29 (2026-07-29): 第四轮并行审计修复
### 审计范围
4 路并行 agent,分别覆盖:
1. `pubmed_query_parser.py` 语法正确性(递归下降解析器)
2. `search_engine.py` 条件构建(_pubmed_conditions 全字段路径)
3. 纯文本降级路径(_is_flat_text 决策、缓存键、引号短语、facet 一致性)
4. 前端搜索参数映射(前后端参数完整性、URL 同步、分页)
### R29-1 (P0): `_expand_partial_date` 未导入
**文件**[search_engine.py:1696](backend/app/services/search_engine.py#L1696)
**根因**`_single_term_condition()` 的 YYYY-MM 部分日期分支调用 `_expand_partial_date()` 但该函数未从 `pubmed_query_parser` 导入。导入语句只导入了 `_PARTIAL_DATE_RE` 和 `_validate_date_str`。
**影响**:组内 YYYY-MM 日期(如 `(2024-06[DP] OR cancer)`)触发 `NameError`,被 `except Exception` 吞没后降级到纯文本。
**修复**:添加 `_expand_partial_date` 到 inline import。
### R29-2 (P0): 非日期范围 field 标签混入文本
**文件**[pubmed_query_parser.py:1111-1115](backend/app/services/pubmed_query_parser.py#L1111)
**根因**`_parse_range()` 对非日期范围(如 `term1:term2[MH]`)的 fallthrough 路径将 `[MH]` 追加入 term 文本,导致 `text="term1:term2[MH]"` 而 `Term.field="MH"` 已正确设置。引擎按 `field` 路由(正确到 MeSH 路径),但 `text` 中的 `[MH]` 被作为字面搜索词。
**修复**:去除 `[{field}]` 文本拼接,只保留 `start_val:end_val`。
### R29-3 (P0): 中文精确短语缺少 title/abstract ILIKE
**文件**[search_engine.py:1789-1798](backend/app/services/search_engine.py#L1789)
**根因**`_field_condition("all", exact=True)` 分支使用 `phraseto_tsquery('english', ...)` 无法匹配非英文文本(如中文)。缺少 title/abstract ILIKE 回退。非精确分支(line 1832)正确包含这些 ILIKE。
**影响**`"肺癌"[TIAB]` 等中文精确短语返回零结果。
**修复**:在 exact 分支中添加 `title.ilike(pat)` 和 `abstract.ilike(pat)`。
### R29-4 (P1): 子组日期范围标记被丢弃
**文件**[search_engine.py:1313-1314](backend/app/services/search_engine.py#L1313)
**根因**:子组(sub-group,由 `_parse_or_expr` AND 集群 OR 拆分产生)的日期范围标记在 child processing 循环中被 `continue` 跳过。
**影响**`(A OR B AND 2020:2024[DP])` 等查询中日期范围丢失。
**修复**:子组循环中添加 `_build_marker_condition` + NULL 安全 NOT,逻辑与父组一致。
### R29-5 (P1): 开区间日期范围不支持
**文件**[pubmed_query_parser.py:930-938](backend/app/services/pubmed_query_parser.py#L930)
**根因**`:2024[DP]`(截止到 2024)和 `2024:[DP]`(从 2024 起)的 COLON 在首个位置或无第二个 NUMBER 的模式不被范围检测匹配。
**修复**:在 `_parse_atom` 中增加 `:NUMBER[FIELD]` 和 `NUMBER:[FIELD]` 检测,路由到新方法 `_handle_date_range_edge()`,正确设置全局属性、标记和 `_neg_single_dates`。
### R29-6 (P2): field_map NOT 条件缺少 NULL 安全
**文件**[search_engine.py:874-881](backend/app/services/search_engine.py#L874)
**根因**`[TI]`/`[AB]`/`[AU]` 等字段标签的否定词使用裸 `not_(cond)`,导致字段为 NULL 的行被错误排除。
**修复**:添加 `_NULL_SAFE_COL_MAP`,对每个字段的否定条件使用 `or_(not_(cond), col.is_(None))`。
### R29-7 (LOW): PRISMA 导出正则不匹配含空格的字段标签
**文件**[pubmed_query_parser.py:1301](backend/app/services/pubmed_query_parser.py#L1301)
**修复**`[\w/:]+` → `[\w/: -]+` 以匹配 `[MeSH Terms]`、`[Date - Publication]` 等。
### R29-8 (LOW): `is_free_full_text` 在 `is_oa` 已设时被跳过
**文件**[search_engine.py:563](backend/app/services/search_engine.py#L563)
**修复**:移除 `is_oa is None` 守卫,`is_free_full_text` 始终独立生效。
## 验证
-**1007 tests passed, 0 failed**
- 全部搜索测试通过
- 无回归