crowdata / app /utils /scoring.py
YOSOYYONOSOYOTRO's picture
Upload folder using huggingface_hub (part 2)
4223796 verified
Raw
History Blame Contribute Delete
9.28 kB
"""Módulo de scoring crediticio unificado."""
import logging
from datetime import datetime
from typing import List, Optional
from app.reports.schemas import (
ScoreCrediticio, ScoreHistorial,
PersonReport, CompanyReport, GroupReport
)
logger = logging.getLogger(__name__)
def compute_score(report: PersonReport, history_scores: List[ScoreHistorial] = None) -> ScoreCrediticio:
"""
Calcula el score crediticio (1-100) para una persona basado en su reporte.
"""
score = 60 # Base score
factores = []
# 1. BCRA Situación Actual
sit = report.financiero.bcra_situacion_actual
if sit is None or sit <= 1:
score += 40
factores.append("BCRA Situación Normal (Sit. 1 o sin deuda) +40pts")
elif sit == 2:
score += 28
factores.append("BCRA Situación 2 (Seguimiento especial) +28pts")
elif sit == 3:
score += 14
factores.append("BCRA Situación 3 (Con problemas) +14pts")
elif sit == 4:
score += 4
factores.append("BCRA Situación 4 (Alto riesgo de insolvencia) +4pts")
else:
# Sit 5 o 6
factores.append(f"BCRA Situación {sit} (Irrecuperable / Incoobrable) +0pts")
# 2. Cheques Rechazados
cant_cheques = len(report.financiero.cheques_rechazados)
if cant_cheques > 0:
deduccion = cant_cheques * 5
score -= deduccion
factores.append(f"{cant_cheques} Cheques rechazados -{deduccion}pts")
# 3. Causas Judiciales
# Consideramos causas judiciales activas (que no tengan estado Terminado/Archivado)
causas_activas = 0
for c in report.judicial.causas:
estado = (c.estado or "").lower()
if not any(x in estado for x in ["archiv", "termin", "finaliz", "cerrad", "resuelt"]):
causas_activas += 1
if causas_activas > 0:
deduccion = causas_activas * 4
score -= deduccion
factores.append(f"{causas_activas} Causas judiciales activas -{deduccion}pts")
# 4. Inhibiciones / Embargos
# Usar solo patrimonial.inhibiciones que ya está consolidado (evitar duplicados con judicial)
inhibiciones_totales = len(report.patrimonial.inhibiciones)
if inhibiciones_totales > 0:
deduccion = inhibiciones_totales * 10
score -= deduccion
factores.append(f"{inhibiciones_totales} Inhibiciones/Embargos registrados -{deduccion}pts")
# 5. AFIP Activo (Señal positiva)
estado_afip = (report.fiscal.estado_afip or "").lower()
if "activ" in estado_afip:
score += 5
factores.append("AFIP Activo +5pts")
# 6. Antigüedad laboral ANSES
if report.previsional.tiene_aportes:
alta_os = report.previsional.fecha_alta_obra_social
es_antiguo = False
if alta_os:
try:
# Intentamos parsear la fecha de alta. Formatos típicos: YYYY-MM-DD o DD/MM/YYYY
fecha_alta = None
for fmt in ("%Y-%m-%d", "%d/%m/%Y", "%Y/%m/%d"):
try:
fecha_alta = datetime.strptime(alta_os, fmt)
break
except ValueError:
continue
if fecha_alta:
anios = (datetime.now() - fecha_alta).days / 365.25
if anios >= 3.0:
es_antiguo = True
except Exception as e:
logger.debug(f"Error parseando fecha alta obra social: {e}")
# Fallback si no hay fecha pero tiene aportes al día
if es_antiguo or (report.previsional.aportes_al_dia and not alta_os):
score += 3
factores.append("Antigüedad laboral/Aportes > 3 años +3pts")
# 7. Inmuebles registrados
cant_inmuebles = len(report.patrimonial.inmuebles)
if cant_inmuebles > 0:
puntos_inm = min(6, cant_inmuebles * 2)
score += puntos_inm
factores.append(f"{cant_inmuebles} Inmuebles registrados +{puntos_inm}pts (máx +6)")
# 8. Marcas INPI (solo para empresas; en personas siempre 0)
cant_marcas = len(getattr(report, "marcas_inpi", []))
if cant_marcas > 0:
puntos_marcas = min(4, cant_marcas * 2)
score += puntos_marcas
factores.append(f"{cant_marcas} Marcas INPI registradas +{puntos_marcas}pts (máx +4)")
# Acotar el score final entre 1 y 100
score_final = max(1, min(100, score))
# Determinar nivel
if score_final >= 80:
nivel = "Excelente"
elif score_final >= 60:
nivel = "Bueno"
elif score_final >= 40:
nivel = "Regular"
elif score_final >= 20:
nivel = "Malo"
else:
nivel = "Crítico"
return ScoreCrediticio(
valor=score_final,
nivel=nivel,
historial=history_scores or [],
factores=factores
)
def compute_company_score(report: CompanyReport, history_scores: List[ScoreHistorial] = None) -> ScoreCrediticio:
"""
Calcula el score crediticio (0-1000) para una empresa/ persona jurídica.
Replica la lógica del frontend: base 950, penalizaciones por BCRA, cheques y causas.
"""
score = 950
factores = []
# 1. BCRA Situación Actual
sit = report.financiero.bcra_situacion_actual
if sit is None or sit <= 1:
factores.append("BCRA Situación Normal +0pts")
elif sit == 2:
score -= 150
factores.append("BCRA Situación 2 -150pts")
elif sit == 3:
score -= 300
factores.append("BCRA Situación 3 -300pts")
elif sit == 4:
score -= 500
factores.append("BCRA Situación 4 -500pts")
elif sit >= 5:
score -= 750
factores.append(f"BCRA Situación {sit} -750pts")
# 2. Cheques Rechazados (máx -250)
cant_cheques = len(report.financiero.cheques_rechazados)
if cant_cheques > 0:
deduccion = min(250, cant_cheques * 50)
score -= deduccion
factores.append(f"{cant_cheques} Cheques rechazados -{deduccion}pts (máx -250)")
# 3. Causas Judiciales (máx -200)
cant_causas = len(report.judicial.causas)
if cant_causas > 0:
deduccion = min(200, cant_causas * 40)
score -= deduccion
factores.append(f"{cant_causas} Causas judiciales -{deduccion}pts (máx -200)")
# 4. AFIP Activo (bonus)
estado_afip = (report.fiscal.estado_afip or "").lower()
if "activ" in estado_afip:
score += 20
factores.append("AFIP Activo +20pts")
# 5. IGJ inscripto (bonus)
if report.igj.numero_inscripcion:
score += 10
factores.append("IGJ Inscripto +10pts")
# Acotar entre 0 y 1000
score_final = max(0, min(1000, score))
# Determinar nivel
if score_final >= 700:
nivel = "Excelente"
elif score_final >= 450:
nivel = "Medio"
else:
nivel = "Crítico"
return ScoreCrediticio(
valor=score_final,
nivel=nivel,
historial=history_scores or [],
factores=factores
)
def compute_group_score(report: GroupReport, history_scores: List[ScoreHistorial] = None) -> ScoreCrediticio:
"""
Calcula el score crediticio (0-1000) para un grupo económico (titular + empresas + vehículos).
Replica la lógica del frontend: base 950, penalizaciones por BCRA/riesgos, bonus por vehículos.
"""
score = 950
factores = []
# 1. BCRA del titular
sit = report.target.financiero.bcra_situacion_actual
if sit is None or sit <= 1:
factores.append("BCRA Titular Normal +0pts")
elif sit == 2:
score -= 150
factores.append("BCRA Titular Sit. 2 -150pts")
elif sit == 3:
score -= 300
factores.append("BCRA Titular Sit. 3 -300pts")
elif sit == 4:
score -= 500
factores.append("BCRA Titular Sit. 4 -500pts")
elif sit >= 5:
score -= 750
factores.append(f"BCRA Titular Sit. {sit} -750pts")
# 2. Riesgos consolidados (máx -300)
total_riesgos = len(report.riesgo_consolidado)
if total_riesgos > 0:
deduccion = min(300, total_riesgos * 60)
score -= deduccion
factores.append(f"{total_riesgos} Riesgos consolidados -{deduccion}pts (máx -300)")
# 3. Vehículos vinculados (bonus, máx +100)
cant_vehiculos = len(report.vehiculos_vinculados)
if cant_vehiculos > 0:
bonus = min(100, cant_vehiculos * 25)
score += bonus
factores.append(f"{cant_vehiculos} Vehículos vinculados +{bonus}pts (máx +100)")
# 4. Empresas vinculadas (bonus si están activas)
empresas_activas = 0
for e in report.empresas_vinculadas:
if "activ" in (e.fiscal.estado_afip or "").lower():
empresas_activas += 1
if empresas_activas > 0:
bonus = min(50, empresas_activas * 25)
score += bonus
factores.append(f"{empresas_activas} Empresas activas +{bonus}pts (máx +50)")
# Acotar entre 0 y 1000
score_final = max(0, min(1000, score))
# Determinar nivel
if score_final >= 700:
nivel = "Excelente"
elif score_final >= 450:
nivel = "Medio"
else:
nivel = "Crítico"
return ScoreCrediticio(
valor=score_final,
nivel=nivel,
historial=history_scores or [],
factores=factores
)