new feature: External guardrails/judges
This commit is contained in:
@@ -10,6 +10,7 @@ from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from .integrations.secure_pdf_crypto import encrypt_secure_pdf_value
|
||||
from .parsers import parse_tim_bill_pdf
|
||||
@@ -23,6 +24,29 @@ class TimApiError(RuntimeError):
|
||||
self.attempts = list(attempts or [])
|
||||
|
||||
|
||||
|
||||
_INTEGRATION_DEFAULTS_CACHE: dict[str, str] | None = None
|
||||
|
||||
def get_tim_integration_value(*names: str, default: str = "") -> str:
|
||||
"""Env > config/tim_integration_defaults.yaml > default explícito."""
|
||||
global _INTEGRATION_DEFAULTS_CACHE
|
||||
for name in names:
|
||||
value = os.getenv(name)
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
if _INTEGRATION_DEFAULTS_CACHE is None:
|
||||
path = Path(os.getenv("TIM_INTEGRATION_DEFAULTS_PATH", "./config/tim_integration_defaults.yaml"))
|
||||
try:
|
||||
raw = yaml.safe_load(path.read_text(encoding="utf-8")) if path.exists() else {}
|
||||
except Exception as exc:
|
||||
raise TimApiError(f"Falha ao ler defaults de integração TIM: {path}: {exc}") from exc
|
||||
_INTEGRATION_DEFAULTS_CACHE = {str(k): str(v) for k, v in (raw or {}).items() if v not in (None, "")}
|
||||
for name in names:
|
||||
value = _INTEGRATION_DEFAULTS_CACHE.get(name)
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
return default
|
||||
|
||||
class TimApiClient:
|
||||
"""Thin TIM integration adapter.
|
||||
|
||||
@@ -31,9 +55,11 @@ class TimApiClient:
|
||||
"""
|
||||
|
||||
FIXTURES = Path(__file__).with_name("fixtures")
|
||||
INTEGRATION_DEFAULTS_PATH = Path(os.getenv("TIM_INTEGRATION_DEFAULTS_PATH", "./config/tim_integration_defaults.yaml"))
|
||||
_integration_defaults_cache: dict[str, str] | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.mock = os.getenv("TIM_USE_MOCK_GATEWAY", "true").lower() in {"1", "true", "yes", "on"} or os.getenv("TIM_GATEWAY_MODE", "mock").lower() == "mock"
|
||||
self.mock = os.getenv("TIM_USE_MOCK_GATEWAY", "false").lower() in {"1", "true", "yes", "on"} or os.getenv("TIM_GATEWAY_MODE", "real").lower() == "mock"
|
||||
self.timeout = int(os.getenv("TIM_GATEWAY_DEFAULT_TIMEOUT", "30"))
|
||||
self.max_retries = int(os.getenv("TIM_GATEWAY_RETRY_MAX_RETRIES", "3"))
|
||||
self.backoff = float(os.getenv("TIM_GATEWAY_RETRY_BACKOFF_FACTOR", "0.5"))
|
||||
@@ -42,14 +68,14 @@ class TimApiClient:
|
||||
path = self.FIXTURES / f"{name}.json"
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
@staticmethod
|
||||
def _env_first(*names: str, default: str = "") -> str:
|
||||
"""Retorna a primeira variável não vazia, preservando aliases do Contas original."""
|
||||
for name in names:
|
||||
value = os.getenv(name)
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
return default
|
||||
@classmethod
|
||||
def _integration_defaults(cls) -> dict[str, str]:
|
||||
get_tim_integration_value("__warmup__")
|
||||
return dict(_INTEGRATION_DEFAULTS_CACHE or {})
|
||||
|
||||
@classmethod
|
||||
def _env_first(cls, *names: str, default: str = "") -> str:
|
||||
return get_tim_integration_value(*names, default=default)
|
||||
|
||||
@classmethod
|
||||
def _auth_value(
|
||||
@@ -155,7 +181,7 @@ class TimApiClient:
|
||||
"POST", url, payload={"msisdn": msisdn},
|
||||
headers=self._headers(
|
||||
client_id=None, auth=self._env_first("TIM_COMPLETE_INVOICES_AUTH"),
|
||||
extra={"ClientID": self._env_first("TIM_COMPLETE_INVOICES_CLIENT_ID", default="AIAGENTCR")},
|
||||
extra={"ClientID": self._env_first("TIM_COMPLETE_INVOICES_CLIENT_ID", )},
|
||||
),
|
||||
timeout=int(self._env_first("TIM_COMPLETE_INVOICES_TIMEOUT", default="30")),
|
||||
)
|
||||
@@ -171,9 +197,9 @@ class TimApiClient:
|
||||
password_names=("TIM_DIVERGENCIA_PASSWORD", "TIM_DIVERGENCIA_PASS", "TIM_DIVERGENCE_PASSWORD", "TIM_DIVERGENCE_PASS"),
|
||||
)
|
||||
headers = self._headers(client_id=None, auth=auth, extra={
|
||||
"clientID": self._env_first("TIM_DIVERGENCIA_CLIENT_ID", default="AIAGENTCR"),
|
||||
"clientID": self._env_first("TIM_DIVERGENCIA_CLIENT_ID", ),
|
||||
})
|
||||
params = {"channel": context.get("channel") or "AIAGENTCR"}
|
||||
params = {"channel": context.get("channel") or self._env_first("TIM_DIVERGENCIA_CHANNEL")}
|
||||
attempts: list[dict[str, Any]] = []
|
||||
try:
|
||||
result = self.request("GET", url, params=params, headers=headers, timeout=int(self._env_first("TIM_DIVERGENCIA_TIMEOUT", "TIM_DIVERGENCE_TIMEOUT", default="120")), attempt_log=attempts)
|
||||
@@ -192,7 +218,7 @@ class TimApiClient:
|
||||
"GET", url,
|
||||
headers=self._headers(
|
||||
client_id=None, auth=self._env_first("TIM_QUERY_AUTH", "TIM_CONSULTA_AUTH"),
|
||||
extra={"clientId": self._env_first("TIM_CONSULTA_CLIENT_ID", default="AIAAGENTCR")},
|
||||
extra={"clientId": self._env_first("TIM_CONSULTA_CLIENT_ID", )},
|
||||
),
|
||||
timeout=int(self._env_first("TIM_QUERY_TIMEOUT", "TIM_CONSULTA_TIMEOUT", default="30")),
|
||||
)
|
||||
@@ -207,7 +233,7 @@ class TimApiClient:
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_VAS_HISTORY_AUTH", ""),
|
||||
extra={
|
||||
"clientId": self._env_first("TIM_VAS_HISTORY_CLIENT_ID", default="AIAGENTCR"),
|
||||
"clientId": self._env_first("TIM_VAS_HISTORY_CLIENT_ID", ),
|
||||
"messageId": os.getenv("TIM_VAS_HISTORY_MESSAGE_ID", "") or str(uuid.uuid4()),
|
||||
},
|
||||
)
|
||||
@@ -220,7 +246,7 @@ class TimApiClient:
|
||||
digits = "".join(ch for ch in str(msisdn) if ch.isdigit())
|
||||
normalized = digits[2:] if len(digits) == 13 else digits
|
||||
app_id = str(service.get("appId") or service.get("app_id") or "")
|
||||
csp_id = str(service.get("cspId") or service.get("csp_id") or os.getenv("TIM_DEFAULT_CSP_ID", "740"))
|
||||
csp_id = str(service.get("cspId") or service.get("csp_id") or self._env_first("TIM_DEFAULT_CSP_ID"))
|
||||
op = self._env_first("TIM_BLOCK_OPERATION_TYPE", "TIM_BLOQUEIO_OPERATION_TYPE", default="block")
|
||||
base = {"Customer": {"Msisdn": normalized}, "AppId": app_id, "CspId": csp_id, "TypeOperation": op}
|
||||
payloads = [
|
||||
@@ -237,7 +263,7 @@ class TimApiClient:
|
||||
auth=self._env_first("TIM_BLOCK_AUTH", "TIM_BLOQUEIO_AUTH"),
|
||||
extra={
|
||||
"Accept-Encoding": self._env_first("TIM_BLOCK_ACCEPT_ENCODING", "TIM_BLOQUEIO_ACCEPT_ENCODING", default="gzip,deflate"),
|
||||
"clientId": self._env_first("TIM_BLOCK_CLIENT_ID", "TIM_BLOQUEIO_CLIENT_ID", default="AIAGENTCR"),
|
||||
"clientId": self._env_first("TIM_BLOCK_CLIENT_ID", "TIM_BLOQUEIO_CLIENT_ID", ),
|
||||
"messageId": str(uuid.uuid4()),
|
||||
},
|
||||
)
|
||||
@@ -256,17 +282,17 @@ class TimApiClient:
|
||||
return self.fixture("cancel_vas")
|
||||
url = self._env_first("TIM_CANCELLATION_URL", "TIM_CANCELAMENTO_URL")
|
||||
payload = {
|
||||
"channel": "AIAGENTCR",
|
||||
"channel": self._env_first("TIM_CANCELAMENTO_CHANNEL"),
|
||||
"msisdn": msisdn,
|
||||
"appId": str(service.get("appId") or service.get("app_id") or ""),
|
||||
"cspId": str(service.get("cspId") or service.get("csp_id") or os.getenv("TIM_DEFAULT_CSP_ID", "740")),
|
||||
"cspId": str(service.get("cspId") or service.get("csp_id") or self._env_first("TIM_DEFAULT_CSP_ID")),
|
||||
"interactionProtocol": protocol,
|
||||
}
|
||||
headers = self._headers(
|
||||
client_id=None,
|
||||
auth=self._env_first("TIM_CANCELLATION_AUTH", "TIM_CANCELAMENTO_AUTH"),
|
||||
extra={
|
||||
"clientId": os.getenv("TIM_CANCELAMENTO_CLIENT_ID", os.getenv("TIM_DEFAULT_CLIENT_ID", "AIAGENTCR")),
|
||||
"clientId": os.getenv("TIM_CANCELAMENTO_CLIENT_ID", self._env_first("TIM_DEFAULT_CLIENT_ID")),
|
||||
"messageId": str(uuid.uuid4()),
|
||||
"AuthorizationOAM": self._env_first("TIM_CANCELLATION_AUTH_OAM", "TIM_CANCELAMENTO_AUTH_OAM", "TIM_CANCELAMENTO_AUTHORIZATION_OAM"),
|
||||
"Cn_field": self._env_first("TIM_CANCELLATION_CN_FIELD", "TIM_CANCELAMENTO_CN_FIELD"),
|
||||
@@ -284,7 +310,7 @@ class TimApiClient:
|
||||
headers = self._headers(
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_CONTRATO_AUTH", ""),
|
||||
extra={"clientId": self._env_first("TIM_CONTRATO_CLIENT_ID", default="AIAGENTCR")},
|
||||
extra={"clientId": self._env_first("TIM_CONTRATO_CLIENT_ID", )},
|
||||
)
|
||||
return self.request("GET", f"{base}/{quote(msisdn)}", headers=headers, timeout=int(os.getenv("TIM_CONTRATO_TIMEOUT", "30")))
|
||||
|
||||
@@ -296,7 +322,7 @@ class TimApiClient:
|
||||
headers = self._headers(
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_PROFILE_FULL_AUTH", ""),
|
||||
extra={"ClientID": os.getenv("TIM_PROFILE_FULL_CLIENT_ID", "AIAGENTCR")},
|
||||
extra={"ClientID": self._env_first("TIM_PROFILE_FULL_CLIENT_ID")},
|
||||
)
|
||||
return self.request("GET", url, headers=headers, timeout=int(self._env_first("TIM_PROFILE_FULL_TIMEOUT", default="30")))
|
||||
|
||||
@@ -321,7 +347,7 @@ class TimApiClient:
|
||||
)
|
||||
body = {
|
||||
"socialSecNo": str(data.get("socialSecNo") or data.get("social_sec_no") or ""),
|
||||
"channel": "AIAGENTCR",
|
||||
"channel": self._env_first("TIM_PROTOCOL_CHANNEL"),
|
||||
"customerId": str(data.get("customerId") or data.get("customer_id") or ""),
|
||||
"assetId": str(data.get("assetId") or data.get("asset_id") or msisdn),
|
||||
"customerName": str(data.get("customerName") or data.get("customer_name") or ""),
|
||||
@@ -340,7 +366,7 @@ class TimApiClient:
|
||||
"interaction": {
|
||||
"protocol": str(data.get("interactionProtocol") or data.get("interaction_protocol") or ""),
|
||||
"flagSms": True,
|
||||
"source": "AIAGENTCR",
|
||||
"source": self._env_first("TIM_DEFAULT_SOURCE"),
|
||||
"callId": str(data.get("interactionCallId") or data.get("interaction_call_id") or ""),
|
||||
"crmSource": "Siebel Pós",
|
||||
"reasonId": str(data.get("interactionReasonId") or data.get("interaction_reason_id") or ""),
|
||||
@@ -355,7 +381,7 @@ class TimApiClient:
|
||||
client_id=None,
|
||||
auth=str(data.get("Authorization") or os.getenv("TIM_PROTOCOL_AUTH", "")),
|
||||
extra={
|
||||
"clientId": str(data.get("clientId") or data.get("client_id") or os.getenv("TIM_PROTOCOL_CLIENT_ID", "BFFDIGITAL")),
|
||||
"clientId": str(data.get("clientId") or data.get("client_id") or self._env_first("TIM_PROTOCOL_CLIENT_ID")),
|
||||
"messageId": message_id,
|
||||
"Authorizationoam": str(data.get("authorization_oam") or os.getenv("TIM_PROTOCOL_AUTHORIZATION_OAM", "")),
|
||||
"Cn_field": str(data.get("cn_field") or os.getenv("TIM_PROTOCOL_CN_FIELD", "")),
|
||||
@@ -378,7 +404,7 @@ class TimApiClient:
|
||||
if self.mock:
|
||||
return self.fixture("contestacao_tool")
|
||||
data = dict(payload)
|
||||
data.setdefault("userId", self._env_first("TIM_CUSTOMER_CONTESTATION_USER_ID", default="AIAGENTCR"))
|
||||
data.setdefault("userId", self._env_first("TIM_CUSTOMER_CONTESTATION_USER_ID", ))
|
||||
data.setdefault("customerIdCurrent", data.get("customerId") or "")
|
||||
data.setdefault("customerType", "2")
|
||||
data.setdefault("customerStatus", "1")
|
||||
@@ -401,9 +427,9 @@ class TimApiClient:
|
||||
data.setdefault("refundOption", "0")
|
||||
data.setdefault("doubleRefund", False)
|
||||
data.setdefault("manualContaCertaIndicator", False)
|
||||
client_id = str(data.pop("clientId", "") or os.getenv("TIM_CUSTOMER_CONTESTATION_CLIENT_ID", "AIAGENTCR"))
|
||||
client_id = str(data.pop("clientId", "") or self._env_first("TIM_CUSTOMER_CONTESTATION_CLIENT_ID"))
|
||||
message_id = str(data.pop("messageId", "") or uuid.uuid4())
|
||||
user_id = str(data.get("userId") or "AIAGENTCR").upper()
|
||||
user_id = str(data.get("userId") or self._env_first("TIM_DEFAULT_CLIENT_ID")).upper()
|
||||
headers = self._headers(
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_CUSTOMER_CONTESTATION_AUTH", ""),
|
||||
@@ -428,7 +454,7 @@ class TimApiClient:
|
||||
}
|
||||
for key in ("reason1", "reason2", "reason3", "auditChecks", "attachments"):
|
||||
if data.get(key): service_request[key] = data[key]
|
||||
body = {"channel": data.get("channel") or "AIAGENTCR", "serviceRequest": service_request}
|
||||
body = {"channel": data.get("channel") or self._env_first("TIM_DEFAULT_CLIENT_ID"), "serviceRequest": service_request}
|
||||
for key in ("msisdn", "customers", "date"):
|
||||
if data.get(key): body[key] = data[key]
|
||||
message_id = str(data.get("messageId") or data.get("ura_call_id") or data.get("session_id") or uuid.uuid4())
|
||||
@@ -436,7 +462,7 @@ class TimApiClient:
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_SERVICE_REQUEST_STATUS_AUTH", ""),
|
||||
extra={
|
||||
"clientId": str(data.get("clientId") or os.getenv("TIM_SERVICE_REQUEST_STATUS_CLIENT_ID", "AIAGENTCR")),
|
||||
"clientId": str(data.get("clientId") or self._env_first("TIM_SERVICE_REQUEST_STATUS_CLIENT_ID")),
|
||||
"messageId": message_id,
|
||||
"Authorizationoam": data.get("authorization_oam") or os.getenv("TIM_SERVICE_REQUEST_STATUS_AUTHORIZATION_OAM", ""),
|
||||
"Cn_field": data.get("cn_field") or os.getenv("TIM_SERVICE_REQUEST_STATUS_CN_FIELD", ""),
|
||||
@@ -463,7 +489,7 @@ class TimApiClient:
|
||||
"totalAmount": data.get("invoiceTotalAmount") or "",
|
||||
}
|
||||
body = {
|
||||
"channel": data.get("channel") or os.getenv("TIM_TRACKING_ACTIVITIES_CHANNEL", "AIAGENTCR"),
|
||||
"channel": data.get("channel") or self._env_first("TIM_TRACKING_ACTIVITIES_CHANNEL"),
|
||||
"customer": {"socialSecNo": data.get("socialSecNo") or "", "msisdn": data.get("msisdn") or ""},
|
||||
"protocolNumber": data.get("protocolNumber") or "",
|
||||
"invoice": invoice,
|
||||
@@ -472,12 +498,12 @@ class TimApiClient:
|
||||
"status": data.get("activityStatus") or "",
|
||||
"id": data.get("activityId") or "",
|
||||
},
|
||||
"user": {"login": data.get("userLogin") or os.getenv("TIM_TRACKING_ACTIVITIES_USER_LOGIN", "SIEBELPOS_INBOUND")},
|
||||
"user": {"login": data.get("userLogin") or self._env_first("TIM_TRACKING_ACTIVITIES_USER_LOGIN")},
|
||||
}
|
||||
headers = self._headers(
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_TRACKING_ACTIVITIES_AUTH", ""),
|
||||
extra={"clientId": data.get("clientId") or os.getenv("TIM_TRACKING_ACTIVITIES_CLIENT_ID", "AIAGENTCR")},
|
||||
extra={"clientId": data.get("clientId") or self._env_first("TIM_TRACKING_ACTIVITIES_CLIENT_ID")},
|
||||
)
|
||||
return self.request("POST", os.getenv("TIM_TRACKING_ACTIVITIES_URL", ""), payload=body, headers=headers, timeout=int(os.getenv("TIM_TRACKING_ACTIVITIES_TIMEOUT", "30")))
|
||||
|
||||
@@ -487,7 +513,7 @@ class TimApiClient:
|
||||
payload: dict[str, Any] = {
|
||||
"msisdn": msisdn,
|
||||
"senderAddress": context.get("sender_address") or os.getenv("TIM_SMS_SENDER_ADDRESS", "324"),
|
||||
"senderName": context.get("sender_name") or os.getenv("TIM_SMS_SENDER_NAME", "TIM Brasil"),
|
||||
"senderName": context.get("sender_name") or self._env_first("TIM_SMS_SENDER_NAME"),
|
||||
"message": message,
|
||||
"longURL": context.get("long_url") or message,
|
||||
}
|
||||
@@ -496,7 +522,7 @@ class TimApiClient:
|
||||
headers = self._headers(
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_SMS_AUTH", ""),
|
||||
extra={"clientId": os.getenv("TIM_SMS_CLIENT_ID", "AIAGENTCR")},
|
||||
extra={"clientId": self._env_first("TIM_SMS_CLIENT_ID")},
|
||||
)
|
||||
attempts: list[dict[str, Any]] = []
|
||||
result = self.request("POST", os.getenv("TIM_SMS_URL", ""), payload=payload, headers=headers, timeout=int(os.getenv("TIM_SMS_TIMEOUT", "30")), attempt_log=attempts)
|
||||
@@ -514,7 +540,7 @@ class TimApiClient:
|
||||
headers=self._headers(
|
||||
client_id=None,
|
||||
auth=self._env_first("TIM_COMPLETE_INVOICES_AUTH", "TIM_PROFILE_BILL_AUTH"),
|
||||
extra={"ClientID": self._env_first("TIM_COMPLETE_INVOICES_CLIENT_ID", "TIM_PROFILE_BILL_CLIENT_ID", default="AIAGENTCR")},
|
||||
extra={"ClientID": self._env_first("TIM_COMPLETE_INVOICES_CLIENT_ID", "TIM_PROFILE_BILL_CLIENT_ID", )},
|
||||
),
|
||||
timeout=int(self._env_first("TIM_COMPLETE_INVOICES_TIMEOUT", "TIM_PROFILE_BILL_TIMEOUT", default="30")),
|
||||
)
|
||||
@@ -531,7 +557,7 @@ class TimApiClient:
|
||||
headers=self._headers(
|
||||
client_id=None,
|
||||
auth=os.getenv("TIM_PROFILE_FULL_AUTH", ""),
|
||||
extra={"ClientID": os.getenv("TIM_PROFILE_FULL_CLIENT_ID", "AIAGENTCR")},
|
||||
extra={"ClientID": self._env_first("TIM_PROFILE_FULL_CLIENT_ID")},
|
||||
),
|
||||
timeout=int(self._env_first("TIM_PROFILE_FULL_TIMEOUT", default="30")),
|
||||
)
|
||||
@@ -581,7 +607,7 @@ class TimApiClient:
|
||||
client_id=None,
|
||||
auth=self._env_first("TIM_BILL_PDF_AUTH", "TIM_SECURE_PDF_AUTH", "TIM_INVOICE_RECOVER_AUTH"),
|
||||
extra={
|
||||
"clientId": self._env_first("TIM_BILL_PDF_CLIENT_ID", "TIM_SECURE_PDF_CLIENT_ID", default="AIAGENTCR"),
|
||||
"clientId": self._env_first("TIM_BILL_PDF_CLIENT_ID", "TIM_SECURE_PDF_CLIENT_ID", ),
|
||||
"Accept": "application/pdf",
|
||||
},
|
||||
)
|
||||
@@ -621,7 +647,7 @@ class TimApiClient:
|
||||
headers=self._headers(
|
||||
client_id=None,
|
||||
auth=self._env_first("TIM_SECURE_PDF_AUTH", "TIM_BILL_PDF_AUTH", "TIM_INVOICE_RECOVER_AUTH"),
|
||||
extra={"clientid": self._env_first("TIM_INVOICE_RECOVER_CLIENT_ID", "TIM_SECURE_PDF_CLIENT_ID", default="AIAGENTCR")},
|
||||
extra={"clientid": self._env_first("TIM_INVOICE_RECOVER_CLIENT_ID", "TIM_SECURE_PDF_CLIENT_ID", )},
|
||||
),
|
||||
timeout=int(os.getenv("TIM_INVOICE_RECOVER_TIMEOUT", "30")),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user