fix: 第23轮搜索审计修复 — De Morgan组内日期/空查询守卫/NULL JSONB等7项
- R23-1 (CRITICAL): Tokenizer尾随间隙捕获误缩进,导致全部field标签失效
- R23-2 (LOW): _parse_range字段归一化
- R23-3 (MEDIUM): YYYY-M单月日期不匹配
- R23-4 (MEDIUM): has_not未检查group_negated
- R23-5 (CRITICAL): NOT组内日期范围De Morgan错误,新增_build_date_cond_from_pp()
- R23-6 (HIGH): 空查询返回全部文献,增加text("FALSE")守卫
- R23-7 (MEDIUM): NULL JSONB/TEXT + NOT排除NULL行
1007 tests passed
This commit is contained in:
@@ -1667,3 +1667,67 @@
|
||||
| 全量测试套件 | **1006** | 全部通过(含前 15 轮 287 项搜索专项 + 719 项通用测试) |
|
||||
|
||||
> **预存失败(13 项)**:9 项 `feed_engine` `StopAsyncIteration`(测试数据缺失) + 4 项 `pubmed_api` `_tag_article` import(函数已移入 pipeline)
|
||||
|
||||
---
|
||||
|
||||
## Round 23:第 18 次全面审计修复(2026-07-28)
|
||||
|
||||
### 审计发现总览
|
||||
|
||||
4 路并行审计 agent 覆盖:回归检查、搜索引擎代码、解析器/分词器、前端集成。发现 8 个新 bug + 1 个回归 bug(R23-1 缩进错误)。
|
||||
|
||||
### Bug-R23-1 (CRITICAL): Tokenizer 尾随字符捕获在循环内
|
||||
|
||||
- **文件**:`pubmed_query_parser.py:287-291`
|
||||
- **根因**:尾随间隙捕获代码缩进在 `for m in _TOKEN_RE.finditer()` 循环体内,每匹配一个 token 后都会执行。对 `36261522[PMID]`:匹配 NUMBER 后 `[PMID]` 被误判为"间隙"加入 WORD 列表,FIELD 被跳过。导致全部 field 标签失效。
|
||||
- **修复**:缩进外移一级,仅在所有 match 结束后运行。
|
||||
|
||||
### Bug-R23-2 (LOW): `_parse_range` 无字段归一化
|
||||
|
||||
- **文件**:`pubmed_query_parser.py:876-880`
|
||||
- **根因**:range 语法 `NUMBER:NUMBER[AU]` → `field="AU"` 未映射为 `"author"`。
|
||||
- **修复**:字段标签解析后调用 `_normalize_field_label()`。
|
||||
|
||||
### Bug-R23-3 (MEDIUM): YYYY-M 单月日期不匹配
|
||||
|
||||
- **文件**:`pubmed_query_parser.py:1021-1025`
|
||||
- **根因**:正则 `\d{2}` 需恰好 2 位,`2024-1[DP]` 不匹配 → 没补 -01。
|
||||
- **修复**:改为 `\d{1,2}` + lambda 零填充。
|
||||
|
||||
### Bug-R23-4 (MEDIUM): `has_not` 未检查 `group_negated`
|
||||
|
||||
- **文件**:`pubmed_query_parser.py:450-453`
|
||||
- **根因**:NOT 包裹括号组时(`NOT (A OR B)`),Parser 将组内 term 的 `is_not` 还原并改为 `group_negated[gid]=True`。但 `has_not` 只检查 `t.is_not`,不检查 `group_negated`。
|
||||
- **修复**:添加 `any(result.group_negated)`。
|
||||
|
||||
### Bug-R23-5 (CRITICAL): NOT 组内日期范围的 De Morgan 错误
|
||||
|
||||
- **文件**:`search_engine.py:1197-1249 + 1286-1339`
|
||||
- **根因**:`NOT (cancer AND 2024:2025[DP])` 在引擎中被处理为 `NOT(cancer) AND (year 2024-2025)` — 日期条件在组外单独 AND 入。但正确 De Morgan 是 `NOT(cancer AND date) = NOT(cancer) OR NOT(date)`。根本原因是日期范围在组外作为顶层 AND 条件构建,不受组内 NOT 影响。
|
||||
- **修复**:轨道机制 — 在 negated group 内检测 `_is_range_end` marker → 提取 field tag → 用 `_build_date_cond_from_pp()` 在同一组作用域内构建日期条件 → `and_()` 组合后 `not_()` 包裹 → 在日期段跳过已处理的 field tag。新增 `AdvancedSearchEngine._build_date_cond_from_pp()` 静态方法。
|
||||
- **影响范围**:所有 field tag 的日期范围(DP/EDAT/CRDT/MHDA/LR/DCOM/DEP)在 NOT 组内均正确。
|
||||
|
||||
### Bug-R23-6 (HIGH): 空查询返回全部文献
|
||||
|
||||
- **文件**:`search_engine.py:674-675`
|
||||
- **根因**:`conditions` 列表为空时跳过 WHERE 子句,全表扫描返回。
|
||||
- **修复**:添加 `elif not _keyset_cond: q = q.where(text("FALSE"))`。
|
||||
|
||||
### Bug-R23-7 (MEDIUM): NULL JSONB/TEXT + NOT 交互
|
||||
|
||||
- **文件**:`search_engine.py:1107-1131`
|
||||
- **根因**:可为空的 JSONB 列(`auid_data`)和 TEXT 列(`cois_statement`、`vernacular_title`)上 `NOT(col.contains(...))` 对 NULL 行求值为 NULL 而非 TRUE → NULL 行被排除,但 NOT 语义应为包含 NULL。
|
||||
- **修复**:对 `auid_data`、`cois_statement`、`vernacular_title` 的 NOT 条件添加 `or_(col.is_(None))` 包装。
|
||||
|
||||
### 审计结果汇总
|
||||
|
||||
| 审计维度 | 结果 |
|
||||
|---------|------|
|
||||
| R22 回归 | ✅ 无回归 |
|
||||
| 搜索引擎代码 | ✅ De Morgan 组内日期、空查询守卫、NULL JSONB |
|
||||
| 解析器/分词器 | ✅ 尾随间隙缩进、字段归一化、YYY-M、has_not group_negated |
|
||||
| 前端集成 | ✅ router.replace 标记已知 |
|
||||
|
||||
### 测试覆盖
|
||||
|
||||
**1007 tests passed**(全量套件,含全部前 22 轮 248 项搜索专项 + 通用测试)
|
||||
|
||||
Reference in New Issue
Block a user