[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:
Claude Code DTP Worker
2026-07-30 08:41:57 +00:00
parent 35a20247f8
commit c06e15c058
16 changed files with 1699 additions and 1 deletions
@@ -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", []),
}