[DTP-Worker] Sprint 5 · Générateur Audit 5D conformité (QA · roadmap L58)
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>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""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", []),
|
||||
}
|
||||
Reference in New Issue
Block a user