Risk Engine — matrice multi-dimensionnelle + RBA paramétrable
Module :
risk-svc(cœur du scoring RBA) · consommé parkyc-svc,aml-svc,case-mgmt-svc. Voir ADR-025, ADR-004, ADR-002.
Le Risk Engine est le cœur de la valeur d’une solution KYC/AML. Il classe chaque client, chaque entité, chaque session d’onboarding en LOW / STANDARD / HIGH / PROHIBITED selon une matrice multi-dimensionnelle paramétrable par tenant, et produit une explication auditable de chaque décision.
Ce document spécifie comment le construire, le brancher et le maintenir.
1. Obligation réglementaire — Risk-Based Approach
Section intitulée « 1. Obligation réglementaire — Risk-Based Approach »| Source | Exigence |
|---|---|
| FATF Reco. 10 + 12 + 15 | Classification BC/FT obligatoire par client, produit, pays, transaction |
| 6AMLD (UE) art. 18 | CDD simplifiée / classique / renforcée selon le risque |
| BCT Circulaire 2017-08 art. 8 + annexe D | ”Approche par les risques” obligatoire, classification type |
| Loi 2015-26 TN art. 99-106 | Revue périodique du profil selon niveau |
| FinCEN CDD Rule (31 CFR 1010.230) | Ongoing monitoring based on risk |
| BaFin KWG §25h | Klassifizierung des Geschäftsrisikos |
| RGPD art. 22 | Droit à l’explication des décisions automatisées |
→ En audit, le régulateur demande : politique écrite + procédures + preuve qu’elle a été appliquée à chaque client. Le Risk Engine produit les trois.
2. Modèle de risque — 5 dimensions
Section intitulée « 2. Modèle de risque — 5 dimensions »2.1 Dimensions standard
Section intitulée « 2.1 Dimensions standard »| Dimension | Variables | Source |
|---|---|---|
| client | PEP (own/family/close_assoc), profession, résidence, âge, marital status, activité pro | Form Designer + screening |
| geo | Pays résidence, pays nationalité, FATF greylist/blacklist, CPI Transparency, sanctions pays (OFAC/UN/EU/BCT) | Listes officielles + connecteurs |
| product | Type produit, montant moyen attendu, crypto, offshore, non-face-to-face product | Paramétrage produit banque |
| channel | Face-à-face vs remote, VideoKYC vs selfie, QES vs OTP, agent vs self-service | Workflow runtime |
| aml_screening | PEP hit, sanctions hit, adverse media score | Module AML screening |
2.2 Extension transactionnelle (AML TxMon)
Section intitulée « 2.2 Extension transactionnelle (AML TxMon) »Le même engine est réutilisé avec une 6ème dimension transactional (vélocité, cash intensity, pays contreparties) pour scorer en continu les comptes actifs, pas seulement à l’onboarding. Voir AML Transaction Monitoring.
2.3 Niveaux et politique associée
Section intitulée « 2.3 Niveaux et politique associée »| Niveau | Score | CDD | Pièces | Décision | Revue | SLA STR |
|---|---|---|---|---|---|---|
| LOW | 0–25 | Simplified (SDD) | CIN + selfie | Auto-approve (si fraud guard > 95%) | 5 ans | N/A |
| STANDARD | 26–50 | Standard CDD | CIN + selfie + justif. domicile | Agent 1 décide | 3 ans | J+10 |
| HIGH | 51–80 | Enhanced (EDD) | +ODF + VideoKYC + 4-eyes | Agent 2 + Superviseur | 1 an | J+5 |
| PROHIBITED | 81–100 | Refus | — | Compliance officer + STR | 6 mois si réévalué | J+2 |
3. DSL — Kotlin type-safe builder
Section intitulée « 3. DSL — Kotlin type-safe builder »3.1 Signature complète
Section intitulée « 3.1 Signature complète »riskPolicy("TN-BANQUEX", version = "2.1") {
metadata { owner = "compliance-officer@banquex.tn" description = "Politique LCB-FT 2026 — conforme BCT Circ. 2017-08 + 6AMLD" effectiveFrom = LocalDate.of(2026, 5, 1) reviewBy = LocalDate.of(2027, 5, 1) }
// --- DIMENSIONS ---
dimension("client", weight = 0.30) { score(100) whenMatch { client.pep == PepStatus.OWN } score(75) whenMatch { client.pep in setOf(PepStatus.FAMILY, PepStatus.CLOSE_ASSOC) } score(50) whenMatch { client.profession in HIGH_RISK_PROFESSIONS } score(30) whenMatch { client.residentStatus == NON_RESIDENT } score(0) otherwise }
dimension("geo", weight = 0.25) { score(100) whenMatch { client.country in FATF_BLACKLIST } score(70) whenMatch { client.country in FATF_GREYLIST_TN_BCT } score(50) whenMatch { client.country in TRANSPARENCY_CPI_HIGH_CORRUPTION } score(0) whenMatch { client.country in SAFE_COUNTRIES } score(20) otherwise }
dimension("product", weight = 0.20) { score(80) whenMatch { product.code == "CRYPTO_WALLET" } score(60) whenMatch { product.code == "COMPTE_DEVISE" && product.expectedMonthly > 50_000 } score(30) whenMatch { product.code in listOf("COMPTE_COURANT", "LIVRET") } score(10) whenMatch { product.code == "COMPTE_JEUNE" } }
dimension("channel", weight = 0.15) { score(60) whenMatch { session.channel == REMOTE_SELFIE_ONLY } score(30) whenMatch { session.channel == REMOTE_WITH_QES_TUNTRUST } score(0) whenMatch { session.channel == FACE_TO_FACE_AGENCY } }
dimension("aml_screening", weight = 0.10) { score(100) whenMatch { screening.sanctionsHit == TRUE_POSITIVE } score(80) whenMatch { screening.adverseMediaScore > 0.8 } score(50) whenMatch { screening.pepConfirmed } score(0) otherwise }
// --- THRESHOLDS ---
threshold(LOW to STANDARD, at = 25) threshold(STANDARD to HIGH, at = 50) threshold(HIGH to PROHIBITED, at = 80)
// --- OVERRIDES (priorité > score) ---
mustProhibit whenAny { screening.ofacMatch == TRUE_POSITIVE client.country in listOf("KP", "IR") client.minor && !product.allowsMinor }
mustHigh whenAny { client.pep == PepStatus.OWN client.isLegalRep && entity.ubo.anyPep screening.adverseMediaScore > 0.9 }
// --- MITIGATIONS par niveau ---
mitigationsFor(HIGH) = listOf( "Demander justificatif origine des fonds", "Entretien visio VideoKYC obligatoire", "Approbation 4-eyes", "Revue annuelle du profil" )
mitigationsFor(PROHIBITED) = listOf( "Refus automatique", "Alerte compliance officer", "STR à évaluer sous 48h" )}3.2 Règles invariantes (compile-time)
Section intitulée « 3.2 Règles invariantes (compile-time) »Le DSL enforce à la compilation :
- Somme des
weightdes dimensions doit faire 1.0 (sinon erreur compile) - Chaque dimension doit avoir au moins une règle
score(...) - Les seuils doivent être monotones : LOW→STANDARD < STANDARD→HIGH < HIGH→PROHIBITED
- Les overrides ne peuvent qu’élever le niveau, jamais l’abaisser (invariant métier)
- Tout symbole (pays, profession, product code) référencé doit exister dans le catalogue tenant
3.3 Mapping DSL ↔ JSON (édition UI)
Section intitulée « 3.3 Mapping DSL ↔ JSON (édition UI) »Le compliance officer n’écrit pas du Kotlin. Il utilise l’UI (workflow 10 mockups) qui sérialise en JSON canonique :
{ "tenant_id": "TN-BANQUEX", "version": "2.1", "metadata": { "owner": "compliance-officer@banquex.tn", "description": "..." }, "dimensions": [ { "name": "client", "weight": 0.30, "rules": [ { "score": 100, "predicate": { "eq": ["client.pep", "OWN"] } }, { "score": 75, "predicate": { "in": ["client.pep", ["FAMILY", "CLOSE_ASSOC"]] } }, { "score": 50, "predicate": { "in": ["client.profession", "@HIGH_RISK_PROFESSIONS"] } }, { "score": 30, "predicate": { "eq": ["client.residentStatus", "NON_RESIDENT"] } }, { "score": 0, "otherwise": true } ] } ], "thresholds": [ { "from": "LOW", "to": "STANDARD", "at": 25 }, { "from": "STANDARD", "to": "HIGH", "at": 50 }, { "from": "HIGH", "to": "PROHIBITED", "at": 80 } ], "overrides": { "must_prohibit": [ { "eq": ["screening.ofacMatch", "TRUE_POSITIVE"] }, { "in": ["client.country", ["KP", "IR"]] } ], "must_high": [ { "eq": ["client.pep", "OWN"] } ] }}Round-trip parfait DSL ↔ JSON : l’UI compile le JSON en DSL Kotlin à la publication.
4. Explainability — format auditable
Section intitulée « 4. Explainability — format auditable »Chaque évaluation produit un RiskEvaluation :
{ "evaluation_id": "revl_01HWR2X3K8", "tenant_id": "TN-BANQUEX", "subject_id": "client:5f3a7b2c", "subject_kind": "CLIENT_INDIVIDUAL", "policy_version": "TN-BANQUEX@2.1", "evaluator_version": "risk-engine@1.4.2", "evaluated_at": "2026-04-23T11:42:13Z",
"decision": "HIGH", "global_score": 67.0, "level": "HIGH", "path_taken": "score-based",
"dimensions": [ { "name": "client", "score": 75.0, "weight": 0.30, "contribution": 22.5, "reasons": [ "client.pep == FAMILY (Mustapha B. — Minister 2019-2021, source Open Sanctions)" ] }, { "name": "geo", "score": 50.0, "weight": 0.25, "contribution": 12.5, "reasons": ["client.country == NG (CPI 24, high corruption — Transparency 2025)"] }, { "name": "product", "score": 60.0, "weight": 0.20, "contribution": 12.0, "reasons": ["product = COMPTE_DEVISE with expectedMonthly=65000 TND (threshold 50k)"] }, { "name": "channel", "score": 60.0, "weight": 0.15, "contribution": 9.0, "reasons": ["session.channel == REMOTE_SELFIE_ONLY (no QES)"] }, { "name": "aml_screening", "score": 50.0, "weight": 0.10, "contribution": 5.0, "reasons": ["screening.pepConfirmed == true (WorldCheck 2026-04-22)"] } ],
"overrides_triggered": [],
"recommended_action": "EDD", "required_mitigations": [ "Demander justificatif origine des fonds", "Entretien visio VideoKYC obligatoire", "Approbation 4-eyes", "Revue annuelle du profil" ],
"list_refs": { "fatf_greylist_version": "2025-10", "ofac_version": "2026-04-22", "transparency_cpi_year": 2025 }}4.1 Audiences de l’explain
Section intitulée « 4.1 Audiences de l’explain »| Audience | Champs lus | Usage |
|---|---|---|
| Agent | decision, level, dimensions[].reasons, requiredMitigations | Comprendre pourquoi et que faire |
| Compliance officer | Tout | Revoir, overrider (avec justif), prendre décision finale |
| Auditeur BCT/DGI | policyVersion, evaluatorVersion, listRefs, evaluatedAt, overridesTriggered | Tracer reproductibilité et traçabilité |
| Client (sur demande RGPD art. 22) | level, dimensions[].name + reasons (sanitisés) | Droit à l’explication |
5. Architecture technique
Section intitulée « 5. Architecture technique »5.1 Choix techniques
Section intitulée « 5.1 Choix techniques »| Composant | Choix | Rationale |
|---|---|---|
| Langage | Kotlin 1.9+ (JVM 21) | Type-safe builders, null-safety, immutability par défaut, écosystème JVM |
| Runtime DSL | Kotlin compilation JIT + OPA Rego pour règles complexes | Kotlin compile le DSL en bytecode à la publication → exécution native |
| Cache listes | Caffeine + refresh async Kafka | p99 < 5ms lookup pays/OFAC |
| Stockage policy | PostgreSQL JSONB + trigger d’audit WORM | Versioning natif via append-only + timestamp |
| Évaluations (audit) | Table WORM chiffrée au repos (AES-256) + retention 10 ans | BCT exige 10 ans, RGPD impose pseudonymisation |
| Stream | Kafka topic risk.evaluated (per tenant partition key) | Réutilisé par AML, case mgmt, dashboards |
| Perf target | p50 ≤ 15 ms, p99 ≤ 50 ms | Hot path synchrone sur l’onboarding |
5.2 API — REST endpoints
Section intitulée « 5.2 API — REST endpoints »POST /v1/risk/policiesContent-Type: application/jsonAuthorization: Bearer <compliance-officer-token>
{ "tenant_id": "TN-BANQUEX", "draft_of": "TN-BANQUEX@2.0", "dimensions": [...] }
→ 201 Created{ "policy_id": "pol_01HX...", "version": "draft-2.1", "status": "DRAFT" }POST /v1/risk/policies/{id}/shadow-activate→ 200 OK (nouvelle policy évaluée en shadow mode en parallèle de la prod)POST /v1/risk/policies/{id}/backtest{ "window_months": 6, "sample_size": 5000 }→ 202 Accepted (Temporal workflow, résultat via webhook ou polling)POST /v1/risk/policies/{id}/publishContent-Type: application/json{ "dual_control_approver": "dsi-approver@banquex.tn", "approver_signature": "..." }→ 200 OK (policy devient ACTIVE, l'ancienne passe en ARCHIVED)POST /v1/risk/evaluate{ "subject_kind": "CLIENT_INDIVIDUAL", "client": { ... }, "product": { ... }, "session": { ... }, "screening": { ... }}→ 200 OK (RiskEvaluation, cf. §4)GET /v1/risk/evaluations/{id}→ 200 OK (RiskEvaluation complet, pour audit)5.3 gRPC — pour AML TxMon
Section intitulée « 5.3 gRPC — pour AML TxMon »Pour le scoring temps réel transactionnel (volumétries élevées), gRPC EvaluateTransactional(TxContext) returns (RiskEvaluation) avec streaming bidirectionnel.
6. Cycle de vie d’une policy
Section intitulée « 6. Cycle de vie d’une policy »6.1 Dual control
Section intitulée « 6.1 Dual control »Toute publication de policy requiert deux comptes distincts signant électroniquement :
- Compliance officer (propose)
- Directeur compliance OU DSI (approuve)
Aucune policy ne passe en ACTIVE sans signatures cryptographiques des deux (Ed25519 via Vault), horodatées, attachées à l’audit WORM.
6.2 Shadow mode
Section intitulée « 6.2 Shadow mode »Pendant la phase SHADOW, chaque requête production est évaluée deux fois :
- Par la policy ACTIVE (décision utilisée en vrai)
- Par la policy SHADOW (décision logguée uniquement)
Métriques comparées :
| Métrique | Seuil d’alerte |
|---|---|
| % divergence de niveau | > 15% |
| % auto-approve LOW changé | > ±10% |
| % HIGH → PROHIBITED | > 5% de nouveaux PROHIBITED |
| Taux faux-positifs estimé (vs feedback agents) | > 2x prod |
Si seuils dépassés → la nouvelle policy ne peut pas passer en PUBLISHED sans justif écrite (override du dir compliance).
6.3 Backtesting
Section intitulée « 6.3 Backtesting »Temporal workflow :
- Charge les N derniers dossiers (par défaut 6 mois, échantillon 5000) depuis la table WORM évaluations.
- Ré-évalue chaque dossier avec la policy draft, en tenant compte des listes FATF/OFAC à l’époque de l’évaluation originale (reproductibilité par
listRefs). - Produit un rapport :
- Confusion matrix (vrai niveau vs nouveau niveau)
- Dossiers dont la décision changerait : top 20 avec raisons
- Impact business : % auto-approve LOW, SLA agents, charge EDD
- Impact conformité : % PROHIBITED nouveaux (détection améliorée ?), % dossiers qui passeraient LOW alors qu’avant HIGH (risque régression)
6.4 Revue périodique obligatoire
Section intitulée « 6.4 Revue périodique obligatoire »- Annuelle par défaut (ligne BCT / FATF Reco. 1)
- Triggerable à tout moment par un compliance officer
- Rappel automatique 60 / 30 / 7 jours avant échéance
- Après 1 an sans revue : alerte supervisor + incident compliance
6.5 Audit WORM
Section intitulée « 6.5 Audit WORM »Table risk_policy_audit append-only :
| Event | Champs obligatoires |
|---|---|
policy.created | actor, tenant, content_hash, timestamp |
policy.edited | actor, policy_id, diff, content_hash, timestamp |
policy.shadowed | actor, policy_id, timestamp |
policy.backtested | actor, policy_id, backtest_report_hash, timestamp |
policy.approved | actor, policy_id, signature, timestamp |
policy.published | actor, policy_id, effectiveFrom, timestamp |
policy.archived | actor, policy_id, reason, timestamp |
Chaîne de hash SHA-256 (chaque entry inclut le hash de l’entry précédente) → intégrité vérifiable par l’auditeur.
7. Performance & scalabilité
Section intitulée « 7. Performance & scalabilité »| Métrique | Target | Comment atteint |
|---|---|---|
| Évaluation synchrone p50 | ≤ 15 ms | DSL compilé JIT, listes FATF/OFAC en Caffeine cache (L1), pas de DB hit |
| Évaluation synchrone p99 | ≤ 50 ms | idem, plus GC tuning G1GC (pause < 10ms) |
| Throughput par replica | 2000 req/s | Calcul pur CPU, scale horizontal linéaire |
| Refresh liste OFAC (quotidien) | < 30 s (downtime 0) | Read-only pattern + atomic swap de cache |
| Backtesting 6 mois / 5000 dossiers | ≤ 15 min | Temporal workflow parallèle, batch de 100 |
| Storage evaluations (1 tenant 1M/an) | ~ 400 GB / an JSONB compressé | Partitionnement mensuel + compression lz4 |
8. Sécurité
Section intitulée « 8. Sécurité »- Row-Level Security PostgreSQL : tenant_id propagé via
SET LOCAL app.tenant_id = '...'dans chaque transaction (cf. ADR-002) - Chiffrement at rest : AES-256 sur WORM evaluations + policies
- Chiffrement in transit : TLS 1.3 + mTLS interne Vault-managed
- PII handling : le profil client utilisé pour évaluation contient PII → jamais de log raw, seulement
subjectIdpseudonymisé dans les logs - Secret management : clés de signature dual control dans Vault, rotation automatique 90j
- RBAC :
risk.policy.read: tous les officers compliance du tenantrisk.policy.draft: senior compliance officersrisk.policy.approve: directeur compliance + DSI (un des deux suffit pour signer ; les deux doivent signer pour publier)risk.evaluation.override: senior compliance uniquement, laisse trace audit
- Anti-tampering : policy content hashé à la création, vérifié à chaque chargement runtime. Mismatch → refus de servir + incident
9. Intégration avec autres modules
Section intitulée « 9. Intégration avec autres modules »| Module | Intégration | Sens |
|---|---|---|
| kyc-svc | POST /v1/risk/evaluate à la soumission onboarding | Synchrone, bloquant (hot path) |
| aml-svc screening | POST /v1/risk/evaluate après screening, POST /v1/risk/policies/.../publish consomme updated lists | Synchrone + event-driven |
| aml-svc txmon | gRPC EvaluateTransactional sur chaque transaction eligible | Synchrone temps réel |
| case-mgmt-svc | Consomme Kafka risk.evaluated → alimente queue agent par priorité | Asynchrone |
| form-designer | Publie client_profile_schema consommé par risk engine (nouveau champ form → nouveau variable DSL) | Config time |
| reporting-svc | KPI dashboards : % LOW auto-approve, temps moyen EDD, taux PROHIBITED | Asynchrone |
10. Templates — matrices de référence
Section intitulée « 10. Templates — matrices de référence »VitaKYC livre 4 templates prêts-à-l’emploi comme point de départ :
| Template | Pour qui | Pondérations |
|---|---|---|
TEMPLATE_BANK_TN_BCT | Banques tunisiennes (conforme BCT Circ. 2017-08 annexe D) | client 0.30 · geo 0.25 · product 0.20 · channel 0.15 · aml 0.10 |
TEMPLATE_BANK_EU_6AMLD | Banques européennes (conforme 6AMLD + BaFin) | client 0.25 · geo 0.30 · product 0.15 · channel 0.15 · aml 0.15 |
TEMPLATE_FINTECH_WALLET | Fintechs/wallets crypto-friendly | client 0.20 · geo 0.25 · product 0.25 · channel 0.10 · aml 0.20 |
TEMPLATE_INSURANCE_CREDIT | Assurance + crédit (risque produit dominant) | client 0.20 · geo 0.15 · product 0.40 · channel 0.10 · aml 0.15 |
Chaque template est fork-able par tenant, avec notes de politique + model cards.
11. Model cards (obligatoire pour audit)
Section intitulée « 11. Model cards (obligatoire pour audit) »Chaque dimension a une model card maintenue en YAML dans la policy :
dimension: clientowner: compliance-officer@banquex.tnpurpose: Évaluer le risque intrinsèque lié à la personne (PEP, profession, statut)variables: - client.pep (source: screening WorldCheck + OpenSanctions, refresh daily) - client.profession (source: form KYC, référentiel BCT codes professions) - client.residentStatus (source: form KYC) - client.maritalStatus (source: form KYC, optionnel)regulatory_basis: - "BCT Circulaire 2017-08 art. 8.2 (risque personne)" - "FATF Reco. 12 (PEP)" - "Wolfsberg Group Principles (2015)"last_review: 2026-04-15last_reviewer: directeur-compliance@banquex.tnnext_review: 2027-04-15known_limitations: - "Adverse media uniquement en FR/AR/EN — gap sur presse locale non-indexée" - "PEP proche associé : définition subjective, tolérance 10% faux-positifs acceptée"12. Checklist go-live par tenant
Section intitulée « 12. Checklist go-live par tenant »Avant la 1ère évaluation production :
- Template de base choisi (ex:
TEMPLATE_BANK_TN_BCT) - Model cards renseignées pour les 5 dimensions
- 20+ profils test joués dans le POC, résultats validés par compliance officer
- Backtesting sur échantillon 6 mois (si historique dispo)
- Dual control wired (2 comptes avec clés Vault)
- Shadow mode activé minimum 2 semaines
- Alertes SLA configurées (drift % auto-approve, p99 évaluation)
- Export audit WORM testé (format BCT + DGI)
- Training compliance officers (1 demi-journée par tenant)
- Playbook calibration publié et signé (voir playbook)
13. Références
Section intitulée « 13. Références »- ADR-025 — Modèle de risque client
- ADR-004 — Moteur de règles DSL + OPA
- ADR-002 — Multi-tenant RLS
- POC Kotlin — risk engine
- Playbook — Calibrer la matrice BCT
- Maquettes UI workflow 10 — Admin Risk Matrix
- FATF Recommendations (révision 2023) : Reco. 1, 10, 12, 15, 19
- Wolfsberg Group — Risk-Based Approach Guidance (2015)
- BCT Circulaire 2017-08 + annexe D
- Loi tunisienne 2015-26 + 2019-9 (LCB-FT)
- RGPD art. 22 (décisions automatisées)
- BCBS — “Sound management of risks related to AML/CFT” (2020)