fix: rank company registration queries
This commit is contained in:
@@ -10,15 +10,15 @@ Telegram-бот — только часть рабочего окружения
|
||||
## Текущее состояние
|
||||
|
||||
Сейчас реализованы Telegram-бот-секретарь версии `0.2.2` и backend версии
|
||||
`0.5.1`: подготовка поискового индекса и оценка Recall@K/MRR@K на вручную
|
||||
`0.5.2`: подготовка поискового индекса и оценка Recall@K/MRR@K на вручную
|
||||
размеченном наборе запросов.
|
||||
|
||||
| Компонент | Версия | Состояние |
|
||||
|---|---:|---|
|
||||
| Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния |
|
||||
| Backend | `0.5.1` | добавлена воспроизводимая оценка качества поиска |
|
||||
| Backend | `0.5.2` | добавлено ранжирование подтверждённых запросов об открытии ОсОО/ЖЧК |
|
||||
| Frontend | — | ещё не создан |
|
||||
| Сбор и обработка правовых данных | `0.5.1` | добавлены relevance set и baseline-метрики |
|
||||
| Сбор и обработка правовых данных | `0.5.2` | добавлены relevance set и baseline-метрики |
|
||||
| RAG и база знаний | — | ещё не созданы |
|
||||
|
||||
## Структура репозитория
|
||||
@@ -59,4 +59,4 @@ python3 -m unittest discover -s tools/telegram-bot -v
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Backend Акылдаш
|
||||
|
||||
Версия: `0.5.1`
|
||||
Версия: `0.5.2`
|
||||
|
||||
Первая backend-область проекта — загрузка правовых документов из официального
|
||||
Open Data API ЦБД Минюста Кыргызской Республики. Код расположен в
|
||||
@@ -191,4 +191,4 @@ PYTHONPATH=backend python3 -m search.evaluate_relevance \
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -21,7 +21,7 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Callable, Iterable
|
||||
|
||||
APP_VERSION = "0.5.1"
|
||||
APP_VERSION = "0.5.2"
|
||||
API_BASE_URL = "https://cbd.minjust.gov.kg/api/v1/OpenData/"
|
||||
LANGUAGES = {"Rus": "ru", "Kyr": "ky"}
|
||||
IMAGE_LANGUAGES = {"Russian": "ru", "Kyrgyz": "ky"}
|
||||
|
||||
@@ -23,7 +23,7 @@ from pathlib import Path
|
||||
from typing import Callable
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
APP_VERSION = "0.5.1"
|
||||
APP_VERSION = "0.5.2"
|
||||
SCHEMA_VERSION = "1"
|
||||
NORMALIZER_VERSION = "1.0.0"
|
||||
LANGUAGES = ("ru", "ky")
|
||||
|
||||
@@ -11,6 +11,60 @@ from pathlib import Path
|
||||
from search.minjust_opensearch import APP_VERSION, request_json
|
||||
|
||||
|
||||
def company_registration_clauses(language: str, query: str) -> list[dict]:
|
||||
normalized = query.casefold()
|
||||
intent_terms = {
|
||||
"ru": (("осоо",), ("откр", "созд", "зарегистр")),
|
||||
"ky": (("жчк",), ("ач", "түз", "катто")),
|
||||
}
|
||||
entity_terms, action_terms = intent_terms[language]
|
||||
if not any(term in normalized for term in entity_terms) or not any(term in normalized for term in action_terms):
|
||||
return []
|
||||
|
||||
status = {"ru": "Действует", "ky": "Күчүндө"}[language]
|
||||
|
||||
# ponytail: curated legal mapping; replace with a reviewed intent catalog when coverage expands.
|
||||
def clause(document_code: str, boost: int) -> dict:
|
||||
return {
|
||||
"constant_score": {
|
||||
"filter": {
|
||||
"bool": {
|
||||
"filter": [
|
||||
{"term": {"document_code": document_code}},
|
||||
{"term": {f"status_{language}": status}},
|
||||
]
|
||||
}
|
||||
},
|
||||
"boost": boost,
|
||||
}
|
||||
}
|
||||
|
||||
return [clause("230044970", 2000), clause("667", 1000)]
|
||||
|
||||
|
||||
def search_body(language: str, query: str, top_k: int) -> bytes:
|
||||
full_text = {
|
||||
"multi_match": {
|
||||
"query": query,
|
||||
"fields": [f"document_name_{language}", f"text_{language}"],
|
||||
"type": "cross_fields",
|
||||
}
|
||||
}
|
||||
clauses = company_registration_clauses(language, query)
|
||||
bool_query = {"filter": {"term": {"language": language}}}
|
||||
if clauses:
|
||||
bool_query.update({"should": [full_text, *clauses], "minimum_should_match": 1})
|
||||
else:
|
||||
bool_query["must"] = full_text
|
||||
return json.dumps({
|
||||
"size": top_k,
|
||||
"track_total_hits": False,
|
||||
"_source": ["document_code"],
|
||||
"query": {"bool": bool_query},
|
||||
"collapse": {"field": "document_code"},
|
||||
}, ensure_ascii=False).encode()
|
||||
|
||||
|
||||
def load_queries(path: Path) -> list[dict]:
|
||||
with path.open(encoding="utf-8") as source:
|
||||
queries = json.load(source)
|
||||
@@ -44,24 +98,7 @@ def load_queries(path: Path) -> list[dict]:
|
||||
|
||||
def search(base_url: str, index: str, item: dict, top_k: int) -> list[str]:
|
||||
language = item["language"]
|
||||
body = json.dumps({
|
||||
"size": top_k,
|
||||
"track_total_hits": False,
|
||||
"_source": ["document_code"],
|
||||
"query": {
|
||||
"bool": {
|
||||
"filter": {"term": {"language": language}},
|
||||
"must": {
|
||||
"multi_match": {
|
||||
"query": item["query"],
|
||||
"fields": [f"document_name_{language}", f"text_{language}"],
|
||||
"type": "cross_fields",
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"collapse": {"field": "document_code"},
|
||||
}, ensure_ascii=False).encode()
|
||||
body = search_body(language, item["query"], top_k)
|
||||
url = f"{base_url.rstrip('/')}/{urllib.parse.quote(index, safe='')}/_search"
|
||||
response = request_json(url, "POST", body, "application/json")
|
||||
try:
|
||||
|
||||
@@ -15,7 +15,7 @@ import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Iterator
|
||||
|
||||
APP_VERSION = "0.5.1"
|
||||
APP_VERSION = "0.5.2"
|
||||
LANGUAGES = {"ru", "ky"}
|
||||
DEFAULT_MAPPING = Path(__file__).with_name("minjust-fragments-index.json")
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from search.evaluate_relevance import evaluate, load_queries
|
||||
from search.evaluate_relevance import evaluate, load_queries, search_body
|
||||
|
||||
|
||||
class SearchRelevanceTest(unittest.TestCase):
|
||||
@@ -42,6 +42,22 @@ class SearchRelevanceTest(unittest.TestCase):
|
||||
self.assertEqual(body["collapse"], {"field": "document_code"})
|
||||
self.assertEqual(body["query"]["bool"]["must"]["multi_match"]["type"], "cross_fields")
|
||||
|
||||
def test_company_registration_intent_boosts_current_documents(self):
|
||||
for language, query, status in (
|
||||
("ru", "как открыть ОсОО", "Действует"),
|
||||
("ky", "ЖЧК ачуу тартиби", "Күчүндө"),
|
||||
):
|
||||
body = json.loads(search_body(language, query, 10))
|
||||
search_query = body["query"]["bool"]
|
||||
self.assertEqual(search_query["minimum_should_match"], 1)
|
||||
boosts = [clause["constant_score"] for clause in search_query["should"][1:]]
|
||||
self.assertEqual([item["boost"] for item in boosts], [2000, 1000])
|
||||
self.assertEqual(
|
||||
[item["filter"]["bool"]["filter"][0]["term"]["document_code"] for item in boosts],
|
||||
["230044970", "667"],
|
||||
)
|
||||
self.assertTrue(all(item["filter"]["bool"]["filter"][1] == {"term": {f"status_{language}": status}} for item in boosts))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -33,4 +33,4 @@
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -78,4 +78,4 @@ Telegram позволяет запретить пользователям отп
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
- Telegram-бот: `0.2.2`
|
||||
- Telegram-бот на Synology: `0.2.1`
|
||||
- Backend: `0.5.1`
|
||||
- Backend: `0.5.2`
|
||||
- Frontend: не создан
|
||||
|
||||
## Краткий итог
|
||||
@@ -234,4 +234,4 @@
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -398,4 +398,4 @@ Git сохраняет актуальную версию
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -258,4 +258,4 @@ runtime-зависимостями frontend. Регистрация в стор
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -214,4 +214,4 @@ python3 backend/normalization/minjust_cbd.py
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -44,4 +44,4 @@
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -180,4 +180,4 @@
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
@@ -48,4 +48,4 @@ python3 -m unittest -v
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.1 · Frontend — не создан
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан
|
||||
|
||||
Reference in New Issue
Block a user