feat: preserve gear components through catch imports
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Conservative offline crosswalk suggestions for gear identities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .community_sources import GEAR_CATEGORIES
|
||||
from .media_assets import normalize_entity_label
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CanonicalGear:
|
||||
key: str
|
||||
name: str
|
||||
category: str
|
||||
aliases: tuple[str, ...] = ()
|
||||
brand: str | None = None
|
||||
family: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class GearIdentity:
|
||||
source_system: str
|
||||
external_id: str
|
||||
name: str
|
||||
category: str
|
||||
brand: str | None = None
|
||||
family: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class GearCrosswalkSuggestion:
|
||||
source_system: str
|
||||
external_id: str
|
||||
external_name: str
|
||||
category: str
|
||||
status: str
|
||||
canonical_keys: tuple[str, ...]
|
||||
|
||||
|
||||
def _compatible_category(value: str) -> bool:
|
||||
return value.casefold().strip() in GEAR_CATEGORIES
|
||||
|
||||
|
||||
def suggest_gear_crosswalk(
|
||||
canonical: list[CanonicalGear], identities: list[GearIdentity],
|
||||
) -> list[GearCrosswalkSuggestion]:
|
||||
"""Suggest only unique exact normalized-name matches in the same category.
|
||||
|
||||
Brand/family differences never create an automatic match. Ambiguous,
|
||||
category-mismatched and unmatched identities retain no canonical key.
|
||||
"""
|
||||
by_name: dict[str, set[str]] = {}
|
||||
by_key = {item.key: item for item in canonical}
|
||||
for item in canonical:
|
||||
if not _compatible_category(item.category):
|
||||
raise ValueError(f"invalid canonical gear category: {item.category!r}")
|
||||
for name in (item.name, *item.aliases):
|
||||
by_name.setdefault(normalize_entity_label(name), set()).add(item.key)
|
||||
result: list[GearCrosswalkSuggestion] = []
|
||||
for identity in identities:
|
||||
keys = tuple(sorted(by_name.get(normalize_entity_label(identity.name), set())))
|
||||
compatible = tuple(
|
||||
key for key in keys
|
||||
if by_key[key].category.casefold() == identity.category.casefold()
|
||||
and not (by_key[key].brand and identity.brand and normalize_entity_label(by_key[key].brand) != normalize_entity_label(identity.brand))
|
||||
and not (by_key[key].family and identity.family and normalize_entity_label(by_key[key].family) != normalize_entity_label(identity.family))
|
||||
)
|
||||
if not _compatible_category(identity.category):
|
||||
status = "category_mismatch"
|
||||
selected: tuple[str, ...] = ()
|
||||
elif len(compatible) == 1:
|
||||
status = "exact"
|
||||
selected = compatible
|
||||
elif len(compatible) > 1:
|
||||
status = "ambiguous"
|
||||
selected = ()
|
||||
else:
|
||||
status = "unmatched"
|
||||
selected = ()
|
||||
result.append(GearCrosswalkSuggestion(
|
||||
source_system=identity.source_system, external_id=identity.external_id,
|
||||
external_name=identity.name, category=identity.category,
|
||||
status=status, canonical_keys=selected,
|
||||
))
|
||||
return result
|
||||
Reference in New Issue
Block a user