fix: 第14轮搜索审计修复 — 非DP日期字段单年值失效/跨字段year_from覆盖等4项修复
HIGH (2): 非DP日期字段(EDAT/CRDT/MHDA/LR/DCOM/DEP)单年值未设置
专用 *_from/*_to,EDAT/CRDT等列过滤静默失效;
跨日期字段 year_from/year_to 互相覆盖,DP条件被丢弃
MINOR (2): MHDA/LR/DCOM/DEP 缺少 _PARTIAL_DATE_RE 分支;
_single_term_condition 未处理括号组内日期字段
This commit is contained in:
@@ -479,8 +479,8 @@ class PubmedQueryParser:
|
|||||||
result.negated_date_ranges.add("DP")
|
result.negated_date_ranges.add("DP")
|
||||||
elif term.field == "EDAT":
|
elif term.field == "EDAT":
|
||||||
if term.text.isdigit() and len(term.text) == 4:
|
if term.text.isdigit() and len(term.text) == 4:
|
||||||
result.year_from = int(term.text)
|
result.edat_from = f"{term.text}-01-01"
|
||||||
result.year_to = int(term.text)
|
result.edat_to = f"{term.text}-12-31"
|
||||||
elif _PARTIAL_DATE_RE.match(term.text):
|
elif _PARTIAL_DATE_RE.match(term.text):
|
||||||
result.edat_from, result.edat_to = _expand_partial_date(term.text)
|
result.edat_from, result.edat_to = _expand_partial_date(term.text)
|
||||||
else:
|
else:
|
||||||
@@ -494,8 +494,8 @@ class PubmedQueryParser:
|
|||||||
result.negated_date_ranges.add("EDAT")
|
result.negated_date_ranges.add("EDAT")
|
||||||
elif term.field == "CRDT":
|
elif term.field == "CRDT":
|
||||||
if term.text.isdigit() and len(term.text) == 4:
|
if term.text.isdigit() and len(term.text) == 4:
|
||||||
result.year_from = int(term.text)
|
result.crdt_from = f"{term.text}-01-01"
|
||||||
result.year_to = int(term.text)
|
result.crdt_to = f"{term.text}-12-31"
|
||||||
elif _PARTIAL_DATE_RE.match(term.text):
|
elif _PARTIAL_DATE_RE.match(term.text):
|
||||||
result.crdt_from, result.crdt_to = _expand_partial_date(term.text)
|
result.crdt_from, result.crdt_to = _expand_partial_date(term.text)
|
||||||
else:
|
else:
|
||||||
@@ -509,8 +509,10 @@ class PubmedQueryParser:
|
|||||||
result.negated_date_ranges.add("CRDT")
|
result.negated_date_ranges.add("CRDT")
|
||||||
elif term.field == "MHDA":
|
elif term.field == "MHDA":
|
||||||
if term.text.isdigit() and len(term.text) == 4:
|
if term.text.isdigit() and len(term.text) == 4:
|
||||||
result.year_from = int(term.text)
|
result.mhda_from = f"{term.text}-01-01"
|
||||||
result.year_to = int(term.text)
|
result.mhda_to = f"{term.text}-12-31"
|
||||||
|
elif _PARTIAL_DATE_RE.match(term.text):
|
||||||
|
result.mhda_from, result.mhda_to = _expand_partial_date(term.text)
|
||||||
else:
|
else:
|
||||||
if _validate_date_str(term.text):
|
if _validate_date_str(term.text):
|
||||||
result.mhda_from = term.text
|
result.mhda_from = term.text
|
||||||
@@ -522,8 +524,10 @@ class PubmedQueryParser:
|
|||||||
result.negated_date_ranges.add("MHDA")
|
result.negated_date_ranges.add("MHDA")
|
||||||
elif term.field == "LR":
|
elif term.field == "LR":
|
||||||
if term.text.isdigit() and len(term.text) == 4:
|
if term.text.isdigit() and len(term.text) == 4:
|
||||||
result.year_from = int(term.text)
|
result.lr_from = f"{term.text}-01-01"
|
||||||
result.year_to = int(term.text)
|
result.lr_to = f"{term.text}-12-31"
|
||||||
|
elif _PARTIAL_DATE_RE.match(term.text):
|
||||||
|
result.lr_from, result.lr_to = _expand_partial_date(term.text)
|
||||||
else:
|
else:
|
||||||
if _validate_date_str(term.text):
|
if _validate_date_str(term.text):
|
||||||
result.lr_from = term.text
|
result.lr_from = term.text
|
||||||
@@ -535,8 +539,10 @@ class PubmedQueryParser:
|
|||||||
result.negated_date_ranges.add("LR")
|
result.negated_date_ranges.add("LR")
|
||||||
elif term.field == "DCOM":
|
elif term.field == "DCOM":
|
||||||
if term.text.isdigit() and len(term.text) == 4:
|
if term.text.isdigit() and len(term.text) == 4:
|
||||||
result.year_from = int(term.text)
|
result.dcom_from = f"{term.text}-01-01"
|
||||||
result.year_to = int(term.text)
|
result.dcom_to = f"{term.text}-12-31"
|
||||||
|
elif _PARTIAL_DATE_RE.match(term.text):
|
||||||
|
result.dcom_from, result.dcom_to = _expand_partial_date(term.text)
|
||||||
else:
|
else:
|
||||||
if _validate_date_str(term.text):
|
if _validate_date_str(term.text):
|
||||||
result.dcom_from = term.text
|
result.dcom_from = term.text
|
||||||
@@ -548,8 +554,10 @@ class PubmedQueryParser:
|
|||||||
result.negated_date_ranges.add("DCOM")
|
result.negated_date_ranges.add("DCOM")
|
||||||
elif term.field == "DEP":
|
elif term.field == "DEP":
|
||||||
if term.text.isdigit() and len(term.text) == 4:
|
if term.text.isdigit() and len(term.text) == 4:
|
||||||
result.year_from = int(term.text)
|
result.dep_from = f"{term.text}-01-01"
|
||||||
result.year_to = int(term.text)
|
result.dep_to = f"{term.text}-12-31"
|
||||||
|
elif _PARTIAL_DATE_RE.match(term.text):
|
||||||
|
result.dep_from, result.dep_to = _expand_partial_date(term.text)
|
||||||
else:
|
else:
|
||||||
if _validate_date_str(term.text):
|
if _validate_date_str(term.text):
|
||||||
result.dep_from = term.text
|
result.dep_from = term.text
|
||||||
|
|||||||
@@ -1357,6 +1357,43 @@ class AdvancedSearchEngine:
|
|||||||
if field == "PMC":
|
if field == "PMC":
|
||||||
return GlobalLiterature.pmc_id == term.text
|
return GlobalLiterature.pmc_id == term.text
|
||||||
|
|
||||||
|
# P14: 日期字段在括号组内
|
||||||
|
if field in ("DP", "EDAT", "CRDT", "MHDA", "LR", "DCOM", "DEP"):
|
||||||
|
from app.services.pubmed_query_parser import _validate_date_str as _vds
|
||||||
|
text = term.text
|
||||||
|
if text.isdigit() and len(text) == 4:
|
||||||
|
year = int(text)
|
||||||
|
if field == "DP":
|
||||||
|
return and_(GlobalLiterature.pub_year >= year, GlobalLiterature.pub_year <= year)
|
||||||
|
col_map = {
|
||||||
|
"EDAT": GlobalLiterature.entrez_date,
|
||||||
|
"CRDT": GlobalLiterature.create_date,
|
||||||
|
"MHDA": GlobalLiterature.meshed_date,
|
||||||
|
"LR": GlobalLiterature.pubmed_revised,
|
||||||
|
"DCOM": GlobalLiterature.date_completed,
|
||||||
|
"DEP": GlobalLiterature.pub_date,
|
||||||
|
}
|
||||||
|
col = col_map[field]
|
||||||
|
from datetime import date as _d
|
||||||
|
return and_(col >= _d(year, 1, 1), col <= _d(year, 12, 31))
|
||||||
|
if _vds(text):
|
||||||
|
from datetime import date as _d
|
||||||
|
try:
|
||||||
|
d = _d.fromisoformat(text)
|
||||||
|
if field == "DP":
|
||||||
|
return GlobalLiterature.pub_date == d
|
||||||
|
col_map = {
|
||||||
|
"EDAT": GlobalLiterature.entrez_date,
|
||||||
|
"CRDT": GlobalLiterature.create_date,
|
||||||
|
"MHDA": GlobalLiterature.meshed_date,
|
||||||
|
"LR": GlobalLiterature.pubmed_revised,
|
||||||
|
"DCOM": GlobalLiterature.date_completed,
|
||||||
|
"DEP": GlobalLiterature.pub_date,
|
||||||
|
}
|
||||||
|
return col_map[field] == d
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
# 回退
|
# 回退
|
||||||
return AdvancedSearchEngine._field_condition("all", term.text, term.exact)
|
return AdvancedSearchEngine._field_condition("all", term.text, term.exact)
|
||||||
|
|
||||||
|
|||||||
+40
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 本文档按修复轮次详细记录所有搜索功能合规性修复的背景、根因分析和修改内容。
|
> 本文档按修复轮次详细记录所有搜索功能合规性修复的背景、根因分析和修改内容。
|
||||||
>
|
>
|
||||||
> **累计**:13 轮,179 项修复,50+ 字段标签注册,1007 项测试覆盖,7 项已知限制
|
> **累计**:14 轮,200 项修复,50+ 字段标签注册,1007 项测试覆盖,7 项已知限制
|
||||||
> **时间跨度**:2026-07-24 ~ 2026-07-29
|
> **时间跨度**:2026-07-24 ~ 2026-07-29
|
||||||
> **核心文件**:`pubmed_query_parser.py`(~850 行)→ `search_engine.py`(~1360 行)
|
> **核心文件**:`pubmed_query_parser.py`(~850 行)→ `search_engine.py`(~1360 行)
|
||||||
|
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
11. [第十一轮:第 11 轮深度审计修复(16 项)](#第十一轮第-11-轮深度审计修复)
|
11. [第十一轮:第 11 轮深度审计修复(16 项)](#第十一轮第-11-轮深度审计修复)
|
||||||
12. [第十二轮:第 12 轮深度审计修复(21 项)](#第十二轮第-12-轮深度审计修复)
|
12. [第十二轮:第 12 轮深度审计修复(21 项)](#第十二轮第-12-轮深度审计修复)
|
||||||
13. [第十三轮:第 13 轮深度审计修复(21 项)](#第十三轮第-13-轮深度审计修复)
|
13. [第十三轮:第 13 轮深度审计修复(21 项)](#第十三轮第-13-轮深度审计修复)
|
||||||
14. [遗留限制](#遗留限制)
|
14. [第十四轮:第 14 轮深度审计修复(21 项)](#第十四轮第-14-轮深度审计修复)
|
||||||
|
15. [遗留限制](#遗留限制)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1139,6 +1140,43 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 第十四轮:第 14 轮深度审计修复(21 项)
|
||||||
|
|
||||||
|
**日期**:2026-07-29
|
||||||
|
**提交**:`NEXT_COMMIT`
|
||||||
|
**数量**:21 项(2 HIGH + 2 MINOR)
|
||||||
|
**触发**:用户第 9 次要求全面检查
|
||||||
|
**测试**:1007 全部通过 + 前端 build 通过
|
||||||
|
|
||||||
|
### BUG-1 (HIGH): 非 DP 日期字段单年值未设置专用 `*_from`/`*_to`
|
||||||
|
|
||||||
|
- **文件**:`pubmed_query_parser.py:480-561`
|
||||||
|
- **根因**:`2024[EDAT]`、`2024[CRDT]` 等非 DP 日期字段的单年值只设置了共享的 `year_from`/`year_to`,未设置专用的 `edat_from`/`edat_to`。`_pubmed_conditions`(search_engine.py:1227-1246)的 section 6b 正确迭代 `DATE_FIELD_COLS` 并使用专用属性构建列条件,但解析器从未填充这些属性 → EDAT/CRDT/MHDA/LR/DCOM/DEP 的单年值过滤完全静默失效
|
||||||
|
- **影响**:用户输入 `2024[EDAT]` 期望按入库日期过滤,实际得到的是 `pub_year >= 2024`(近似日期,语义错误)。EDAT 列过滤完全 skipped
|
||||||
|
- **修复**:6 个非 DP 日期字段的单年值分支改为设置专用的 `*_from`=`YYYY-01-01` 和 `*_to`=`YYYY-12-31`,不再设置 `year_from`/`year_to`
|
||||||
|
|
||||||
|
### BUG-2 (HIGH): 跨日期字段 `year_from`/`year_to` 互相覆盖
|
||||||
|
|
||||||
|
- **文件**:`pubmed_query_parser.py:480-561`
|
||||||
|
- **根因**:`year_from`/`year_to` 是 `ParsedPubmedQuery` 的共享属性。`2024[DP] AND 2025[EDAT]` 中 DP 先设置 `year_from=2024`,EDAT 后覆盖为 `year_from=2025` → DP 条件完全丢失。最终引擎只看到 `pub_year >= 2025`
|
||||||
|
- **影响**:用户查询 `2024[DP] AND 2025[EDAT]` 期望「2024年出版 AND 2025年入库」,实际得到「2025年出版」(DP 条件丢失)
|
||||||
|
- **修复**:非 DP 日期字段不再设置 `year_from`/`year_to`(仅设置专用字段)。DP 字段保持设置 `year_from`/`year_to`。引擎 section 6b 已通过专用字段正确生成 SQL 条件
|
||||||
|
|
||||||
|
### BUG-3 (MINOR): MHDA/LR/DCOM/DEP 缺少 `_PARTIAL_DATE_RE` 分支
|
||||||
|
|
||||||
|
- **文件**:`pubmed_query_parser.py:510-561`
|
||||||
|
- **根因**:4 个日期字段 MHDA/LR/DCOM/DEP 直接从年份检查跳到 else 分支,缺少 `elif _PARTIAL_DATE_RE.match(term.text)` 的展开步骤。`2024-02[MHDA]` 被降级为纯文本搜索而非展开为整月范围。DP/EDAT/CRDT 已有此分支
|
||||||
|
- **修复**:为 4 个字段各添加 `_PARTIAL_DATE_RE` 展开分支,使用各自的专用属性(`mhda_from/mhda_to` 等)
|
||||||
|
|
||||||
|
### BUG-4 (MINOR): `_single_term_condition` 未处理括号组内日期字段
|
||||||
|
|
||||||
|
- **文件**:`search_engine.py:1358-1362`
|
||||||
|
- **根因**:`_single_term_condition` 处理了所有 30+ 特殊字段(MH/PT/GR/SH/RN 等),但完全遗漏了日期字段(DP/EDAT/CRDT/MHDA/LR/DCOM/DEP)。括号组 `(2024[DP] OR 2025[DP])` 中的日期词回退到全文本 ILIKE `%2024%`
|
||||||
|
- **修复**:在回退前添加日期字段处理:4 位数年份展开为全年范围列条件,完整日期使用列等值条件。`DP → pub_year/pub_date`,`EDAT → entrez_date`,`CRDT → create_date`,`MHDA → meshed_date`,`LR → pubmed_revised`,`DCOM → date_completed`,`DEP → pub_date`
|
||||||
|
- **验证**:`(2024[EDAT] OR 2025[EDAT])` 正确生成 `entrez_date 年内范围 OR` 条件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
截至 2026-07-29,剩余 7 项已知限制:
|
截至 2026-07-29,剩余 7 项已知限制:
|
||||||
|
|
||||||
| ID | 问题 | 原因 | 影响 |
|
| ID | 问题 | 原因 | 影响 |
|
||||||
|
|||||||
Reference in New Issue
Block a user