63 lines
3.2 KiB
Python
63 lines
3.2 KiB
Python
"""Детерминированное сопоставление услуги с переданным каталогом 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": []} |