diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c98b1..203696c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Не выпущено - Backend обновлён до `0.10.3`: запрос о налоге на прибыль поднимает действующий - Налоговый кодекс; релевантность текста и названия оценивается раздельно. + Налоговый кодекс; точное совпадение фразы дополнительно усиливает результат. - Backend обновлён до `0.10.2`: действующие документы ранжируются выше, а утратившие силу — после остальных; из лаборатории удалён пустой блок фрагмента. - Backend обновлён до `0.10.1`: поисковая выдача по регистрации больше не diff --git a/backend/search/query.py b/backend/search/query.py index ce95748..40690e9 100644 --- a/backend/search/query.py +++ b/backend/search/query.py @@ -100,17 +100,22 @@ def tax_profit_clauses(language: str, query: str) -> list[dict]: def build_search_query(language: str, query: str) -> dict: registration_clauses = company_registration_clauses(language, query) intent_clauses = [*registration_clauses, *tax_profit_clauses(language, query)] - operator = "OR" if registration_clauses else "AND" full_text = [ - clause + { + "multi_match": { + "query": variant, + "fields": [f"document_name_{language}^4", f"text_{language}"], + "type": "cross_fields", + "operator": "OR" if registration_clauses else "AND", + } + } for variant in query_variants(language, query) - for clause in ( - {"match": {f"text_{language}": {"query": variant, "operator": operator}}}, - {"match": {f"document_name_{language}": {"query": variant, "operator": operator, "boost": 4}}}, - {"match_phrase": {f"text_{language}": {"query": variant, "boost": 3}}}, - ) ] - planned = {"should": [*full_text, *intent_clauses], "minimum_should_match": 1} + phrase_matches = [ + {"match_phrase": {f"text_{language}": {"query": variant, "boost": 3}}} + for variant in query_variants(language, query) + ] + planned = {"should": [*full_text, *phrase_matches, *intent_clauses], "minimum_should_match": 1} return {"bool": planned} diff --git a/backend/test_search_api.py b/backend/test_search_api.py index 1f19748..41c4267 100644 --- a/backend/test_search_api.py +++ b/backend/test_search_api.py @@ -47,7 +47,7 @@ class SearchApiTest(unittest.TestCase): self.assertEqual(planned["minimum_should_match"], 1) self.assertNotIn("filter", planned) self.assertTrue(any("constant_score" in clause for clause in planned["should"])) - self.assertTrue(any("общество с ограниченной ответственностью" in clause.get("match", {}).get("text_ru", {}).get("query", "") for clause in planned["should"])) + self.assertTrue(any("общество с ограниченной ответственностью" in clause.get("multi_match", {}).get("query", "") for clause in planned["should"])) def test_production_api_boosts_tax_code_for_profit_tax_query(self): with tempfile.TemporaryDirectory() as temporary: diff --git a/backend/test_search_relevance.py b/backend/test_search_relevance.py index 3940c23..3dfe394 100644 --- a/backend/test_search_relevance.py +++ b/backend/test_search_relevance.py @@ -38,7 +38,9 @@ class SearchRelevanceTest(unittest.TestCase): self.assertFalse(body["track_total_hits"]) self.assertEqual(body["collapse"], {"field": "document_code"}) self.assertIn({"term": {"is_current_edition": True}}, body["query"]["bool"]["filter"]) - lexical_query = body["query"]["bool"]["must"]["bool"]["should"][0]["match"]["text_ru"] + lexical_query = body["query"]["bool"]["must"]["bool"]["should"][0]["multi_match"] + self.assertEqual(lexical_query["type"], "cross_fields") + self.assertEqual(lexical_query["fields"], ["document_name_ru^4", "text_ru"]) self.assertEqual(lexical_query["operator"], "AND") self.assertTrue(any("match_phrase" in clause for clause in body["query"]["bool"]["must"]["bool"]["should"])) @@ -56,8 +58,8 @@ class SearchRelevanceTest(unittest.TestCase): search_query = body["query"]["bool"]["must"]["bool"] self.assertEqual(search_query["minimum_should_match"], 1) self.assertNotIn("filter", search_query) - lexical = [clause["match"] for clause in search_query["should"] if "match" in clause] - self.assertTrue(all(next(iter(clause.values()))["operator"] == "OR" for clause in lexical)) + lexical = [clause["multi_match"] for clause in search_query["should"] if "multi_match" in clause] + self.assertTrue(all(clause["type"] == "cross_fields" and clause["operator"] == "OR" for clause in lexical)) boosts = [clause["constant_score"] for clause in search_query["should"] if "constant_score" in clause] self.assertEqual([item["boost"] for item in boosts], [boost for _, boost in COMPANY_REGISTRATION_DOCUMENTS]) self.assertEqual(