"""Run document searches against the local OpenSearch index.""" from __future__ import annotations import argparse import json import re import urllib.parse from search.minjust_opensearch import APP_VERSION, request_json LEGAL_FORM_ALIASES = { "ru": { "осоо": ("ооо", "общество с ограниченной ответственностью"), "ооо": ("осоо", "общество с ограниченной ответственностью"), "общество с ограниченной ответственностью": ("осоо", "ооо"), "общества с ограниченной ответственностью": ("осоо", "ооо"), }, "ky": { "жчк": ("жоопкерчилиги чектелген коом",), "жоопкерчилиги чектелген коом": ("жчк",), }, } COMPANY_REGISTRATION_DOCUMENTS = (("230044970", 3000), ("667", 2000), ("4", 1000)) TAX_PROFIT_DOCUMENTS = (("112340", 3000),) def query_variants(language: str, query: str) -> list[str]: variants = [query] for alias, expansions in LEGAL_FORM_ALIASES[language].items(): pattern = re.compile(rf"(? list[dict]: status = {"ru": "Действует", "ky": "Күчүндө"}[language] return [ { "constant_score": { "filter": { "bool": { "filter": [ {"term": {"document_code": code}}, {"term": {f"status_{language}": status}}, ] } }, "boost": boost, } } for code, boost in documents ] def company_registration_clauses(language: str, query: str) -> list[dict]: non_registration_context = { "ru": (r"\bсч[её]т\w*", r"\bбанк\w*", r"\bфилиал\w*", r"\bпредставительств\w*"), "ky": (r"\bбанк\w*", r"\bэсеп\w*", r"\bфилиал\w*", r"\bөкүлчүлүк\w*"), }[language] normalized_query = query.casefold() if any(re.search(pattern, normalized_query) for pattern in non_registration_context): return [] forms, patterns = { "ru": ( r"(?:осоо|ооо|обществ\w*\s+с\s+ограниченной\s+ответственностью)", ( r"\bкак\s+(?:откры\w*|созда\w*|зарегистр\w*)\s+{form}\b", r"\b(?:порядок|процедура)\s+(?:открыти\w*|создани\w*|регистраци\w*)\s+{form}\b", r"\b(?:государственн\w+\s+)?регистраци\w*\s+{form}\b", r"\bсоздани\w*\s+{form}\b", ), ), "ky": ( r"(?:жчк|жоопкерчилиги\s+чектелген\s+коом)", ( r"\bжчк\s+ач\w*\s+тартиби\b", r"\bжчк\s+кантип\s+ач\w*\b", r"\b{form}\s+(?:ачуу|ачуунун|каттоо|каттоонун)\b", r"\b(?:ачуу|каттоо)\s+{form}\b", ), ), }[language] if not any(re.search(pattern.format(form=forms), normalized_query) for pattern in patterns): return [] return document_priority_clauses(language, COMPANY_REGISTRATION_DOCUMENTS) def tax_profit_clauses(language: str, query: str) -> list[dict]: if language != "ru" or not re.search(r"\bналог\w*\s+(?:на\s+)?прибыл\w*\b", query.casefold()): return [] # ponytail: exact topic anchor; expand only with verified tax-code sources. return document_priority_clauses(language, TAX_PROFIT_DOCUMENTS) 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 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} return {"bool": planned} def build_search_body(language: str, query: str, top_k: int) -> bytes: return json.dumps({ "size": top_k, "track_total_hits": False, "_source": ["document_code"], "query": {"bool": {"filter": [{"term": {"language": language}}, {"term": {"is_current_edition": True}}], "must": build_search_query(language, query)}}, "collapse": {"field": "document_code"}, }, ensure_ascii=False).encode() def search_documents(base_url: str, index: str, language: str, query: str, top_k: int) -> list[str]: url = f"{base_url.rstrip('/')}/{urllib.parse.quote(index, safe='')}/_search" response = request_json(url, "POST", build_search_body(language, query, top_k), "application/json") try: return [hit["_source"]["document_code"] for hit in response["hits"]["hits"]] except (KeyError, TypeError) as error: raise RuntimeError("OpenSearch search response is incomplete") from error def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("query") parser.add_argument("--language", choices=("ru", "ky"), required=True) parser.add_argument("--url", default="http://127.0.0.1:9200") parser.add_argument("--index", default="akyldash-fragments-v1") parser.add_argument("--top-k", type=int, default=10) parser.add_argument("--version", action="version", version=APP_VERSION) arguments = parser.parse_args() if arguments.top_k <= 0: raise SystemExit("--top-k must be greater than zero") print(json.dumps(search_documents(arguments.url, arguments.index, arguments.language, arguments.query, arguments.top_k), ensure_ascii=False)) print(f"Akyldash Backend v{APP_VERSION} · Frontend — not created") return 0 if __name__ == "__main__": raise SystemExit(main())