"""Phase 0 proof: справочник загружается, реальный прайс парсится, каскад работает. Считает базовый процент автосопоставления БЕЗ эмбеддингов (только код тарификатора + точное совпадение + RapidFuzz). Эмбеддинги в боевом каскаде поднимут этот процент — здесь нужен нижний ориентир, чтобы убедиться, что цель 70% достижима. """ import re import sys from pathlib import Path REPO = Path(__file__).resolve().parents[2] sys.path.insert(0, str(REPO)) import docx from rapidfuzz import fuzz, process from etl.dictionary import load_dictionary, normalize_name svcs = load_dictionary(REPO / "data/reference/dictionary.xlsx") print( f"Справочник: {len(svcs)} услуг | с кодом тарификатора: {sum(1 for s in svcs if s.tarificator_code)}" ) by_code = {s.tarificator_code: s for s in svcs if s.tarificator_code} by_norm: dict[str, object] = {} for s in svcs: by_norm.setdefault(s.name_norm, s) names_norm = [s.name_norm for s in svcs] # --- разобрать реальный прайс Клиники 1 (docx, таблица «Код | Наименование | Стоимость») --- doc = docx.Document(REPO / "data/raw/Клиника 1 прайс 2024.docx") table = doc.tables[0] items = [] for row in table.rows: cells = [c.text.strip() for c in row.cells] if len(cells) < 3: continue code, name, price = cells[0], cells[1], cells[2] if code == name == price: # строка-заголовок секции (объединённые ячейки) continue if name.lower() == "наименование услуги": continue if not re.search(r"\d", price): # строка без цены — не позиция continue items.append((code, name, price)) print(f"Клиника 1 (2024): извлечено позиций — {len(items)}") THRESHOLD = 88 matched = by_code_n = by_exact_n = by_fuzzy_n = 0 examples_ok, examples_miss = [], [] for code, name, price in items: nn = normalize_name(name) if code in by_code: matched += 1 by_code_n += 1 continue if nn in by_norm: matched += 1 by_exact_n += 1 continue best = process.extractOne(nn, names_norm, scorer=fuzz.token_set_ratio) if best and best[1] >= THRESHOLD: matched += 1 by_fuzzy_n += 1 if len(examples_ok) < 6: examples_ok.append((name, svcs[best[2]].name_ru, round(best[1]))) elif len(examples_miss) < 6 and best: examples_miss.append((name, svcs[best[2]].name_ru, round(best[1]))) pct = 100 * matched / max(1, len(items)) print(f"\nАВТО-СОПОСТАВЛЕНО (без эмбеддингов): {matched}/{len(items)} = {pct:.0f}%") print( f" код тарификатора: {by_code_n} | точное имя: {by_exact_n} | fuzzy≥{THRESHOLD}: {by_fuzzy_n}" ) print("\nпримеры совпадений:") for raw, canon, sc in examples_ok: print(f" «{raw[:46]}» → «{canon[:46]}» ({sc})") print("\nпримеры в очередь unmatched (лучший кандидат ниже порога):") for raw, canon, sc in examples_miss: print(f" «{raw[:46]}» → лучший «{canon[:40]}» ({sc})")