diff --git a/backend/app/services/pubmed_query_parser.py b/backend/app/services/pubmed_query_parser.py index ddbef95..96deea1 100644 --- a/backend/app/services/pubmed_query_parser.py +++ b/backend/app/services/pubmed_query_parser.py @@ -473,15 +473,36 @@ class PubmedQueryParser: result.plain_terms.append(term) def _parse_or_expr(self, result: ParsedPubmedQuery) -> list[Term]: - """or_expr → and_expr (OR and_expr)*""" - left = self._parse_and_expr(result) + """or_expr → and_expr (OR and_expr)* + + 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: + had_or = True self.advance() if self.peek().type == TokenType.EOF: break # trailing OR, ignore silently - right = self._parse_and_expr(result) - left.extend(right) - return left + clusters.append(self._parse_and_expr(result)) + + 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: """Check if token could start a primary expression.""" diff --git a/backend/app/services/search_engine.py b/backend/app/services/search_engine.py index cac5768..50775e8 100644 --- a/backend/app/services/search_engine.py +++ b/backend/app/services/search_engine.py @@ -1195,6 +1195,12 @@ class AdvancedSearchEngine: cast(GlobalLiterature.pmid, String).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)) @staticmethod