analytical_force / src /analytics /contract_metrics.py
ViniciusKhan's picture
Corrige unidade do campo de reajuste (percent vs valor)
373caa7
Raw
History Blame Contribute Delete
7.04 kB
"""Cálculo de métricas de Contratos (``Contrato_oPT__c``).
Calcula, a partir dos DataFrames já extraídos do Salesforce:
- quantos contratos foram modificados no dia e quem modificou cada um
(usa apenas campos padrão: ``LastModifiedDate``/``LastModifiedBy``);
- o total de reajuste aplicado no MÊS corrente, quando o módulo configurável
de reajuste estiver ativo (``value_field``/``previous_value_field``);
- um alerta de qualidade de dados quando o reajuste informado no contrato
diverge do delta calculado (valor atual - valor anterior), que costuma
indicar erro de preenchimento (ex.: valor anterior errado).
Segue o princípio do projeto: Python calcula, a IA apenas interpreta o JSON
resultante. Este módulo NUNCA deve ser chamado pela camada de IA.
"""
from __future__ import annotations
from typing import Any
import pandas as pd
AVISO_REAJUSTE = (
"Os valores de reajuste são estimados pela diferença entre o valor atual "
"e o valor anterior do contrato. Podem não refletir com 100% de precisão "
"o reajuste real caso haja erro de preenchimento no contrato (ex.: valor "
"anterior lançado incorretamente)."
)
_TOLERANCIA_CENTAVOS = 0.01
_TOLERANCIA_PONTOS_PERCENTUAIS = 0.1
def _valor_relacionamento(linha: Any, campo: str) -> Any:
atual: Any = linha
for parte in campo.split("."):
if atual is None:
return None
if isinstance(atual, dict):
atual = atual.get(parte)
elif hasattr(atual, "get"):
atual = atual.get(parte)
else:
return None
return atual
def _serializar_data(valor: Any) -> str | None:
if valor is None:
return None
try:
if pd.isna(valor):
return None
except (TypeError, ValueError):
pass
try:
return valor.isoformat()
except AttributeError:
texto = str(valor).strip()
return texto or None
def _listar_contratos_modificados(df: pd.DataFrame, account_name_field: str) -> list[dict[str, Any]]:
if df is None or df.empty:
return []
itens: list[dict[str, Any]] = []
for _, linha in df.iterrows():
item: dict[str, Any] = {
"id": linha.get("Id"),
"name": linha.get("Name") or linha.get("Id"),
"modified_by": _valor_relacionamento(linha, "LastModifiedBy.Name"),
"modified_at": _serializar_data(linha.get("LastModifiedDate")),
}
if account_name_field:
item["account_name"] = _valor_relacionamento(linha, account_name_field)
itens.append(item)
return itens
def calculate_contract_metrics(
df_modificados: pd.DataFrame | None,
df_reajustados: pd.DataFrame | None,
source: dict[str, Any] | None,
) -> dict[str, Any]:
source = source or {}
account_name_field = source.get("account_name_field") or ""
value_field = source.get("value_field") or ""
previous_field = source.get("previous_value_field") or ""
readjustment_field = source.get("readjustment_field") or ""
readjustment_field_unit = (source.get("readjustment_field_unit") or "amount").strip().lower()
if readjustment_field_unit not in ("amount", "percent"):
readjustment_field_unit = "amount"
readjustment_configured = bool(
value_field and previous_field and source.get("readjustment_date_field")
)
modificados = _listar_contratos_modificados(df_modificados, account_name_field)
resultado: dict[str, Any] = {
"configured": bool(source.get("object")),
"modified_today_count": len(modificados),
"modified_today": modificados,
"readjustment_configured": readjustment_configured,
"readjustment_month_count": 0,
"readjustment_month_total": 0.0,
"readjustment_month_avg_percent": None,
"readjustment_inconsistent_count": 0,
"readjustment_contracts": [],
"readjustment_disclaimer": AVISO_REAJUSTE,
}
if not readjustment_configured or df_reajustados is None or df_reajustados.empty:
return resultado
df = df_reajustados.copy()
df["_valor_atual"] = pd.to_numeric(df.get(value_field), errors="coerce")
df["_valor_anterior"] = pd.to_numeric(df.get(previous_field), errors="coerce")
validos = df.dropna(subset=["_valor_atual", "_valor_anterior"])
if validos.empty:
return resultado
validos = validos.copy()
validos["_delta"] = validos["_valor_atual"] - validos["_valor_anterior"]
tem_informado = bool(readjustment_field) and readjustment_field in validos.columns
if tem_informado:
validos["_informado"] = pd.to_numeric(validos[readjustment_field], errors="coerce")
contratos: list[dict[str, Any]] = []
percentuais: list[float] = []
inconsistentes = 0
for _, linha in validos.iterrows():
anterior = float(linha["_valor_anterior"])
atual = float(linha["_valor_atual"])
delta = float(linha["_delta"])
pct = (delta / anterior * 100.0) if anterior else None
if pct is not None:
percentuais.append(pct)
informado_pct: float | None = None
informado_amount: float | None = None
inconsistente = False
if tem_informado:
bruto = linha.get("_informado")
if bruto is not None and not pd.isna(bruto):
bruto = float(bruto)
if readjustment_field_unit == "percent":
informado_pct = bruto
informado_amount = anterior * (bruto / 100.0) if anterior else None
if pct is not None:
inconsistente = abs(informado_pct - pct) > _TOLERANCIA_PONTOS_PERCENTUAIS
else:
informado_amount = bruto
informado_pct = (bruto / anterior * 100.0) if anterior else None
inconsistente = abs(informado_amount - delta) > _TOLERANCIA_CENTAVOS
if inconsistente:
inconsistentes += 1
item: dict[str, Any] = {
"id": linha.get("Id"),
"name": linha.get("Name") or linha.get("Id"),
"previous_value": anterior,
"current_value": atual,
"delta": delta,
"reported_readjustment_percent": informado_pct,
"reported_readjustment_amount": informado_amount,
"percent": pct,
"inconsistent": inconsistente,
}
if account_name_field:
item["account_name"] = _valor_relacionamento(linha, account_name_field)
contratos.append(item)
resultado["readjustment_month_count"] = len(contratos)
resultado["readjustment_month_total"] = float(validos["_delta"].sum())
resultado["readjustment_month_avg_percent"] = (
sum(percentuais) / len(percentuais) if percentuais else None
)
resultado["readjustment_inconsistent_count"] = inconsistentes
resultado["readjustment_contracts"] = sorted(
contratos, key=lambda c: abs(c["delta"]), reverse=True
)
return resultado