"""MP1 · paso 31: compara la implementación 1 (salidas/30_.json, 22_rec.json) con la segunda (impl2/salidas/impl2_.json) con las tolerancias pre-registradas (§12 (7)): AUC y pendiente ≤ 1e-6; Brier y log-loss ≤ 1e-9; deciles y coberturas idénticos (≤ 1e-12). Imagen bquantagents-worker o el host (solo json). Escribe salidas/31_comparar_impl.json. """ import json, os, math TOL = {"auc": 1e-6, "pendiente": 1e-6, "brier": 1e-9, "log_loss": 1e-9, "cal": 1e-9, "dec": 1e-12} out = {} def d(a, b): return None if a is None or b is None else abs(float(a) - float(b)) for h in ("ou", "des", "mod", "ser", "rec"): f1 = f"/w/salidas/30_{h}.json" if h != "rec" else "/w/salidas/22_rec.json" f2 = f"/w/impl2/salidas/impl2_{h}.json" if not (os.path.exists(f1) and os.path.exists(f2)): out[h] = "falta alguno"; continue a, b = json.load(open(f1)), json.load(open(f2)) r = {} if h == "rec": for k in ("p", "p_raw", "b3"): r[f"auc_{k}"] = d(a["auc"][k], b["prueba"]["auc"][k]) r["o_e"] = d(a["o_e"], b["prueba"]["o_e"]); r["pendiente"] = d(a["pendiente"], b["prueba"]["pendiente"]) ok = all(v is not None and v <= TOL["auc"] for k, v in r.items()) else: pa, pb = a["prueba"], b["prueba"] for k in pa["auc"]: r[f"auc_{k}"] = d(pa["auc"][k], pb["auc"].get(k)) if "p-b2" in pa.get("diferencias", {}): r["auc_p_sub_b2"] = d(pa["diferencias"]["p-b2"]["auc_p"], pb["auc_b2_subconjunto"]["p"]) r["auc_b2"] = d(pa["diferencias"]["p-b2"]["auc_b2"], pb["auc_b2_subconjunto"]["p_b2"]) for k in ("brier", "log_loss"): r[k] = d(pa[k], pb[k]) r["cal_global_puntos"] = d(pa["cal_global_puntos"], pb["cal_global_puntos"]) r["pendiente"] = d(pa["pendiente"], pb["pendiente"]) dec_a = [x for x in pa["deciles"] if x["n"] > 0] dec_b = [x for x in pb["deciles"] if x["n"] and x["n"] > 0] # impl2 conserva los tramos vacíos de qcut con n = 0 r["deciles_mismo_numero"] = len(dec_a) == len(dec_b) r["deciles_max_dif"] = max((max(abs(x["n"] - y["n"]), abs(x["predicha"] - y["predicha"]), abs(x["observada"] - y["observada"])) for x, y in zip(dec_a, dec_b)), default=None) if r["deciles_mismo_numero"] else None cov_a = (a.get("intervalos") or {}).get("cobertura_test"); cov_b = pb.get("cobertura") r["cobertura"] = d(cov_a, cov_b) if cov_a is not None else ("ambas nulas" if cov_b is None else "distinta") r["auc_recal_p"] = d(a["recal"]["auc"]["p"], b["recal"]["auc"]["p"]) ok = (all(v <= TOL["auc"] for k, v in r.items() if k.startswith("auc") and v is not None) and r["brier"] <= TOL["brier"] and r["log_loss"] <= TOL["log_loss"] and r["pendiente"] <= TOL["pendiente"] and r["cal_global_puntos"] <= 1e-7 and r["deciles_mismo_numero"] and (r["deciles_max_dif"] or 0) <= TOL["dec"] and (r["cobertura"] in ("ambas nulas",) or (isinstance(r["cobertura"], float) and r["cobertura"] <= TOL["dec"]))) r["coincide"] = bool(ok) out[h] = r json.dump(out, open("/w/salidas/31_comparar_impl.json", "w"), indent=1, default=str) print(json.dumps(out, indent=1, default=str))