fix: D2 中文搜索 + D3 混合布尔语义 — PubMed 搜索路径修复
D2: PubMed 路径搜索中文(不带字段标签)时,plainto_tsquery("english")
对中文返回空,导致零结果。新增 ILIKE fallback 检测中文字符。
D3: A OR B AND C 被解析器拉平为 A OR B OR C。修改 _parse_or_expr
保留 AND cluster 分组,引擎正确产生产出 A OR (B AND C)。
This commit is contained in:
@@ -473,15 +473,36 @@ class PubmedQueryParser:
|
|||||||
result.plain_terms.append(term)
|
result.plain_terms.append(term)
|
||||||
|
|
||||||
def _parse_or_expr(self, result: ParsedPubmedQuery) -> list[Term]:
|
def _parse_or_expr(self, result: ParsedPubmedQuery) -> list[Term]:
|
||||||
"""or_expr → and_expr (OR and_expr)*"""
|
"""or_expr → and_expr (OR and_expr)*
|
||||||
left = self._parse_and_expr(result)
|
|
||||||
|
P7-D3: Preserve AND clusters when OR is mixed (e.g. ``A OR B AND C``).
|
||||||
|
Collect each ``_parse_and_expr`` result as a separate cluster. When OR
|
||||||
|
is found AND a cluster has >1 term (implicit AND), wrap it in a group
|
||||||
|
so the engine produces ``A OR (B AND C)`` instead of ``A OR B OR C``.
|
||||||
|
"""
|
||||||
|
clusters = [self._parse_and_expr(result)]
|
||||||
|
had_or = False
|
||||||
while self.peek().type == TokenType.OR:
|
while self.peek().type == TokenType.OR:
|
||||||
|
had_or = True
|
||||||
self.advance()
|
self.advance()
|
||||||
if self.peek().type == TokenType.EOF:
|
if self.peek().type == TokenType.EOF:
|
||||||
break # trailing OR, ignore silently
|
break # trailing OR, ignore silently
|
||||||
right = self._parse_and_expr(result)
|
clusters.append(self._parse_and_expr(result))
|
||||||
left.extend(right)
|
|
||||||
return left
|
if not had_or:
|
||||||
|
return clusters[0] if clusters else []
|
||||||
|
|
||||||
|
# OR present: group any AND-cluster with >1 term
|
||||||
|
all_terms: list[Term] = []
|
||||||
|
for cluster in clusters:
|
||||||
|
if len(cluster) > 1 and not any(t.group_id >= 0 for t in cluster):
|
||||||
|
gid = len(result.groups)
|
||||||
|
for t in cluster:
|
||||||
|
t.group_id = gid
|
||||||
|
result.groups.append(cluster)
|
||||||
|
result.group_operators.append("and")
|
||||||
|
all_terms.extend(cluster)
|
||||||
|
return all_terms
|
||||||
|
|
||||||
def _is_primary_start(self, token: Token) -> bool:
|
def _is_primary_start(self, token: Token) -> bool:
|
||||||
"""Check if token could start a primary expression."""
|
"""Check if token could start a primary expression."""
|
||||||
|
|||||||
@@ -1195,6 +1195,12 @@ class AdvancedSearchEngine:
|
|||||||
cast(GlobalLiterature.pmid, String).ilike(like_val),
|
cast(GlobalLiterature.pmid, String).ilike(like_val),
|
||||||
GlobalLiterature.doi.ilike(like_val),
|
GlobalLiterature.doi.ilike(like_val),
|
||||||
)
|
)
|
||||||
|
# P7-D2: Chinese → ILIKE fallback (tsvector is English-only)
|
||||||
|
if re.search(r'[一-鿿㐀-䶿豈-]', term):
|
||||||
|
return or_(
|
||||||
|
GlobalLiterature.title.ilike(like_val),
|
||||||
|
GlobalLiterature.abstract.ilike(like_val),
|
||||||
|
)
|
||||||
return GlobalLiterature.search_tsv.op("@@")(func.plainto_tsquery("english", term))
|
return GlobalLiterature.search_tsv.op("@@")(func.plainto_tsquery("english", term))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user