"""Assemblage déterministe du rapport d'audit 4Big. Verdict global = PASS ssi (a) la couverture est bijective vs le CI (100% des livrables gated, hors auditeur) ET (b) TOUS les modules audités atteignent ≥ 95/100 (aucun sous le seuil). Sinon FAIL — l'audit est un GATE, pas un rapport indicatif. Le nombre de modules est DÉRIVÉ (`len(modules)` == `totals.modules`), jamais figé ici : il croît quand un livrable est gated (17 → 22 à ce jour) sans retoucher ce docstring. Aucune date/horodatage → build reproductible + diffable (gate CI stable). """ from __future__ import annotations from typing import Any from . import registry, scoring REPORT_NAME = "OTO QA · Audit 4Big Qualité" def build(spec: dict[str, Any]) -> dict[str, Any]: coverage = registry.coverage_report(spec) modules = scoring.score_all(spec) scores = [m["score"] for m in modules] below = [m["id"] for m in modules if m["verdict"] != "PASS"] totals = { "modules": len(modules), "pass": sum(1 for m in modules if m["verdict"] == "PASS"), "fail": len(below), "min_score": min(scores) if scores else 0, "max_score": max(scores) if scores else 0, "pass_rate_pct": scoring._round_half_up( 100.0 * sum(1 for m in modules if m["verdict"] == "PASS") / len(modules) ) if modules else 0, } verdict = "PASS" if (coverage["ok"] and not below) else "FAIL" return { "audit": REPORT_NAME, "version": spec["version"], "reference_cadre": spec["reference_cadre"], "pass_score": spec["thresholds"]["pass_score"], "coverage": coverage, "criteria": [ {"id": c["id"], "label": c["label"], "weight": c["weight"]} for c in spec["criteria"] ], "modules": modules, "totals": totals, "verdict": verdict, "notes": spec.get("notes", []), }