Files
contracts-flask/site/services/catalog_matcher.py
T

63 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Детерминированное сопоставление услуги с переданным каталогом 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": []}