You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
orquestrador/app/services/handlers.py

196 lines
6.7 KiB
Python

from typing import Optional, List, Dict, Any
from datetime import datetime
import hashlib
import re
import httpx
from fastapi import HTTPException
from app.core.settings import settings
from app.services.fakerapi_client import FakerApiClient
def normalize_cpf(value: str) -> str:
return re.sub(r"\D", "", value or "")
def _parse_float(value: Any, default: float = 0.0) -> float:
if value is None:
return default
if isinstance(value, (int, float)):
return float(value)
text = str(value).replace("R$", "").replace(" ", "")
text = text.replace(".", "").replace(",", ".") if "," in text else text
try:
return float(text)
except Exception:
return default
def _stable_int(seed_text: str) -> int:
digest = hashlib.sha256(seed_text.encode("utf-8")).hexdigest()
return int(digest[:16], 16)
def _cpf_from_any(value: Any) -> str:
as_int = _stable_int(str(value)) % (10**11)
return str(as_int).zfill(11)
async def _fetch_faker_products(count: int) -> List[Dict[str, Any]]:
client = FakerApiClient()
try:
return await client.fetch_resource("products", quantity=count)
except httpx.HTTPStatusError as exc:
status_code = exc.response.status_code if exc.response is not None else 502
request_url = str(exc.request.url) if exc.request is not None else "desconhecida"
raise HTTPException(
status_code=502,
detail=f"FakerAPI retornou HTTP {status_code} em '{request_url}'.",
)
except httpx.RequestError as exc:
raise HTTPException(
status_code=502,
detail=(
"Falha de rede ao acessar FakerAPI (products). "
f"{exc.__class__.__name__}: {exc}. "
"Verifique egress/NAT do Cloud Run e resolucao DNS."
),
)
except Exception:
raise HTTPException(
status_code=502,
detail="Falha de integracao com FakerAPI ao consultar products.",
)
async def _fetch_faker_persons(count: int) -> List[Dict[str, Any]]:
client = FakerApiClient()
try:
return await client.fetch_resource("persons", quantity=count)
except httpx.HTTPStatusError as exc:
status_code = exc.response.status_code if exc.response is not None else 502
request_url = str(exc.request.url) if exc.request is not None else "desconhecida"
raise HTTPException(
status_code=502,
detail=f"FakerAPI retornou HTTP {status_code} em '{request_url}'.",
)
except httpx.RequestError as exc:
raise HTTPException(
status_code=502,
detail=(
"Falha de rede ao acessar FakerAPI (persons). "
f"{exc.__class__.__name__}: {exc}. "
"Verifique egress/NAT do Cloud Run e resolucao DNS."
),
)
except Exception:
raise HTTPException(
status_code=502,
detail="Falha de integracao com FakerAPI ao consultar persons.",
)
async def consultar_estoque(preco_max: float, categoria: Optional[str] = None) -> List[Dict[str, Any]]:
raw = await _fetch_faker_products(settings.fakerapi_products_quantity)
registros: List[Dict[str, Any]] = []
for item in raw:
categories = item.get("categories")
if isinstance(categories, list) and categories:
category_value = str(categories[0])
else:
category_value = str(item.get("category") or "geral")
registro = {
"id": item.get("id"),
"modelo": item.get("name") or item.get("title") or "Veiculo",
"categoria": category_value.lower(),
"preco": _parse_float(item.get("price"), 0.0),
}
registros.append(registro)
categoria_norm = categoria.lower() if categoria else None
return [
r for r in registros
if _parse_float(r.get("preco"), 0.0) <= preco_max
and (categoria_norm is None or str(r.get("categoria", "")).lower() == categoria_norm)
]
async def validar_cliente_venda(cpf: str, valor_veiculo: float) -> Dict[str, Any]:
cpf_norm = normalize_cpf(cpf)
raw = await _fetch_faker_persons(settings.fakerapi_persons_quantity)
registros: List[Dict[str, Any]] = []
for item in raw:
person_id = item.get("id") or item.get("email") or item.get("firstname")
generated_cpf = _cpf_from_any(person_id)
entropy = _stable_int(f"{generated_cpf}:{settings.fakerapi_seed}")
limite = float(30000 + (entropy % 150000))
score = int(300 + (entropy % 550))
possui_restricao = (entropy % 7 == 0)
nome = f"{item.get('firstname', '')} {item.get('lastname', '')}".strip() or "Cliente"
registros.append(
{
"cpf": generated_cpf,
"nome": nome,
"score": score,
"limite_credito": limite,
"possui_restricao": possui_restricao,
}
)
cliente = next((r for r in registros if normalize_cpf(r.get("cpf", "")) == cpf_norm), None)
if not cliente:
entropy = _stable_int(f"{cpf_norm}:{settings.fakerapi_seed}")
cliente = {
"cpf": cpf_norm,
"nome": "Cliente Faker",
"score": int(300 + (entropy % 550)),
"limite_credito": float(30000 + (entropy % 150000)),
"possui_restricao": (entropy % 7 == 0),
}
limite = _parse_float(cliente.get("limite_credito", 0), 0.0)
restricao = bool(cliente.get("possui_restricao", False))
aprovado = (not restricao) and (valor_veiculo <= limite)
return {
"aprovado": aprovado,
"cpf": cpf_norm,
"nome": cliente.get("nome"),
"score": cliente.get("score"),
"limite_credito": limite,
"possui_restricao": restricao,
"valor_veiculo": valor_veiculo,
}
async def avaliar_veiculo_troca(modelo: str, ano: int, km: int) -> Dict[str, Any]:
ano_atual = datetime.now().year
idade = max(0, ano_atual - ano)
base = 80000.0
valor = base * (0.85 ** idade) - (km * 0.03)
valor = max(5000.0, valor)
return {
"modelo": modelo,
"ano": ano,
"km": km,
"valor_estimado_troca": round(valor, 2),
}
async def agendar_revisao(placa: str, data_hora: str) -> Dict[str, Any]:
raise HTTPException(
status_code=503,
detail="FakerAPI nao suporta escrita/persistencia. Endpoint disponivel apenas para leitura de dados ficticios.",
)
async def cancelar_pedido(numero_pedido: str, motivo: str) -> Dict[str, Any]:
raise HTTPException(
status_code=503,
detail="FakerAPI nao suporta cancelamento persistente de pedidos. Endpoint indisponivel neste modo.",
)