From 601c7f085f201cb2bccf2bd27d9cc25e6038a08a Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 21 Aug 2026 00:11:57 +0300 Subject: [PATCH 1/4] feat: switch search index alias atomically --- README.md | 8 ++--- backend/README.md | 14 +++++++-- backend/ingestion/minjust_cbd.py | 2 +- backend/normalization/minjust_cbd.py | 2 +- backend/search/minjust_opensearch.py | 29 ++++++++++++++++++- backend/test_minjust_opensearch.py | 22 ++++++++++++-- docs/README.md | 2 +- docs/decisions/001-telegram-workspace-mvp.md | 2 +- docs/operations/project-status.md | 4 +-- docs/operations/telegram-workspace-plan.md | 2 +- docs/product/frontend-search-sps-plan.md | 2 +- ...njust-document-normalization-agent-task.md | 2 +- docs/product/project-overview.md | 2 +- docs/team/ai-skills-for-beginners.md | 2 +- tools/telegram-bot/README.md | 2 +- 15 files changed, 76 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c6f8952..07e273d 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ Telegram-бот — только часть рабочего окружения ## Текущее состояние Сейчас реализованы Telegram-бот-секретарь версии `0.2.2` и backend версии -`0.5.2`: подготовка поискового индекса и оценка Recall@K/MRR@K на вручную +`0.5.3`: подготовка поискового индекса и оценка Recall@K/MRR@K на вручную размеченном наборе запросов. | Компонент | Версия | Состояние | |---|---:|---| | Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния | -| Backend | `0.5.2` | добавлено ранжирование подтверждённых запросов об открытии ОсОО/ЖЧК | +| Backend | `0.5.3` | добавлено атомарное переключение alias поискового индекса | | Frontend | — | ещё не создан | -| Сбор и обработка правовых данных | `0.5.2` | добавлены relevance set и baseline-метрики | +| Сбор и обработка правовых данных | `0.5.3` | добавлены relevance set и baseline-метрики | | RAG и база знаний | — | ещё не созданы | ## Структура репозитория @@ -59,4 +59,4 @@ python3 -m unittest discover -s tools/telegram-bot -v --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/backend/README.md b/backend/README.md index 6b777a5..faf2d13 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # Backend Акылдаш -Версия: `0.5.2` +Версия: `0.5.3` Первая backend-область проекта — загрузка правовых документов из официального Open Data API ЦБД Минюста Кыргызской Республики. Код расположен в @@ -128,6 +128,16 @@ python3 backend/search/minjust_opensearch.py \ После проверки production-индекса следует переключать alias, чтобы удалённые фрагменты не оставались в поиске. +Чтобы переключить alias атомарно только после успешной полной загрузки, +передайте `--alias`: + +```bash +python3 backend/search/minjust_opensearch.py \ + --url http://127.0.0.1:9200 \ + --index akyldash-fragments-v2 \ + --alias akyldash-fragments-current +``` + После каждого принятого Bulk-пакета загрузчик атомарно сохраняет checkpoint и печатает код безопасного возобновления. При временных HTTP 429/5xx, timeout и обрыве соединения запрос повторяется автоматически. Прерванную загрузку можно @@ -198,4 +208,4 @@ PYTHONPATH=backend python3 -m search.query "ЖЧК ачуу тартиби" --la --- -Акылдаш · Backend v0.5.2 · Frontend — не создан +Акылдаш · Backend v0.5.3 · Frontend — не создан diff --git a/backend/ingestion/minjust_cbd.py b/backend/ingestion/minjust_cbd.py index d17da03..ba897f1 100644 --- a/backend/ingestion/minjust_cbd.py +++ b/backend/ingestion/minjust_cbd.py @@ -21,7 +21,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import Callable, Iterable -APP_VERSION = "0.5.2" +APP_VERSION = "0.5.3" API_BASE_URL = "https://cbd.minjust.gov.kg/api/v1/OpenData/" LANGUAGES = {"Rus": "ru", "Kyr": "ky"} IMAGE_LANGUAGES = {"Russian": "ru", "Kyrgyz": "ky"} diff --git a/backend/normalization/minjust_cbd.py b/backend/normalization/minjust_cbd.py index 87f62c1..2ea23f5 100644 --- a/backend/normalization/minjust_cbd.py +++ b/backend/normalization/minjust_cbd.py @@ -23,7 +23,7 @@ from pathlib import Path from typing import Callable from urllib.parse import urlsplit -APP_VERSION = "0.5.2" +APP_VERSION = "0.5.3" SCHEMA_VERSION = "1" NORMALIZER_VERSION = "1.0.0" LANGUAGES = ("ru", "ky") diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py index a45c4cd..2db1be1 100644 --- a/backend/search/minjust_opensearch.py +++ b/backend/search/minjust_opensearch.py @@ -15,7 +15,7 @@ import urllib.request from pathlib import Path from typing import Iterator -APP_VERSION = "0.5.2" +APP_VERSION = "0.5.3" LANGUAGES = {"ru", "ky"} DEFAULT_MAPPING = Path(__file__).with_name("minjust-fragments-index.json") @@ -311,8 +311,11 @@ def load_bulk( limit: int | None = None, resume: bool = False, checkpoint: Path = Path("data/opensearch/minjust-fragments.checkpoint.json"), + alias: str | None = None, ) -> tuple[int, int]: base = url.rstrip("/") + if alias is not None and alias == index: + raise ValueError("--alias must differ from --index") index_url = f"{base}/{urllib.parse.quote(index, safe='')}" if resume: cluster_uuid, index_uuid = opensearch_identity(base, index, index_url) @@ -363,11 +366,31 @@ def load_bulk( state["last_document_code"] = last_code write_json_atomic(checkpoint, state) print(f"checkpoint={last_code} fragments={fragments}", flush=True) + if alias: + switch_alias(base, index, alias) state["complete"] = True write_json_atomic(checkpoint, state) return documents, fragments +def switch_alias(base: str, index: str, alias: str) -> None: + if not alias or alias == index: + raise ValueError("--alias must differ from --index") + result = request_json( + f"{base.rstrip('/')}/_aliases", + "POST", + json.dumps({ + "actions": [ + {"remove": {"index": "*", "alias": alias, "must_exist": False}}, + {"add": {"index": index, "alias": alias}}, + ] + }).encode(), + "application/json", + ) + if result.get("acknowledged") is not True: + raise RuntimeError(f"OpenSearch did not acknowledge alias switch: {alias}") + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--input", type=Path, default=Path("data/minjust-normalized")) @@ -375,6 +398,7 @@ def main() -> int: parser.add_argument("--index", default="akyldash-fragments-v1") parser.add_argument("--limit", type=int) parser.add_argument("--url", help="create the index and stream bounded Bulk requests instead of writing a file") + parser.add_argument("--alias", help="atomically point this alias at --index after a successful load") parser.add_argument("--mapping", type=Path, default=DEFAULT_MAPPING) parser.add_argument("--batch-mb", type=int, default=25) parser.add_argument("--resume", action="store_true", help="load into an existing index") @@ -389,6 +413,8 @@ def main() -> int: raise SystemExit("--resume requires --url") if arguments.checkpoint and not arguments.url: raise SystemExit("--checkpoint requires --url") + if arguments.alias and not arguments.url: + raise SystemExit("--alias requires --url") if arguments.url: checkpoint = arguments.checkpoint or Path("data/opensearch") / f"{arguments.index}.checkpoint.json" documents, fragments = load_bulk( @@ -400,6 +426,7 @@ def main() -> int: arguments.limit, arguments.resume, checkpoint, + arguments.alias, ) destination = arguments.url else: diff --git a/backend/test_minjust_opensearch.py b/backend/test_minjust_opensearch.py index db76c75..7abc2aa 100644 --- a/backend/test_minjust_opensearch.py +++ b/backend/test_minjust_opensearch.py @@ -9,10 +9,25 @@ from contextlib import closing from pathlib import Path from unittest.mock import patch -from search.minjust_opensearch import bulk_batches, document_codes, export_bulk, load_bulk, request_json +from search.minjust_opensearch import bulk_batches, document_codes, export_bulk, load_bulk, request_json, switch_alias class MinjustOpenSearchTest(unittest.TestCase): + def test_switches_alias_atomically_after_successful_load(self): + with patch("search.minjust_opensearch.request_json", return_value={"acknowledged": True}) as request: + switch_alias("http://127.0.0.1:9200/", "akyldash-fragments-v2", "akyldash-fragments-current") + + self.assertEqual(request.call_args.args[:2], ("http://127.0.0.1:9200/_aliases", "POST")) + body = json.loads(request.call_args.args[2]) + self.assertEqual(body["actions"][0], {"remove": {"index": "*", "alias": "akyldash-fragments-current", "must_exist": False}}) + self.assertEqual(body["actions"][1], {"add": {"index": "akyldash-fragments-v2", "alias": "akyldash-fragments-current"}}) + + with self.assertRaisesRegex(ValueError, "differ"): + switch_alias("http://127.0.0.1:9200", "same", "same") + with patch("search.minjust_opensearch.request_json", return_value={"acknowledged": False}): + with self.assertRaisesRegex(RuntimeError, "did not acknowledge"): + switch_alias("http://127.0.0.1:9200", "index", "alias") + def test_exports_atomic_bulk_and_rejects_mismatched_fragment(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) @@ -91,6 +106,7 @@ class MinjustOpenSearchTest(unittest.TestCase): {"cluster_uuid": "cluster-1"}, {"test-index": {"settings": {"index": {"uuid": "index-1"}}}}, {"errors": False, "items": [{"index": {}}, {"index": {}}]}, + {"acknowledged": True}, ] self.assertEqual( load_bulk( @@ -99,10 +115,12 @@ class MinjustOpenSearchTest(unittest.TestCase): "test-index", maximum_bytes=4096, checkpoint=checkpoint, + alias="test-current", ), (2, 2), ) - self.assertEqual(request.call_args_list[-1].args[3], "application/x-ndjson") + self.assertEqual(request.call_args_list[-2].args[3], "application/x-ndjson") + self.assertEqual(request.call_args_list[-1].args[:2], ("http://127.0.0.1:9200/_aliases", "POST")) state = json.loads(checkpoint.read_text(encoding="utf-8")) self.assertEqual(state["last_document_code"], "7") self.assertTrue(state["complete"]) diff --git a/docs/README.md b/docs/README.md index feebcc7..ddf7a89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,4 +35,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/decisions/001-telegram-workspace-mvp.md b/docs/decisions/001-telegram-workspace-mvp.md index 186d7f6..650d41a 100644 --- a/docs/decisions/001-telegram-workspace-mvp.md +++ b/docs/decisions/001-telegram-workspace-mvp.md @@ -78,4 +78,4 @@ Telegram позволяет запретить пользователям отп --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/operations/project-status.md b/docs/operations/project-status.md index 67b0434..072a02f 100644 --- a/docs/operations/project-status.md +++ b/docs/operations/project-status.md @@ -5,7 +5,7 @@ - Telegram-бот: `0.2.2` - Telegram-бот на Synology: `0.2.1` -- Backend: `0.5.2` +- Backend: `0.5.3` - Frontend: не создан ## Краткий итог @@ -234,4 +234,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/operations/telegram-workspace-plan.md b/docs/operations/telegram-workspace-plan.md index 35ca7f4..f8bc682 100644 --- a/docs/operations/telegram-workspace-plan.md +++ b/docs/operations/telegram-workspace-plan.md @@ -398,4 +398,4 @@ Git сохраняет актуальную версию --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/product/frontend-search-sps-plan.md b/docs/product/frontend-search-sps-plan.md index 32f55ae..28e3c05 100644 --- a/docs/product/frontend-search-sps-plan.md +++ b/docs/product/frontend-search-sps-plan.md @@ -258,4 +258,4 @@ runtime-зависимостями frontend. Регистрация в стор --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/product/minjust-document-normalization-agent-task.md b/docs/product/minjust-document-normalization-agent-task.md index 87100b6..a204a69 100644 --- a/docs/product/minjust-document-normalization-agent-task.md +++ b/docs/product/minjust-document-normalization-agent-task.md @@ -214,4 +214,4 @@ python3 backend/normalization/minjust_cbd.py --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/product/project-overview.md b/docs/product/project-overview.md index 6290b27..f9be4da 100644 --- a/docs/product/project-overview.md +++ b/docs/product/project-overview.md @@ -44,4 +44,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/docs/team/ai-skills-for-beginners.md b/docs/team/ai-skills-for-beginners.md index 774ffb0..4c674cf 100644 --- a/docs/team/ai-skills-for-beginners.md +++ b/docs/team/ai-skills-for-beginners.md @@ -180,4 +180,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан diff --git a/tools/telegram-bot/README.md b/tools/telegram-bot/README.md index ed1b138..2ea9743 100644 --- a/tools/telegram-bot/README.md +++ b/tools/telegram-bot/README.md @@ -48,4 +48,4 @@ python3 -m unittest -v --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.2 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан -- 2.49.1 From 9781e93eafd65a0a6c90ede97bb7b4109f718afc Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 21 Aug 2026 00:15:24 +0300 Subject: [PATCH 2/4] fix: preserve alias in search load checkpoint --- backend/search/minjust_opensearch.py | 10 ++++++++-- backend/test_minjust_opensearch.py | 23 ++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py index 2db1be1..51ebe78 100644 --- a/backend/search/minjust_opensearch.py +++ b/backend/search/minjust_opensearch.py @@ -246,6 +246,7 @@ def checkpoint_state( cluster_uuid: str, index_uuid: str, limit: int | None, + alias: str | None, ) -> tuple[dict, str | None]: source = input_root.resolve() destination = checkpoint.resolve() @@ -254,7 +255,7 @@ def checkpoint_state( manifest_sha256 = file_sha256(input_root / "manifest.sqlite3") if not resume: return { - "schema_version": 1, + "schema_version": 2, "url": url, "cluster_uuid": cluster_uuid, "index": index, @@ -262,6 +263,7 @@ def checkpoint_state( "input": str(source), "manifest_sha256": manifest_sha256, "limit": limit, + "alias": alias, "last_document_code": None, "complete": False, }, None @@ -276,13 +278,14 @@ def checkpoint_state( "input", "manifest_sha256", "limit", + "alias", "last_document_code", "complete", } if not isinstance(state, dict) or set(state) != expected: raise ValueError(f"Invalid checkpoint: {checkpoint}") if ( - state["schema_version"] != 1 + state["schema_version"] != 2 or state["url"] != url or state["cluster_uuid"] != cluster_uuid or state["index"] != index @@ -292,6 +295,8 @@ def checkpoint_state( raise ValueError(f"Checkpoint does not match this load: {checkpoint}") if state["limit"] != limit: raise ValueError(f"Checkpoint limit does not match --limit: {checkpoint}") + if state["alias"] != alias: + raise ValueError(f"Checkpoint alias does not match --alias: {checkpoint}") if state["manifest_sha256"] != manifest_sha256: raise ValueError("Normalized manifest changed; create a new versioned index") if state["complete"] is not False: @@ -331,6 +336,7 @@ def load_bulk( cluster_uuid, index_uuid, limit, + alias, ) documents = document_count(input_root, limit, start_at) if not resume: diff --git a/backend/test_minjust_opensearch.py b/backend/test_minjust_opensearch.py index 7abc2aa..ed3f7ce 100644 --- a/backend/test_minjust_opensearch.py +++ b/backend/test_minjust_opensearch.py @@ -134,6 +134,21 @@ class MinjustOpenSearchTest(unittest.TestCase): {"test-index": {"settings": {"index": {"uuid": "index-1"}}}}, ] with self.assertRaisesRegex(ValueError, "does not match"): + load_bulk( + root / "normalized", + "http://127.0.0.1:9200", + "test-index", + maximum_bytes=4096, + resume=True, + checkpoint=checkpoint, + alias="test-current", + ) + with patch("search.minjust_opensearch.request_json") as request: + request.side_effect = [ + {"cluster_uuid": "cluster-1"}, + {"test-index": {"settings": {"index": {"uuid": "index-1"}}}}, + ] + with self.assertRaisesRegex(ValueError, "alias"): load_bulk( root / "normalized", "http://127.0.0.1:9200", @@ -156,11 +171,13 @@ class MinjustOpenSearchTest(unittest.TestCase): limit=1, resume=True, checkpoint=checkpoint, + alias="test-current", ) with patch("search.minjust_opensearch.request_json") as request: request.side_effect = [ {"cluster_uuid": "cluster-1"}, {"test-index": {"settings": {"index": {"uuid": "index-1"}}}}, + {"acknowledged": True}, ] self.assertEqual( load_bulk( @@ -170,10 +187,12 @@ class MinjustOpenSearchTest(unittest.TestCase): maximum_bytes=4096, resume=True, checkpoint=checkpoint, + alias="test-current", ), (1, 0), ) - self.assertTrue(all(call.args[1] == "GET" for call in request.call_args_list)) + self.assertTrue(all(call.args[1] == "GET" for call in request.call_args_list[:-1])) + self.assertEqual(request.call_args_list[-1].args[1], "POST") state["last_document_code"] = "9" state["complete"] = False @@ -191,6 +210,7 @@ class MinjustOpenSearchTest(unittest.TestCase): maximum_bytes=4096, resume=True, checkpoint=checkpoint, + alias="test-current", ) failed_checkpoint = root / "failed-checkpoint.json" @@ -236,6 +256,7 @@ class MinjustOpenSearchTest(unittest.TestCase): maximum_bytes=4096, resume=True, checkpoint=checkpoint, + alias="test-current", ) http_error = urllib.error.HTTPError( -- 2.49.1 From 61681cde7370fff763c12506dfdc0e6772260b0b Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 21 Aug 2026 00:18:28 +0300 Subject: [PATCH 3/4] fix: validate search alias checkpoints --- README.md | 8 +++--- backend/README.md | 10 ++++--- backend/ingestion/minjust_cbd.py | 2 +- backend/normalization/minjust_cbd.py | 2 +- backend/search/minjust_opensearch.py | 18 ++++++++++--- backend/test_minjust_opensearch.py | 26 +++++++++++++++++++ docs/README.md | 2 +- docs/decisions/001-telegram-workspace-mvp.md | 2 +- docs/operations/project-status.md | 4 +-- docs/operations/telegram-workspace-plan.md | 2 +- docs/product/frontend-search-sps-plan.md | 2 +- ...njust-document-normalization-agent-task.md | 2 +- docs/product/project-overview.md | 2 +- docs/team/ai-skills-for-beginners.md | 2 +- tools/telegram-bot/README.md | 2 +- 15 files changed, 62 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 07e273d..5edadb6 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ Telegram-бот — только часть рабочего окружения ## Текущее состояние Сейчас реализованы Telegram-бот-секретарь версии `0.2.2` и backend версии -`0.5.3`: подготовка поискового индекса и оценка Recall@K/MRR@K на вручную +`0.6.0`: подготовка поискового индекса и оценка Recall@K/MRR@K на вручную размеченном наборе запросов. | Компонент | Версия | Состояние | |---|---:|---| | Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния | -| Backend | `0.5.3` | добавлено атомарное переключение alias поискового индекса | +| Backend | `0.6.0` | добавлено атомарное переключение alias поискового индекса | | Frontend | — | ещё не создан | -| Сбор и обработка правовых данных | `0.5.3` | добавлены relevance set и baseline-метрики | +| Сбор и обработка правовых данных | `0.6.0` | добавлены relevance set и baseline-метрики | | RAG и база знаний | — | ещё не созданы | ## Структура репозитория @@ -59,4 +59,4 @@ python3 -m unittest discover -s tools/telegram-bot -v --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/backend/README.md b/backend/README.md index faf2d13..fb45519 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # Backend Акылдаш -Версия: `0.5.3` +Версия: `0.6.0` Первая backend-область проекта — загрузка правовых документов из официального Open Data API ЦБД Минюста Кыргызской Республики. Код расположен в @@ -152,8 +152,10 @@ python3 backend/search/minjust_opensearch.py \ По умолчанию checkpoint хранится в `data/opensearch/.checkpoint.json`; путь можно изменить через -`--checkpoint`. Checkpoint привязан к URL, cluster UUID, index UUID, `--limit` -и SHA-256 нормализованного manifest. Resume отклоняется при любом несовпадении: +`--checkpoint`. Checkpoint привязан к URL, cluster UUID, index UUID, `--limit`, +`--alias` и SHA-256 нормализованного manifest. При `--resume` передавайте то же +значение `--alias`; старый checkpoint без alias можно продолжить только без +него. Resume отклоняется при любом несовпадении: для обновлённого корпуса или пересозданного индекса нужно создать новый версионный индекс, проверить его и переключить alias. Это не оставляет удалённые trailing-фрагменты старых документов. @@ -208,4 +210,4 @@ PYTHONPATH=backend python3 -m search.query "ЖЧК ачуу тартиби" --la --- -Акылдаш · Backend v0.5.3 · Frontend — не создан +Акылдаш · Backend v0.6.0 · Frontend — не создан diff --git a/backend/ingestion/minjust_cbd.py b/backend/ingestion/minjust_cbd.py index ba897f1..1cf8d26 100644 --- a/backend/ingestion/minjust_cbd.py +++ b/backend/ingestion/minjust_cbd.py @@ -21,7 +21,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import Callable, Iterable -APP_VERSION = "0.5.3" +APP_VERSION = "0.6.0" API_BASE_URL = "https://cbd.minjust.gov.kg/api/v1/OpenData/" LANGUAGES = {"Rus": "ru", "Kyr": "ky"} IMAGE_LANGUAGES = {"Russian": "ru", "Kyrgyz": "ky"} diff --git a/backend/normalization/minjust_cbd.py b/backend/normalization/minjust_cbd.py index 2ea23f5..8ebd579 100644 --- a/backend/normalization/minjust_cbd.py +++ b/backend/normalization/minjust_cbd.py @@ -23,7 +23,7 @@ from pathlib import Path from typing import Callable from urllib.parse import urlsplit -APP_VERSION = "0.5.3" +APP_VERSION = "0.6.0" SCHEMA_VERSION = "1" NORMALIZER_VERSION = "1.0.0" LANGUAGES = ("ru", "ky") diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py index 51ebe78..466f97a 100644 --- a/backend/search/minjust_opensearch.py +++ b/backend/search/minjust_opensearch.py @@ -15,7 +15,7 @@ import urllib.request from pathlib import Path from typing import Iterator -APP_VERSION = "0.5.3" +APP_VERSION = "0.6.0" LANGUAGES = {"ru", "ky"} DEFAULT_MAPPING = Path(__file__).with_name("minjust-fragments-index.json") @@ -282,7 +282,15 @@ def checkpoint_state( "last_document_code", "complete", } - if not isinstance(state, dict) or set(state) != expected: + legacy_expected = expected - {"alias"} + if not isinstance(state, dict): + raise ValueError(f"Invalid checkpoint: {checkpoint}") + if set(state) == legacy_expected: + if state["schema_version"] != 1 or alias is not None: + raise ValueError(f"Legacy checkpoint does not support --alias: {checkpoint}") + state["schema_version"] = 2 + state["alias"] = None + elif set(state) != expected: raise ValueError(f"Invalid checkpoint: {checkpoint}") if ( state["schema_version"] != 2 @@ -319,7 +327,7 @@ def load_bulk( alias: str | None = None, ) -> tuple[int, int]: base = url.rstrip("/") - if alias is not None and alias == index: + if alias is not None and (not alias or alias == index): raise ValueError("--alias must differ from --index") index_url = f"{base}/{urllib.parse.quote(index, safe='')}" if resume: @@ -419,7 +427,9 @@ def main() -> int: raise SystemExit("--resume requires --url") if arguments.checkpoint and not arguments.url: raise SystemExit("--checkpoint requires --url") - if arguments.alias and not arguments.url: + if arguments.alias == "": + raise SystemExit("--alias must not be empty") + if arguments.alias is not None and not arguments.url: raise SystemExit("--alias requires --url") if arguments.url: checkpoint = arguments.checkpoint or Path("data/opensearch") / f"{arguments.index}.checkpoint.json" diff --git a/backend/test_minjust_opensearch.py b/backend/test_minjust_opensearch.py index ed3f7ce..cf02ac8 100644 --- a/backend/test_minjust_opensearch.py +++ b/backend/test_minjust_opensearch.py @@ -24,6 +24,8 @@ class MinjustOpenSearchTest(unittest.TestCase): with self.assertRaisesRegex(ValueError, "differ"): switch_alias("http://127.0.0.1:9200", "same", "same") + with self.assertRaisesRegex(ValueError, "differ"): + switch_alias("http://127.0.0.1:9200", "index", "") with patch("search.minjust_opensearch.request_json", return_value={"acknowledged": False}): with self.assertRaisesRegex(RuntimeError, "did not acknowledge"): switch_alias("http://127.0.0.1:9200", "index", "alias") @@ -125,6 +127,30 @@ class MinjustOpenSearchTest(unittest.TestCase): self.assertEqual(state["last_document_code"], "7") self.assertTrue(state["complete"]) + legacy = state.copy() + legacy.pop("alias") + legacy["schema_version"] = 1 + legacy["last_document_code"] = "8" + legacy["complete"] = False + checkpoint.write_text(json.dumps(legacy), encoding="utf-8") + with patch("search.minjust_opensearch.request_json") as request: + request.side_effect = [ + {"cluster_uuid": "cluster-1"}, + {"test-index": {"settings": {"index": {"uuid": "index-1"}}}}, + ] + self.assertEqual( + load_bulk( + root / "normalized", + "http://127.0.0.1:9200", + "test-index", + maximum_bytes=4096, + resume=True, + checkpoint=checkpoint, + ), + (1, 0), + ) + self.assertEqual(json.loads(checkpoint.read_text(encoding="utf-8"))["schema_version"], 2) + state["last_document_code"] = "8" state["complete"] = False checkpoint.write_text(json.dumps(state), encoding="utf-8") diff --git a/docs/README.md b/docs/README.md index ddf7a89..b3fbba1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,4 +35,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/decisions/001-telegram-workspace-mvp.md b/docs/decisions/001-telegram-workspace-mvp.md index 650d41a..2acba6d 100644 --- a/docs/decisions/001-telegram-workspace-mvp.md +++ b/docs/decisions/001-telegram-workspace-mvp.md @@ -78,4 +78,4 @@ Telegram позволяет запретить пользователям отп --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/operations/project-status.md b/docs/operations/project-status.md index 072a02f..df80cd2 100644 --- a/docs/operations/project-status.md +++ b/docs/operations/project-status.md @@ -5,7 +5,7 @@ - Telegram-бот: `0.2.2` - Telegram-бот на Synology: `0.2.1` -- Backend: `0.5.3` +- Backend: `0.6.0` - Frontend: не создан ## Краткий итог @@ -234,4 +234,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/operations/telegram-workspace-plan.md b/docs/operations/telegram-workspace-plan.md index f8bc682..3852ddd 100644 --- a/docs/operations/telegram-workspace-plan.md +++ b/docs/operations/telegram-workspace-plan.md @@ -398,4 +398,4 @@ Git сохраняет актуальную версию --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/product/frontend-search-sps-plan.md b/docs/product/frontend-search-sps-plan.md index 28e3c05..beeece2 100644 --- a/docs/product/frontend-search-sps-plan.md +++ b/docs/product/frontend-search-sps-plan.md @@ -258,4 +258,4 @@ runtime-зависимостями frontend. Регистрация в стор --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/product/minjust-document-normalization-agent-task.md b/docs/product/minjust-document-normalization-agent-task.md index a204a69..dfc0246 100644 --- a/docs/product/minjust-document-normalization-agent-task.md +++ b/docs/product/minjust-document-normalization-agent-task.md @@ -214,4 +214,4 @@ python3 backend/normalization/minjust_cbd.py --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/product/project-overview.md b/docs/product/project-overview.md index f9be4da..10c7fa8 100644 --- a/docs/product/project-overview.md +++ b/docs/product/project-overview.md @@ -44,4 +44,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/docs/team/ai-skills-for-beginners.md b/docs/team/ai-skills-for-beginners.md index 4c674cf..6770db1 100644 --- a/docs/team/ai-skills-for-beginners.md +++ b/docs/team/ai-skills-for-beginners.md @@ -180,4 +180,4 @@ --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан diff --git a/tools/telegram-bot/README.md b/tools/telegram-bot/README.md index 2ea9743..2d65772 100644 --- a/tools/telegram-bot/README.md +++ b/tools/telegram-bot/README.md @@ -48,4 +48,4 @@ python3 -m unittest -v --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.5.3 · Frontend — не создан +Акылдаш · Telegram-бот v0.2.2 · Backend v0.6.0 · Frontend — не создан -- 2.49.1 From 1ad2e4b2566d6bef7c9111cd10cd4b90d78819bf Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 21 Aug 2026 00:19:38 +0300 Subject: [PATCH 4/4] docs: record index alias switch --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a890a..eeb37b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Не выпущено +- Добавлено безопасное переключение alias на новую версию поискового индекса + после полной загрузки; checkpoint защищает возобновление загрузки от смены + alias. - Добавлен roadmap готовности данных, поиска и API перед проектированием frontend. - Исправлена выдача для явных запросов об открытии ОсОО и ЖЧК: первыми -- 2.49.1