#!/usr/bin/env python3 """Shared anime title normalization and matching helpers.""" import re import unicodedata def normalize_title_key(value): text = unicodedata.normalize("NFKD", str(value or "")) text = "".join(char for char in text if not unicodedata.combining(char)) text = text.lower() text = re.sub(r"\((19|20)\d{2}\)", " ", text) text = re.sub(r"[^a-z0-9]+", " ", text) return re.sub(r"\s+", " ", text).strip() def normalize_identity_title_key(value): text = normalize_title_key(value) if not text: return "" text = re.sub(r"\b(\d+)(st|nd|rd|th)\s+season\b", r"season \1", text) return re.sub(r"\s+", " ", text).strip() def base_title_variants(value): text = str(value or "").strip() variants = [] if text: variants.append(text) patterns = [ r"\s+\((19|20)\d{2}\)\s*$", r"\s+season\s+\d+\s*$", r"\s+part\s+\d+\s*$", r"\s+cour\s+\d+\s*$", r"\s+\d+(st|nd|rd|th)\s+season\s*$", ] changed = True current = text while current and changed: changed = False for pattern in patterns: trimmed = re.sub(pattern, "", current, flags=re.IGNORECASE).strip(" -:") if trimmed and trimmed != current: current = trimmed if current not in variants: variants.append(current) changed = True return variants def title_match_variants(value): variants = set() for raw_value in base_title_variants(value): normalized = normalize_title_key(raw_value) if normalized: variants.add(normalized) stripped = re.sub(r"\b(19|20)\d{2}\b", " ", normalized) stripped = re.sub(r"\s+", " ", stripped).strip() if stripped: variants.add(stripped) return variants def identity_title_variants(value): variants = set() normalized = normalize_identity_title_key(value) if normalized: variants.add(normalized) stripped = re.sub(r"\b(19|20)\d{2}\b", " ", normalized) stripped = re.sub(r"\s+", " ", stripped).strip() if stripped: variants.add(stripped) return variants def title_lookup_queries(value): queries = [] for raw_value in base_title_variants(value): text = str(raw_value or "").strip() if text and text not in queries: queries.append(text) return queries