POC · Normaliseur de transactions (Kotlin)
Code source :
poc-transaction-normalizer/dans le monorepo VitaKYC.
Quatrième POC technique : le normaliseur de transactions. Ingestion des deux formats dominants du monde bancaire (SWIFT MT103 legacy, ISO 20022 pacs.008 moderne) et conversion vers le modèle pivot canonique JSON de VitaKYC — la fondation du module AML Transaction Monitoring.
1. Pourquoi un normaliseur pivot ?
Section intitulée « 1. Pourquoi un normaliseur pivot ? »Les banques MENA et européennes reçoivent des transactions depuis plusieurs systèmes hétérogènes simultanément :
- SWIFT MT103 (legacy) — encore majoritaire sur les flux internationaux jusqu’à fin 2025
- ISO 20022 pacs.008 / pacs.009 / pain.001 (moderne) — SEPA obligatoire, SWIFT MX en migration, norme TARGET2
- CSV batch nocturne du core banking (Temenos, Finastra, Oracle FlexCube)
- CDC Debezium sur réplique DB en temps réel
- Kafka streaming (fintechs modernes)
Décision d’architecture (cf. AML Transaction Monitoring §3.2) : tout est normalisé vers un unique modèle JSON pivot avant d’entrer dans le moteur de règles AML. Cela isole la logique métier des variations de formats et rend le code AML testable sur un seul type d’entrée.
2. Architecture
Section intitulée « 2. Architecture »Ce POC implémente P1 (MT103) et P2 (ISO 20022 pacs.008) ; P3 et P4 sont documentés mais livrés en V1.
3. Modèle canonique VitaKYC
Section intitulée « 3. Modèle canonique VitaKYC »Extrait de CanonicalTransaction :
data class CanonicalTransaction( val txId: String, val timestamp: String, // ISO 8601 UTC val account: Account, // { id, iban, bic, currency, customerId } val direction: Direction, // DEBIT | CREDIT val type: TxType, // TRANSFER | CASH_DEPOSIT | ... val channel: Channel, // SWIFT | SEPA | RTGS | ... val amount: Money, val amountEurEquivalent: Double? = null, val counterparty: Counterparty? = null, val purpose: Purpose? = null, // ISO 20022 purpose code + remittance val endToEndRef: String? = null, val sourceSystem: String, // "SWIFT_MT" | "ISO20022" | ... val sourceFormat: String, // "swift_mt103" | "iso20022_pacs008" | ... val rawRef: String? = null, // pointeur vers la copie archivée)Un CanonicalBatch contient les transactions acceptées + les transactions rejetées (RejectedTransaction avec raison précise).
4. Parseurs implémentés
Section intitulée « 4. Parseurs implémentés »4.1 SWIFT MT103
Section intitulée « 4.1 SWIFT MT103 »Extrait le bloc 4 du message et route selon les tags :
:20:— référence (→txId):23B:— code opération bancaire:32A:— date valeur + devise + montant (ex260422USD847320,00):50K:— ordering customer:52A:— BIC banque émettrice:59:— beneficiary (ligne 1 =/account, lignes suivantes = nom + adresse):57A:— BIC banque bénéficiaire:70:— purpose / remittance:71A:— details of charges
Robustesse : tolère absence des blocs 1-3 (enveloppe), supporte format européen du montant (virgule décimale), distingue account de nom dans :59:.
4.2 ISO 20022 pacs.008
Section intitulée « 4.2 ISO 20022 pacs.008 »Parsing StAX streaming, indépendant de la version (.08 à .13). Chemin XML routé par une stack d’ancêtres :
GrpHdr/MsgId→ référence groupeCdtTrfTxInf/PmtId/TxId→txIdCdtTrfTxInf/PmtId/EndToEndId→endToEndRefCdtTrfTxInf/IntrBkSttlmAmt[@Ccy]→amount.value + amount.currencyCdtTrfTxInf/IntrBkSttlmDt→timestampDbtr/Nm,Dbtr/CtryOfRes→ débiteurDbtrAcct/Id/IBAN→ IBAN débiteurDbtrAgt/FinInstnId/BICFI→ BIC banque débiteur- Idem
Cdtr*pour le créditeur Purp/Cd+RmtInf/Ustrd→ purpose
Inférence direction : si notre IBAN = débiteur → DEBIT, sinon CREDIT.
Dérivation pays counterparty : si CtryOfRes absent, extrait des positions 5-6 du BIC (standard ISO 9362).
5. Tests (9 cas passants)
Section intitulée « 5. Tests (9 cas passants) »Sortie ./gradlew test :
NormalizerTest > MT103 · parse une transaction complète PASSEDNormalizerTest > MT103 · rejet propre quand :20: manquant PASSEDNormalizerTest > MT103 · rejet propre quand :32A: manquant PASSEDNormalizerTest > MT103 · nombre avec virgule européenne parsé correctement PASSEDNormalizerTest > pacs.008 · parse une transaction complète PASSEDNormalizerTest > pacs.008 · direction CREDIT quand notre IBAN = Cdtr PASSEDNormalizerTest > pacs.008 · dérive le pays counterparty depuis le BIC si CtryOfRes absent PASSEDNormalizerTest > toJson · produit un JSON canonique valide indenté PASSEDNormalizerTest > Batch · champs ingestedAt + batchId générés PASSEDBUILD SUCCESSFUL6. Fixtures utilisées
Section intitulée « 6. Fixtures utilisées »6.1 sample-mt103.txt
Section intitulée « 6.1 sample-mt103.txt »{1:F01BNKATNTTXXXX0000000000}{2:I103BNKAFRPPXXXXN}{3:{108:REF-2026-042200001}}{4::20:TX-2026-00126:23B:CRED:32A:260422USD847320,00:50K:/TN59 1000 1234 5678 9012 3456ACME TRADING SARL12 AV HABIB BOURGUIBA1000 TUNIS TN:52A:BNKATNTT:59:/FR7630001007941234567890185HORIZON BUSINESS LLCDIFC GATE BUILDING 3DUBAI AE:57A:FRPPBICX:70:PASS-THROUGH SUPP INV-2026-00126:71A:SHA-}{5:}6.2 sample-pacs008.xml
Section intitulée « 6.2 sample-pacs008.xml »Extrait (74 500 € EUR, Dbtr Tunisie BNKATNTT, Cdtr UK NWBKGB2L, purpose code SUPP, message “Supplier Payment Invoice INV-2026-00123”).
7. JSON canonique produit
Section intitulée « 7. JSON canonique produit »{ "batchId": "6a7c3e2b-1111-4222-8333-abcdef012345", "ingestedAt": "2026-04-22T10:00:00Z", "sourceFormat": "swift_mt103", "transactions": [ { "txId": "TX-2026-00126", "timestamp": "2026-04-22T00:00:00Z", "account": { "id": "ACCT-TN59-1234567", "iban": "TN59 1000 1234 5678 9012 3456", "currency": "USD", "customerId": "CUST-42" }, "direction": "DEBIT", "type": "TRANSFER", "channel": "SWIFT", "amount": { "value": 847320.0, "currency": "USD" }, "counterparty": { "name": "HORIZON BUSINESS LLC", "accountNumber": "FR7630001007941234567890185", "bic": "FRPPBICX" }, "purpose": { "description": "PASS-THROUGH SUPP INV-2026-00126" }, "endToEndRef": "TX-2026-00126", "sourceSystem": "SWIFT_MT", "sourceFormat": "swift_mt103" } ]}8. Comment se branche aml-svc
Section intitulée « 8. Comment se branche aml-svc »Après normalisation, le CanonicalBatch est publié sur Kafka (topic tx.normalized) :
SFTP drop Kafka topic MT103 files ────────┐ tx.normalized ├──► ingest-batch-svc ──► Normalizer ──────────┐REST POST JSON ──────┤ │ ▼ ▼ISO 20022 pacs.008 aml-svc (rules + ML)via Kafka inbound → alerts + scoringLatence cible : < 5 s streaming temps réel (cf. AML TxMon performance §7).
9. Ce que ce POC valide
Section intitulée « 9. Ce que ce POC valide »- Un seul pipeline de traitement AML peut recevoir des formats hétérogènes sans altération du code métier.
- La production peut adopter progressivement ISO 20022 pendant que legacy MT continue à fonctionner (pattern strangler fig).
- Banques tunisiennes : MT103 reste le flux entrant majoritaire pour les virements internationaux hors SEPA — ce POC couvre leur cas d’usage immédiat.
- Banques européennes / SEPA : pacs.008 est déjà obligatoire pour SEPA ; ce POC prouve qu’on est prêt.
10. Ce qu’il reste à faire
Section intitulée « 10. Ce qu’il reste à faire »- Parseur CSV VitaKYC (format trivial, tolérant).
- Connecteur Kafka CDC Debezium pour consommer les logs core banking.
- Connecteur Temenos T24 via MQ + TOCF (cf. AML TxMon §3.4).
- Validation XSD stricte pour ISO 20022 (schéma publié sur iso20022.org).
- Champs additionnels rares :
InstrForDbtrAgt,ClsRmbrsmntAgt, chargeable to multiple intermediaries. - Benchmark performance : cible 1 000 tx/s/tenant au MVP.
- Intégration au service
ingest-svcen production. - Conversion FX automatique vers EUR equivalent via taux BCE quotidien.
- Validation BIC (longueur 8/11, algorithme checksum).
- Dead letter queue pour les messages rejetés avec replay manuel via UI.
11. Impact commercial
Section intitulée « 11. Impact commercial »- Argumentaire AO : « nous supportons dès le MVP MT103 legacy + ISO 20022 moderne — zéro migration forcée côté banque ».
- Différenciation vs concurrents SaaS KYC modernes qui ne savent pas traiter MT103.
- Base du module AML TxMon premium — un tenant qui ingère 1 M tx/mois = +2 500 €/mois de revenu récurrent.
POC validé le 2026-04-22 · 9 tests passants · 0 dépendance lourde (Kotlin + StAX JDK + OkHttp léger).