c06e15c058
Audit de second niveau : lit les hand-off out/ des livrables (workflow vente, Dossier Vente, commissions, e-CF DGII, CONFOTUR) et vérifie 17 contrôles en 5 dimensions (D1 Traçabilité/ISA 500 · D2 AML-UAF/Ley 155-17 · D3 Fiscal e-CF/Ley 32-23 · D4 Intégrité/IFRS · D5 Gouvernance-SoD/ISA 315). Anti-invention #6 : paramètre réglementaire non confirmé → A_CONFIRMER (open item assigné au métier), jamais fabriqué. Verdict PASS_WITH_OPEN_ITEMS (13 PASS, 0 FAIL, 4 à confirmer). Réutilise validateur Publiciste + RoleResolver CRM + roles_targeting CONFOTUR (zéro duplication). 37 tests · 15 invariants · build déterministe · régression 341 tests verts. Job CI qa-audit-5d-tests + gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
2.7 KiB
Python
84 lines
2.7 KiB
Python
"""Assemblage déterministe du rapport d'audit 5D à partir des contrôles.
|
|
|
|
Verdict global :
|
|
FAIL — au moins un contrôle FAIL (défaut réel à corriger).
|
|
PASS_WITH_OPEN_ITEMS — zéro FAIL, mais des paramètres réglementaires restent à
|
|
confirmer (A_CONFIRMER) → backlog honnête, pas un échec.
|
|
PASS — tous les contrôles PASS.
|
|
Aucune date/horodatage (déterminisme du build · gate CI reproductible).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from . import controls
|
|
from .artifacts import load_artifacts
|
|
|
|
REPORT_NAME = "OTO QA · Audit 5D Conformité"
|
|
REPORT_VERSION = "1.0"
|
|
|
|
_PASS, _FAIL, _AC = controls.PASS, controls.FAIL, controls.A_CONFIRMER
|
|
|
|
|
|
def _dimension_status(statuts: list[str]) -> str:
|
|
if _FAIL in statuts:
|
|
return _FAIL
|
|
if _AC in statuts:
|
|
return "PASS_WITH_OPEN_ITEMS"
|
|
return _PASS
|
|
|
|
|
|
def build(spec: dict[str, Any]) -> dict[str, Any]:
|
|
art = load_artifacts()
|
|
results = controls.run_all(art, spec)
|
|
|
|
# Synthèse par dimension (ordre canonique de DIMENSIONS).
|
|
dims = []
|
|
for d in controls.DIMENSIONS:
|
|
rs = [r for r in results if r["dimension"] == d["id"]]
|
|
statuts = [r["statut"] for r in rs]
|
|
dims.append({
|
|
"id": d["id"], "titre": d["titre"], "reference": d["reference"],
|
|
"controls_total": len(rs),
|
|
"pass": statuts.count(_PASS),
|
|
"fail": statuts.count(_FAIL),
|
|
"a_confirmer": statuts.count(_AC),
|
|
"statut": _dimension_status(statuts),
|
|
})
|
|
|
|
# Open items = tout A_CONFIRMER, avec son propriétaire métier.
|
|
open_items = [{
|
|
"control": r["id"], "dimension": r["dimension"],
|
|
"detail": r["detail"], "owner": r["owner"],
|
|
} for r in results if r["statut"] == _AC]
|
|
|
|
all_statuts = [r["statut"] for r in results]
|
|
totals = {
|
|
"controls": len(results),
|
|
"pass": all_statuts.count(_PASS),
|
|
"fail": all_statuts.count(_FAIL),
|
|
"a_confirmer": all_statuts.count(_AC),
|
|
}
|
|
if totals["fail"]:
|
|
verdict = "FAIL"
|
|
elif totals["a_confirmer"]:
|
|
verdict = "PASS_WITH_OPEN_ITEMS"
|
|
else:
|
|
verdict = "PASS"
|
|
|
|
# On n'expose pas la fonction (`fn`) : le rapport reste sérialisable JSON.
|
|
public_results = [{k: v for k, v in r.items() if k != "fn"} for r in results]
|
|
|
|
return {
|
|
"audit": REPORT_NAME,
|
|
"version": REPORT_VERSION,
|
|
"reference_cadre": spec.get("reference_cadre", ""),
|
|
"dimensions": dims,
|
|
"controls": public_results,
|
|
"open_items": open_items,
|
|
"totals": totals,
|
|
"verdict": verdict,
|
|
"notes": spec.get("notes", []),
|
|
}
|