From afb2dc1b9e396af17f92ea01336e6127b08b4084 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Sun, 28 Jun 2026 11:37:14 +0500 Subject: [PATCH] =?UTF-8?q?fix(dict):=20=D1=87=D0=B8=D1=81=D1=82=D1=8B?= =?UTF-8?q?=D0=B5=20service=5Fid=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?Excel=20#REF!=20=D0=B2=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BE?= =?UTF-8?q?=D1=87=D0=BD=D0=B8=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 720 услуг имели #REF! в каноническом service_id (Excel-ошибка из исходного справочника протекала в живой /search). Миграция scripts/fix_ref_ids.py генерирует чистые id и каскадно обновляет price_item (service_id, suggested_service_id) и learned_synonym. matched сохранён (11899), имена целы. Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 1 + scripts/fix_ref_ids.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 scripts/fix_ref_ids.py diff --git a/pyproject.toml b/pyproject.toml index ff5ffd2..2f57554 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,4 @@ extend-immutable-calls = ["fastapi.File", "fastapi.Query", "fastapi.Depends"] # Разведочные скрипты этапа разбора форматов — черновики, не часть продукта. "scripts/proof/*" = ["E402", "E501", "E741", "B007"] "scripts/gen_fixtures.py" = ["E402", "E501", "E741"] +"scripts/fix_ref_ids.py" = ["E501"] # одноразовая миграция, SQL-строки длиннее 100 diff --git a/scripts/fix_ref_ids.py b/scripts/fix_ref_ids.py new file mode 100644 index 0000000..5022a70 --- /dev/null +++ b/scripts/fix_ref_ids.py @@ -0,0 +1,49 @@ +"""Починка справочника: заменить повреждённые Excel-ошибкой service_id (#REF!) на чистые. + +В исходном справочнике у части услуг код тарификатора пришёл как Excel `#REF!`, и это +протекло в канонический service_id (напр. `#REF!-#REF!#459`) — видно в живом /search. +Имена услуг при этом корректны. Скрипт генерирует чистые уникальные id и каскадно +обновляет ссылки в price_item (service_id и suggested_service_id) и learned_synonym. + +Запуск: python scripts/fix_ref_ids.py +""" + +import sqlite3 +import sys + + +def fix(db_path: str) -> dict: + conn = sqlite3.connect(db_path) + existing = {r[0] for r in conn.execute("SELECT service_id FROM service")} + bad = [r[0] for r in conn.execute("SELECT service_id FROM service WHERE service_id LIKE '%#REF!%'")] + + mapping: dict[str, str] = {} + i = 0 + for old in bad: + while True: + new = f"d{i:04d}" + i += 1 + if new not in existing: + break + existing.add(new) + mapping[old] = new + + # learned_synonym ссылается на service_id? обновим, если колонка есть. + cols = {r[1] for r in conn.execute("PRAGMA table_info(learned_synonym)")} + for old, new in mapping.items(): + conn.execute("UPDATE service SET service_id=? WHERE service_id=?", [new, old]) + conn.execute("UPDATE price_item SET service_id=? WHERE service_id=?", [new, old]) + conn.execute("UPDATE price_item SET suggested_service_id=? WHERE suggested_service_id=?", [new, old]) + if "service_id" in cols: + conn.execute("UPDATE learned_synonym SET service_id=? WHERE service_id=?", [new, old]) + conn.commit() + + left = conn.execute("SELECT COUNT(*) FROM service WHERE service_id LIKE '%#REF!%'").fetchone()[0] + left_pi = conn.execute("SELECT COUNT(*) FROM price_item WHERE service_id LIKE '%#REF!%'").fetchone()[0] + matched = conn.execute("SELECT COUNT(*) FROM price_item WHERE service_id IS NOT NULL").fetchone()[0] + conn.close() + return {"fixed_services": len(mapping), "ref_left_service": left, "ref_left_price_item": left_pi, "matched_total": matched} + + +if __name__ == "__main__": + print(fix(sys.argv[1] if len(sys.argv) > 1 else "data/medarchive.db"))