Files
agent_contas/app/domain/contas/line_reference.py

56 lines
2.1 KiB
Python

from __future__ import annotations
import re
import unicodedata
from typing import Any
_DIGIT_WORDS = {
"zero": "0", "um": "1", "uma": "1", "dois": "2", "duas": "2",
"tres": "3", "quatro": "4", "cinco": "5", "seis": "6", "sete": "7",
"oito": "8", "nove": "9",
}
def _norm(text: Any) -> str:
value = unicodedata.normalize("NFKD", str(text or "").casefold())
value = "".join(ch for ch in value if not unicodedata.combining(ch))
return re.sub(r"\s+", " ", value).strip()
def extract_requested_line_reference(text: Any) -> dict[str, str] | None:
"""Extrai somente uma referência explícita de linha citada pelo usuário.
Não transforma a referência em identidade autorizada. Essa decisão pertence à
política de linha ativa no domínio Contas.
"""
raw = str(text or "").strip()
if not raw:
return None
norm = _norm(raw)
# Número completo explicitamente presente no texto (10 a 13 dígitos, com
# separadores opcionais). Evita capturar valores monetários ou protocolos curtos.
for match in re.finditer(r"(?<!\d)(?:\+?\d[\s().-]*){10,13}(?!\d)", raw):
digits = "".join(ch for ch in match.group(0) if ch.isdigit())
if 10 <= len(digits) <= 13:
return {"kind": "full", "value": digits, "raw": match.group(0).strip()}
# Referência por final da linha: "final 4321", "final quatro três dois um",
# "termina em 4321". Só a referência é extraída; nunca é assumida como MSISDN.
marker = re.search(r"\b(?:final|termina(?:ndo)?\s+em|terminado\s+em)\b(.{0,45})", norm)
if marker:
tail = marker.group(1)
numeric = re.search(r"\b(\d{4})\b", tail)
if numeric:
return {"kind": "suffix", "value": numeric.group(1), "raw": numeric.group(1)}
tokens = re.findall(r"[a-z]+", tail)
digits: list[str] = []
for token in tokens:
if token in _DIGIT_WORDS:
digits.append(_DIGIT_WORDS[token])
if len(digits) == 4:
return {"kind": "suffix", "value": "".join(digits), "raw": " ".join(tokens[:4])}
elif digits:
break
return None