""" Auditoría mes a mes de la clasificación de homicidios. Para cada mes M: entrena el multiclase SOLO con casos tipificados anteriores a M y lo aplica a todo lo del mes M sin ver su etiqueta. Responde dos preguntas: 1. ¿Clasificó SINADEF el mes M con las mismas reglas que antes? Compara los homicidios que el modelo espera entre los tipificados del mes con los que SINADEF efectivamente registró. Si las reglas no cambiaron, la diferencia es pequeña y similar a la de meses anteriores. 2. ¿Cuántos homicidios hay escondidos en "NO SE CONOCE" del mes M? Suma de probabilidades calibradas sobre los sin tipificar del mes. Uso: python auditoria_mensual.py 2026-04 2026-09 """ from __future__ import annotations import sys import time from pathlib import Path import numpy as np import pandas as pd BASE = Path(__file__).resolve().parent sys.path.insert(0, str(BASE)) from features_v2 import cargar, preparar # noqa: E402 from multiclase import COLS, construir # noqa: E402 ARMA = r"PROYECTIL|ARMA DE FUEGO|\bPAF\b|ARMA BLANCA|ESTRANGULA|APUÑAL|APU.ALAMIENTO" def main(desde: str, hasta: str): t0 = time.time() df = preparar(cargar()) df["mes"] = pd.to_datetime(df.FECHA).dt.strftime("%Y-%m") filas, casos = [], [] for mes in pd.period_range(desde, hasta, freq="M").strftime("%Y-%m"): tr = df[(df.mes < mes) & (df.MUERTE_VIOLENTA != "NO SE CONOCE")] te = df[df.mes == mes].copy() m = construir().fit(tr[COLS], tr.MUERTE_VIOLENTA.to_numpy()) cf = list(m.named_steps["clf"].classes_) p = m.predict_proba(te[COLS]) te["p_hom"] = p[:, cf.index("HOMICIDIO")] te["pred"] = [cf[i] for i in p.argmax(1)] te["conf"] = p.max(1) lab, nsc = te[te.MUERTE_VIOLENTA != "NO SE CONOCE"], te[te.MUERTE_VIOLENTA == "NO SE CONOCE"] txt = nsc[["DEBIDO_CAUSA_" + x for x in "ABCDEF"]].astype(str).agg(" ".join, axis=1).str.upper() reg = int((lab.MUERTE_VIOLENTA == "HOMICIDIO").sum()) filas.append({ "mes": mes, "hom_registrados": reg, "hom_esperados_en_tipificados": round(lab.p_hom.sum(), 1), # Homicidios que SINADEF tipificó como otra cosa y viceversa "tipificados_otro_pero_perfil_hom": int(((lab.MUERTE_VIOLENTA != "HOMICIDIO") & (lab.p_hom >= .9)).sum()), "nsc": len(nsc), "hom_esperados_en_nsc": round(nsc.p_hom.sum(), 1), "nsc_perfil_hom_90": int(((nsc.pred == "HOMICIDIO") & (nsc.conf >= .9)).sum()), "nsc_con_arma_en_texto": int(txt.str.contains(ARMA, regex=True).sum()), }) nsc = nsc.assign(arma_en_texto=txt.str.contains(ARMA, regex=True).values) casos.append(nsc) print(f" {mes} listo ({time.time()-t0:.0f}s)", flush=True) r = pd.DataFrame(filas) r["hom_ajustado"] = (r.hom_registrados + r.hom_esperados_en_nsc).round() r["desvio_reglas_%"] = ((r.hom_registrados / r.hom_esperados_en_tipificados - 1) * 100).round(1) r.to_csv(BASE / "auditoria_mensual.csv", index=False) pd.concat(casos).to_parquet(BASE / "auditoria_mensual_nsc.parquet", index=False) pd.set_option("display.width", 250) print("\n", r.to_string(index=False)) if __name__ == "__main__": main(*(sys.argv[1:3] if len(sys.argv) > 2 else ("2026-04", "2026-09")))