from __future__ import annotations import json import logging import os from dataclasses import dataclass from functools import lru_cache from typing import Any logger = logging.getLogger(__name__) _TRIPLETS_ENV_VAR = "TIM_PROTOCOL_TRIPLETS_JSON" _STAGES = ("open", "close") _FIELDS = ("reason1", "reason2", "reason3") @dataclass(frozen=True, slots=True) class ProtocolTriplet: reason1: str reason2: str reason3: str _DEFAULT_CATALOG: dict[str, dict[str, ProtocolTriplet]] = { "atendimento_geral": { "open": ProtocolTriplet("Informação", "Conta", "Valor"), "close": ProtocolTriplet("Informação", "Conta", "Serviço"), }, "cancelamento_vas_avulso": { "open": ProtocolTriplet( "Solicitação", "Serviço VAS", "Ativação/Desativação" ), "close": ProtocolTriplet( "Solicitação", "Serviço VAS", "Ativação/Desativação" ), }, "contestacao": { "open": ProtocolTriplet("Informação", "Conta", "Serviço"), "close": ProtocolTriplet("Reclamação", "Conta", "Valor"), }, "vas_estrategico": { "open": ProtocolTriplet("Informação", "Conta", "Serviço"), "close": ProtocolTriplet("Informação", "Conta", "Serviço"), }, "invoice_explanation_aceite_fechado": { "open": ProtocolTriplet("Informação", "Conta", "Serviço"), "close": ProtocolTriplet("Informação", "Conta", "Serviço"), }, "finalizacao_informacional_fechada": { "open": ProtocolTriplet("Informação", "Conta", "Serviço"), "close": ProtocolTriplet("Informação", "Conta", "Esclarecimento"), }, "valor_divergente": { "open": ProtocolTriplet("Informação", "Conta", "Valor"), "close": ProtocolTriplet("Informação", "Conta", "Valor"), }, "pro_rata": { "open": ProtocolTriplet("Informação", "Conta", "Valor"), "close": ProtocolTriplet("Informação", "Conta", "Valor"), }, "pro_rata_mensalidade": { "open": ProtocolTriplet("Informação", "Conta", "Mensalidade"), "close": ProtocolTriplet("Informação", "Conta", "Mensalidade"), }, "pro_rata_reclamacao": { "open": ProtocolTriplet("Reclamação", "Conta", "Valor"), "close": ProtocolTriplet("Reclamação", "Conta", "Valor"), }, "termino_desconto": { "open": ProtocolTriplet("Informação", "Conta", "Serviço"), "close": ProtocolTriplet("Informação", "Conta", "Serviço"), }, "conta_certa_manual": { "open": ProtocolTriplet( "Processo Interno", "Conta", "Conta Certa - Venc {dia_vencimento}" ), "close": ProtocolTriplet( "Processo Interno", "Conta", "Conta Certa - Venc {dia_vencimento}" ), }, } class _SafeFormatDict(dict[str, Any]): def __missing__(self, key: str) -> str: return "" def _normalize_text(value: Any) -> str: return str(value or "").strip() def _override_triplet(base: ProtocolTriplet, override: dict[str, Any]) -> ProtocolTriplet: return ProtocolTriplet( reason1=_normalize_text(override.get("reason1")) or base.reason1, reason2=_normalize_text(override.get("reason2")) or base.reason2, reason3=_normalize_text(override.get("reason3")) or base.reason3, ) @lru_cache(maxsize=1) def _catalog_with_overrides() -> dict[str, dict[str, ProtocolTriplet]]: catalog = { scenario: dict(stages) for scenario, stages in _DEFAULT_CATALOG.items() } raw = os.getenv(_TRIPLETS_ENV_VAR, "").strip() if not raw: return catalog try: payload = json.loads(raw) except json.JSONDecodeError: logger.warning("triplets.override.invalid_json env=%s", _TRIPLETS_ENV_VAR) return catalog if not isinstance(payload, dict): logger.warning("triplets.override.invalid_root env=%s", _TRIPLETS_ENV_VAR) return catalog for scenario, stage_payload in payload.items(): scenario_key = _normalize_text(scenario).lower() if not scenario_key or not isinstance(stage_payload, dict): continue current = catalog.get(scenario_key, {}) for stage in _STAGES: override = stage_payload.get(stage) if not isinstance(override, dict): continue if stage in current: current[stage] = _override_triplet(current[stage], override) continue if any(_normalize_text(override.get(field)) for field in _FIELDS): current[stage] = ProtocolTriplet( reason1=_normalize_text(override.get("reason1")), reason2=_normalize_text(override.get("reason2")), reason3=_normalize_text(override.get("reason3")), ) if current: catalog[scenario_key] = current return catalog def resolve_protocol_triplet( scenario: str, *, stage: str, context: dict[str, Any] | None = None, fallback_scenario: str = "atendimento_geral", ) -> ProtocolTriplet: stage_key = _normalize_text(stage).lower() if stage_key not in _STAGES: stage_key = "open" scenario_key = _normalize_text(scenario).lower() or fallback_scenario fallback_key = _normalize_text(fallback_scenario).lower() or "atendimento_geral" catalog = _catalog_with_overrides() stages = catalog.get(scenario_key) or catalog.get(fallback_key) or {} raw_triplet = stages.get(stage_key) if raw_triplet is None: fallback_stages = catalog.get("atendimento_geral", {}) raw_triplet = fallback_stages.get(stage_key) or ProtocolTriplet("", "", "") fmt_values = _SafeFormatDict( {key: _normalize_text(value) for key, value in (context or {}).items()} ) return ProtocolTriplet( reason1=raw_triplet.reason1.format_map(fmt_values).strip(), reason2=raw_triplet.reason2.format_map(fmt_values).strip(), reason3=raw_triplet.reason3.format_map(fmt_values).strip(), )