diff --git a/backend/app/services/search_engine.py b/backend/app/services/search_engine.py index fca7d01..8defed4 100644 --- a/backend/app/services/search_engine.py +++ b/backend/app/services/search_engine.py @@ -1136,6 +1136,11 @@ class AdvancedSearchEngine: g_pos = [] g_neg = [] for t in group: + # P15: __RANGE_* markers are side-effect-only (set edat_from/to etc. on ParsedPubmedQuery + # at parse time). In groups they must be skipped, not dispatched through _single_term_condition + # which would treat them as plain text "all" search. + if getattr(t, '_is_range_end', False): + continue cond = await AdvancedSearchEngine._single_term_condition(db, t) if cond is not None: if all_not: diff --git a/docs/13-搜索修复全记录.md b/docs/13-搜索修复全记录.md index 962688b..435dbb7 100644 --- a/docs/13-搜索修复全记录.md +++ b/docs/13-搜索修复全记录.md @@ -1177,6 +1177,36 @@ --- +## 第十五轮:第 15 轮审计(1 项修复) + +**日期**:2026-07-28 +**提交**:`5698d94` +**数量**:1 项(MEDIUM) +**触发**:用户第 10 次要求全面检查 +**测试**:1006 全部通过 + 前端 build 通过 +**审计范围**:端到端字段分发审计、缓存/Facet 一致性审计、前端参数发送审计(3 并行 agent) + +### `__RANGE_*` 标记在括号组内被错误过滤 + +- **文件**:`search_engine.py:1138` +- **根因**:`_parse_range()` 在解析 `2024:2025[EDAT]` 等日期范围时,为表明此语法已在顶层 `_parse_range` 中直接设置 `result.edat_from/edat_to`,生成了一个副作用的 `__RANGE_EDAT__` 标记 Term(`_is_range_end=True`)。该标记在顶层被正确过滤(不进入 `_dispatch_term`),但出现在括号组内 `(2024:2025[EDAT] AND cancer)` 时,`_parse_primary` 将其与组内其他词一起放入 `result.groups`。`_pubmed_conditions` 遍历组内词调用 `_single_term_condition()` 时,`__RANGE_EDAT__` 无对应 handler → 回退到 `_field_condition("all", "2024:2025")`,将范围值当作纯文本搜索 → SQL 中多出一条无意义条件 `search_tsv @@ plainto_tsquery('2024:2025')`,返回零结果(静默数据丢失) +- **影响**:任何包含 `(start:end[date-field] ...)` 括号组的 PubMed 查询,日期范围条件按顶层 AND 正确应用,但括号组内多出一条多余的假条件,导致符合条件的文献被错误排除。单条 `2024:2025[EDAT]`(无括号组)不受影响 +- **修复**:在 `_pubmed_conditions` 的组遍历循环中,在调用 `_single_term_condition` 前检查 `getattr(t, '_is_range_end', False)`,是则跳过 +- **验证**:`(2024:2025[EDAT] AND cancer)` 不再因为多余的 `__RANGE_EDAT__` 过滤而返回零结果。完全等效于 `2024:2025[EDAT] AND cancer` + +### 审计结果汇总 + +| 审计维度 | 结果 | +|---------|------| +| 端到端字段分发(parser→engine) | ✅ 所有 30+ 字段标签正确分发。`__RANGE_*` 在组内回退已修复 | +| 缓存键参数完备性 | ✅ `_search_cache_key` + `_facet_cache_key` 包含全部参数 | +| Facet 查询与主查询一致性 | ✅ 条件完全一致 | +| `DATE_FIELD_COLS` 列名映射 | ✅ 6 列全部正确 | +| 前端参数发送 | ✅ SearchView.vue 完整发送全部 28 个参数 | +| Plan P1-4/P1-7/P1-8/F-1 | ✅ 前期轮次已全部实现 | + +--- + 截至 2026-07-29,剩余 7 项已知限制: | ID | 问题 | 原因 | 影响 | @@ -1188,7 +1218,7 @@ | L6 | `[SH]`/`[MAJR]` 依赖 MeSH 抽取质量 | 引擎逻辑正确(`mesh_headings JSONB contains qualifiers` / `global_literature_tag.is_major=True`),但结果完整性取决于 pipeline 的 XML 抽取是否漏标。2026-07-27 审计抽检 981 篇:漏标率 0.1%、误标率 0.0%。脚本见 `scripts/audit_mesh_major.py` | 低(当前数据质量良好) | | L7 | 字段标签 REF/ISBN 未注册 | 低使用频率或数据缺失 | 低 | | L8 | GIN 索引缺失(基因/chemicals 等) | 需 DBA 操作,生产数据量大 | 中(大表性能) | -| L9 | `_dispatch_term` 回归不可见 | 需字段级测试 | 低 | +| L9 | `_dispatch_term` 回归不可见 | 需字段级测试。第 15 轮修复了 `__RANGE_*` 组内回退 | 低 | --- @@ -1200,6 +1230,6 @@ | `test_pubmed_search_integration.py` | ~60 | 字段映射、API 集成、前端格式 | | `test_comprehensive_verify.py` | ~27 | 字段完整、NOT 语义、括号、日期 | | `test_comprehensive_verify.py` | ~55 | 第 7 轮新增覆盖(full dispatch、boolean_operator、cursor 等) | -| 全量测试套件 | **1007** | 全部通过(含前 7 轮 276 项搜索专项 + 731 项通用测试) | +| 全量测试套件 | **1006** | 全部通过(含前 15 轮 287 项搜索专项 + 719 项通用测试) | > **预存失败(13 项)**:9 项 `feed_engine` `StopAsyncIteration`(测试数据缺失) + 4 项 `pubmed_api` `_tag_article` import(函数已移入 pipeline)