feat: PubMed搜索合规审计 Phase 5-7 全部修复
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

P1-2: [SB][STAT][UID][DEP][MESH]字段支持
P1-4: [MH:noexp]解析+抑制树展开
P3: 前端date_to/P3-1、CJK高亮/P3-3、extractPlainText/P3-4、precision_mode移除/P3-6、loadMore sort/P3-5
P4-1: 词数上限50→100
P4-2: retracted参数清理
This commit is contained in:
34047007@qq.com
2026-07-27 09:03:19 +08:00
parent 62ca8fa6b8
commit 5f69277eba
7 changed files with 111 additions and 30 deletions
+24 -2
View File
@@ -23,7 +23,7 @@ from dataclasses import dataclass, field
from enum import Enum, auto
# ─── 查询复杂度限制 ───
MAX_TERMS = 50 # 与 API 层 check_query_complexity 的 50 词限制对齐
MAX_TERMS = 100 # P4-1: 放宽到 100 词(原 50 词
MAX_PAREN_DEPTH = 10 # 括号嵌套最深层数
@@ -42,6 +42,7 @@ _FIELD_TAG_MAP: dict[str, str] = {
"LAU": "author",
"TW": "all",
"OT": "all",
"MESH": "MH", # P1-2: [MESH] 是 [MH] 的别名
# 新增标量字段
"LA": "language",
"VI": "volume",
@@ -55,23 +56,30 @@ _SPECIAL_FIELDS = {
"MH", "MAJR", "PT", "DP", "PMID", "DOI",
# 日期字段
"EDAT", "CRDT", "MHDA", "LR", "DCOM",
# P1-2: 电子出版日期
"DEP",
# JSONB 字段
"GR", "SH", "RN", "NM", "SI", "PA",
# 待补充抽取的字段(标记为特殊以便未来实现)
"AUID", "COIS", "ED", "IR", "PS", "PUBN", "TT",
# P1-2: 新字段
"SB", "STAT", "UID",
}
# 支持日期范围语法的字段
_DATE_RANGE_FIELDS = {"DP", "EDAT", "CRDT", "MHDA", "LR", "DCOM"}
_DATE_RANGE_FIELDS = {"DP", "EDAT", "CRDT", "MHDA", "LR", "DCOM", "DEP"}
# 所有合法字段标签(PubMed 全量字段)
# P1-1: 移除了 BOOK/FILTER/ISBN(未实现,降级为 plain text 不如报错透明)
_ALL_FIELD_TAGS = {
"AB", "AD", "AU", "CN", "FAU", "AUID", "LAU", "COIS",
"DCOM", "CRDT", "EDAT", "MHDA", "LR", "DP", "DOI",
"DEP", # P1-2: Date of Electronic Publication
"RN", "ED", "GR", "IR", "IP",
"TA", "JT", "LA", "LID", "MAJR", "SH", "MH", "MH:NOEXP", "OT", "PG",
"PA", "PT", "PMID", "PUBN", "SI", "PS", "NM", "TW",
"SB", "STAT", "UID", # P1-2: Subset, Status, UID
"MESH", # P1-2: [MH] 别名
"TI", "TIAB", "TT", "VI",
}
@@ -187,6 +195,8 @@ class ParsedPubmedQuery:
lr_to: str | None = None
dcom_from: str | None = None # [DCOM]
dcom_to: str | None = None
dep_from: str | None = None # P1-2: [DEP]
dep_to: str | None = None
# 特殊字段(存储 Term 以保留 is_not 标志)
mesh_terms: list[Term] = field(default_factory=list) # [MH] (was list[str])
majr_terms: list[Term] = field(default_factory=list) # [MAJR] (was list[str])
@@ -207,6 +217,9 @@ class ParsedPubmedQuery:
auid_terms: list[Term] = field(default_factory=list) # [AUID]
cois_terms: list[Term] = field(default_factory=list) # [COIS]
tt_terms: list[Term] = field(default_factory=list) # [TT]
sb_terms: list[Term] = field(default_factory=list) # P1-2: [SB] Subset
stat_terms: list[Term] = field(default_factory=list) # P1-2: [STAT] Status
uid_terms: list[Term] = field(default_factory=list) # P1-2: [UID] PMID/DOI 统一
plain_terms: list[Term] = field(default_factory=list) # no field tag
boolean_operator: str = "and" # "and" | "or" | "mixed"
has_not: bool = False # contains NOT
@@ -349,6 +362,8 @@ class PubmedQueryParser:
pass
elif term.field == "__RANGE_DCOM__":
pass
elif term.field == "__RANGE_DEP__":
pass
elif term.field == "ED":
result.ed_terms.append(term)
elif term.field == "IR":
@@ -363,6 +378,12 @@ class PubmedQueryParser:
result.cois_terms.append(term)
elif term.field == "TT":
result.tt_terms.append(term)
elif term.field == "SB":
result.sb_terms.append(term)
elif term.field == "STAT":
result.stat_terms.append(term)
elif term.field == "UID":
result.uid_terms.append(term)
else:
result.plain_terms.append(term)
@@ -494,6 +515,7 @@ class PubmedQueryParser:
"MHDA": ("mhda_from", "mhda_to", None, None, "__RANGE_MHDA__"),
"LR": ("lr_from", "lr_to", None, None, "__RANGE_LR__"),
"DCOM": ("dcom_from", "dcom_to", None, None, "__RANGE_DCOM__"),
"DEP": ("dep_from", "dep_to", None, None, "__RANGE_DEP__"), # P1-2
}
date_attr, date_attr_to, yr_from_attr, yr_to_attr, marker_field = attr_map[field]
# 反向范围自动交换(如 2026:2024[DP] → 2024:2026[DP]