"""Детерминированное сопоставление услуги с переданным каталогом SKU. В workspace нет утверждённого каталога коммерческих артикулов, поэтому модуль не содержит выдуманных SKU и принимает каталог явным аргументом. Не найденное соответствие возвращает `UNKNOWN`, не мешая сверять даты и суммы договора. """ import re def _normalize_text(value): """Схлопнуть регистр и пунктуацию для точного сравнения названий каталога.""" return re.sub(r"[^0-9A-ZА-ЯЁ]", "", str(value or "").upper().replace("Ё", "Е")) def _normalize_sku(value): """Нормализовать SKU, сохраняя значимый PAYG-суффикс `-m`.""" text = str(value or "").strip().upper() payg = text.endswith("-M") core = re.sub(r"[^0-9A-ZА-ЯЁ]", "", text[:-2] if payg else text) return core + ("-M" if payg else "") def match_catalog_sku(service_item, catalog_entries): """Найти единственный SKU по явному SKU или точному нормализованному имени. PAYG-артикулы с суффиксом `-m` доступны только для тарифа `payg`; обычные абонентские услуги не поглощают такой артикул. При нескольких точных совпадениях возвращается `AMBIGUOUS`, а при пустом каталоге — `UNKNOWN`. """ entries = list(catalog_entries or []) charge_type = str(service_item.get("charge_type") or "unknown").lower() service_sku = _normalize_sku(service_item.get("catalog_sku")) service_name = _normalize_text( service_item.get("canonical_name") or service_item.get("raw_name") or "", ) matches = [] for entry in entries: if not isinstance(entry, dict) or not entry.get("sku"): continue entry_sku = _normalize_sku(entry["sku"]) entry_charge = str(entry.get("charge_type") or "unknown").lower() is_payg_sku = entry_sku.endswith("-M") if is_payg_sku and charge_type != "payg": continue if entry_charge not in {"unknown", charge_type}: continue exact_sku = bool(service_sku and service_sku == entry_sku) exact_name = bool( service_name and service_name in { _normalize_text(entry.get("name")), *(_normalize_text(alias) for alias in entry.get("aliases", [])), } ) if exact_sku or exact_name: matches.append(entry) unique = { _normalize_sku(entry["sku"]): entry for entry in matches } if len(unique) == 1: sku, entry = next(iter(unique.items())) return {"status": "MATCHED", "sku": entry["sku"], "catalog_entry": entry} if unique: return {"status": "AMBIGUOUS", "sku": None, "candidates": list(unique.values())} return {"status": "UNKNOWN", "sku": None, "candidates": []}