Module PRD+FSD - Ingestion (ING)¶
Papillon Collection Solution - V1 (MVP)¶
Préfixe module : ING | Pays : CI (Côte d'Ivoire) | Date : 2026-04-30¶
Source legal audit :
docs/legal/audit/memo-conformite-CI-v2-audit.md(audité le 30/04/2026 - disposition LEGAL REVIEW REQUIRED). Mémo source :docs/legal/memo-conformite-CI-v2.md(Section F1 pour ING).Legal status discipline (memo v2) - toute obligation
INCERTAIN - avocat requisbloque l'implémentation des stories qui en dépendent et impose une note[LEGAL-REVIEW]dans la spec/story. Toute obligationPRÉLIMINAIREdéclenche un avertissement +[LEGAL-REVIEW]. La conformité ne se déduit jamais d'un statutINCERTAIN.⚠️ BLOQUANT V1 go-live pour ING - Autorisation préalable ARTCI pour le traitement de numéros de téléphone (Loi CI n°2013-450, art. 7 + Décision ARTCI n°2023-0877 du 25/05/2023, p. 3) =
VÉRIFIÉmais BLOQUANT. Toute story ING peut être conçue, codée et testée en pré-production, mais l'ingestion en production est désactivée tant que l'autorisation n'est pas accordée. Exigence d'architecture : feature flagfeature.artci.phone-auth.granted(défautfalse) bloquant tous les endpoints d'ingestion en prod ; déclaration ARTCI préalable du responsable de traitement (art. 6) à confirmer côté courtier/assureur. Minimisation, finalité, chiffrement transit + repos, journalisation =VÉRIFIÉ.
V0 Envelope Scope (ajout 2026-05-21)¶
Lire
docs/prd/prd-v0.md§ 4 (ligne ING) et § 8 avant toute story V0. Ce PRD reste le contrat V1. Le V0 coupe FROM cette spec ; chaque sous-version V0 est additive et empile vers la cible V1.ING dans le V0 (per
prd-v0.md§ 4 + § 8) :
Sous-version Périmètre vs V1 PRD Story IDs [V0.0.0]Pickup S3 via cron @02:00 Africa/Abidjanpar tenant. Validation simplifiée : colonnes core uniquement (contrat / client / montant / échéance). Pas d'upload UI. Format de fichier figé Day 1 (colonnes futures présentes mais peuvent être vides).ING-001 [V0.0.1]Gestion d'erreur S3 : cron idempotent, échec pickup -> audit + retry manuel. Pas de feedback opérateur. ING-002 [V0.0.2]Réintroduction incrémentale des règles de validation V1 (champs optionnels). ING-003 [V0.0.3]Upload UI disponible (admin cabinet téléverse via web). L'atelier de correction complet V1 est layered ici. ING-004 [V1]Spec V1 complète telle qu'écrite ci-dessous (retry/erreur avancé, atelier de correction complet, set de validation complet). - Hors V0 entièrement : import en masse d'agences via CSV ingestion. Les agences sont créées via l'UI TENANT à partir de v0.0.2 (cf.
prd-v0.md§ 4 ligne TENANT), pas via un fichier d'ingestion.BLOQUANT production V0 (identique au V1) : autorisation ARTCI numéros de téléphone (mémo B5) reste gating production via
feature.artci.phone-auth.granted=false. L'ingestion pré-production est autorisée dès v0.0.0 ; l'ingestion production reste désactivée tant que l'autorisation ARTCI n'est pas accordée. Le founder a[FOUNDER-RISK-ACCEPTED]le dépôt du dossier ARTCI en parallèle, mais le gate production reste actif.
Sommaire¶
- Partie A - Exigences Produit
- A.1 Résumé exécutif
- A.2 Personas utilisateurs
- A.3 User Stories
- A.4 Exigences non-fonctionnelles
- A.5 Périmètre MVP - V1 vs V2/V3
- A.6 Questions ouvertes
- A.7 Provisoirement validé
- Partie B - Spécifications Fonctionnelles
- B.1 Règles fonctionnelles
- B.2 Machines à états
- B.3 Table de décision - classification des anomalies
- B.4 Spécification du modèle de colonnes canonique
- B.5 Modèle de données
- B.6 Implications multi-tenant
- B.7 Dépendances inter-modules
- B.8 Événements d'audit
- Partie C - Contraintes et frontières
- C.1 Conformité réglementaire
- C.2 Localisation
- C.3 Sécurité
- C.4 Connectivité
- C.5 Hors périmètre V1
Partie A - Exigences Produit¶
A.1 Résumé exécutif¶
Le module Ingestion (ING) est la porte d'entrée des données de la Papillon Collection Solution. Il permet aux opérateurs d'un cabinet de courtage de téléverser les fichiers clients et contrats standardisés qui alimentent l'ensemble des campagnes de renouvellement en aval.
Problème résolu : Les petits cabinets de courtage gèrent leur portefeuille dans Excel ou dans leur propre système de gestion, sans pont automatisé vers la plateforme de renouvellement. ING accepte leur export tel quel (CSV ou XLSX), le valide, remonte les anomalies, et permet à l'opérateur de corriger les blocages directement dans le navigateur - sans retourner dans Excel - avant de valider la base de données canonique de la plateforme.
L'atelier de correction pré-campagne intégré à ING constitue la première ligne de défense contre les problèmes de qualité de données : chaque contrat qui atteint le moteur de campagne du module OP est soit propre, soit délibérément accepté avec ses anomalies non-bloquantes documentées.
Périmètre ING [V1] : import de fichier → validation → staging → atelier de correction → commit dans les tables customer et contract. Le module OP consomme ensuite ces tables pour la sélection des cohortes de renouvellement.
Hors périmètre ING : sélection de campagne, envoi de notifications, portail client, paiement.
A.2 Personas utilisateurs¶
| Persona | Rôle dans ING | Référence V1 PRD |
|---|---|---|
Admin cabinet [OPERATOR] |
Importe des fichiers pour toutes les agences du tenant ; configure les codes agence ; télécharge le modèle | §7, §12 |
Opérateur agence [OPERATOR] |
Importe des fichiers limités aux agences qui lui sont attribuées ; corrige les anomalies dans l'atelier | §7, §12 |
Superviseur [OPERATOR] |
Lecture seule : historique des imports, compteurs d'anomalies ; aucune permission d'upload | §7 |
Client assuré [CUSTOMER] |
Aucun - l'ingestion est un flux purement opérateur | - |
Système [BACKEND] |
Job de sweep quotidien (expiration staging) ; publication de l'événement ImportCommitted |
- |
A.3 User Stories¶
ING-US-01 - Téléverser un fichier d'import [V1] [OPERATOR]¶
En tant qu' opérateur, je veux téléverser un fichier CSV ou XLSX de données clients/contrats, afin que la plateforme le valide et que je puisse démarrer l'atelier de correction.
Critères d'acceptation :
- Étant donné un opérateur autorisé qui téléverse un fichier CSV valide (≤5 Mo, ≤5 000 lignes de données, UTF-8, séparateur
,ou;, ligne d'en-tête obligatoire correspondant au modèle canonique) Quand le serveur reçoit le fichier Alors (a) le hash SHA-256 du fichier est vérifié ; si identique à un import précédent du même tenant, le téléversement est rejeté avec le message « Ce fichier a déjà été importé le JJ/MM/AAAA à HH:MM par \<opérateur> » ; (b) sur hash nouveau, le serveur parse le fichier, valide toutes les lignes, et renvoie en ≤10 s soit un résultatSTAGED_CLEANsoit un résultatSTAGED_WITH_ANOMALIES. - Étant donné un fichier parsé avec succès Quand TOUTES les lignes passent les contrôles d'anomalies bloquantes Alors le système crée un import en staging avec statut
STAGED_CLEANet redirige l'opérateur vers l'écran de confirmation de commit. - Étant donné un fichier parsé Quand ≥1 ligne présente une anomalie bloquante Alors le système crée un import en staging avec statut
STAGED_WITH_ANOMALIES; l'opérateur est dirigé vers l'atelier de correction ; aucune ligne n'est committée dans les tables de production. - Étant donné un fichier dont le format est complètement invalide (extension incorrecte, XLSX corrompu, encodage illisible) Quand le serveur tente de le parser Alors l'import est rejeté avant staging avec une erreur française descriptive ; aucune entrée staging n'est créée ; un événement d'audit
ING_PARSE_FAILEDest écrit.
ING-US-02 - Corriger les anomalies bloquantes dans l'atelier [V1] [OPERATOR]¶
En tant qu' opérateur, je veux corriger les anomalies bloquantes ligne par ligne dans le navigateur, afin de valider l'import sans retourner dans Excel.
Critères d'acceptation :
- Étant donné un import en staging avec anomalies bloquantes Quand l'opérateur ouvre l'atelier de correction Alors il voit une liste paginée des lignes anomales (jusqu'à 200 par page) avec : le type d'anomalie, la colonne concernée, et un champ de correction en ligne pour chaque champ corrigeable (
telephone,date_naissance,montant_du,code_agence). - Étant donné un opérateur qui modifie un champ Quand il soumet la correction Alors le serveur re-valide le champ isolément ; en cas de succès, l'anomalie bloquante de la ligne est résolue et le champ affiche sa valeur normalisée (E.164 pour le téléphone, DD/MM/AAAA pour la date, entier pour le montant) ; en cas d'échec, une erreur française est affichée en ligne et la ligne reste BLOCKED.
- Étant donné un opérateur ayant corrigé TOUTES les anomalies bloquantes de l'import staging Quand il clique sur « Valider l'import » Alors toutes les lignes stagées sont committées en une seule transaction (upsert customer + upsert contract) et un événement
ImportCommittedest publié. - Étant donné un opérateur qui ne peut ou ne veut pas terminer la correction Quand il clique sur « Annuler cet import » Alors l'entrée staging et ses lignes sont purgées ; un événement d'audit
ING_ABANDONEDest écrit.
ING-US-03 - Valider et commettre un import propre [V1] [OPERATOR]¶
En tant qu' opérateur, je veux commettre un import validé en une action, afin que les contrats soient immédiatement disponibles pour la sélection de campagne dans le module OP.
Critères d'acceptation :
- Étant donné un import staging avec statut
STAGED_CLEANQuand l'opérateur clique sur « Confirmer l'import » Alors le système committe toutes les lignes stagées ; publie l'événementImportCommitted(import_id, tenant_id, compteurs lignes créées / mises à jour / ignorées) ; redirige l'opérateur vers l'historique des imports. - Étant donné un échec de la transaction de commit (erreur base de données) Quand l'opérateur réessaie Alors l'entrée staging est toujours présente et le commit est retentable sans re-téléversement du fichier.
- Étant donné un import
STAGED_CLEANouSTAGED_WITH_ANOMALIESnon commité après 7 jours Quand le job de sweep quotidien s'exécute Alors l'entrée staging est purgée ; un événement d'auditING_STAGING_EXPIREDest écrit ; la console OP affiche un badge d'alerte (« Import en attente expiré - [nom du fichier] du [date] ») jusqu'à ce que l'opérateur le ferme.
ING-US-04 - Consulter l'historique des imports [V1] [OPERATOR]¶
En tant qu' admin ou superviseur, je veux consulter l'historique des imports de mon tenant, afin d' auditer ce qui a été téléversé, par qui, et ce qui a été commité.
Critères d'acceptation :
- Étant donné un admin Quand il ouvre l'historique des imports Alors il voit tous les imports du tenant (toutes agences), triés par date décroissante, avec : nom du fichier, pilule de statut (
STAGED_CLEAN/STAGED_WITH_ANOMALIES/COMMITTED/ABANDONED/EXPIRED/PARSE_FAILED), nom de l'opérateur, horodatage de l'upload, compteurs de lignes. - Étant donné un opérateur agence Quand il ouvre l'historique Alors il ne voit que les imports scopés à ses agences attribuées.
- Étant donné tout utilisateur autorisé Quand il clique sur un import Alors il peut consulter le rapport d'anomalies et (pour les imports
COMMITTED) télécharger le fichier source d'origine depuis MinIO pendant 12 mois à compter de la date d'upload.
ING-US-05 - Télécharger le modèle de fichier [V1] [OPERATOR]¶
En tant que nouvel opérateur, je veux télécharger le modèle d'import canonique, afin de connaître exactement les colonnes que mon export doit contenir.
Critères d'acceptation :
- Étant donné tout opérateur authentifié Quand il clique sur « Télécharger le modèle » dans l'interface d'import Alors un fichier XLSX pré-rempli est téléchargé avec les colonnes canoniques en français, une ligne de données exemple, et des commentaires in-cell expliquant chaque colonne. Nom du fichier :
modele_import_papillon.xlsx.
A.4 Exigences non-fonctionnelles¶
| NFR | Cible | Note |
|---|---|---|
| Latence parse + validation | ≤10 s pour 5 000 lignes | Synchrone ; pas de job queue nécessaire |
| Taille max fichier téléversé | ≤5 Mo | Appliqué par la couche HTTP avant parsing |
| Lignes max par fichier | ≤5 000 lignes de données | Rejeté avec TROP_DE_LIGNES au-delà |
| Durabilité staging | 7 jours, survit au redémarrage serveur | Tables PostgreSQL - pas Redis |
| Rétention fichiers source | 12 mois dans MinIO puis purge automatique | Provisoirement validé |
| Isolation tenant | 100 % - aucune fuite inter-tenant | tenant_id sur chaque requête |
| Écriture audit log | Synchrone dans la transaction d'import | Ne doit pas être perdu en cas de rollback |
| Charge opérateur (côté opérateur) | Desktop-first, online-first | Aucune contrainte low-bandwidth spécifique à ING |
| Rate-limit upload | Max 10 tentatives par opérateur par heure | Compteur Redis par user_id |
A.5 Périmètre MVP - V1 vs V2/V3¶
| Fonctionnalité | Statut |
|---|---|
Import CSV (UTF-8, ;/,) + XLSX |
✅ V1 |
Modèle de colonnes rigide (en-têtes français, nom et prenoms séparés) |
✅ V1 |
| Déduplication par hash de fichier | ✅ V1 |
| Commit tout-ou-rien | ✅ V1 |
| Table de staging + atelier de correction | ✅ V1 |
Correction en ligne : telephone, date_naissance, montant_du, code_agence |
✅ V1 |
| Classification des anomalies (bloquant / non-bloquant / surveillance) | ✅ V1 |
| Normalisation téléphone E.164 (+225, CI) | ✅ V1 |
| XOF codé en dur - pas de colonne devise | ✅ V1 |
| Rétention 12 mois fichiers source (MinIO) | ✅ V1 |
| Expiration auto staging (7 j) + badge quotidien | ✅ V1 |
| Admin = toutes agences ; Opérateur = agences attribuées | ✅ V1 |
Événement domaine ImportCommitted → OP |
✅ V1 |
| Téléchargement du modèle XLSX | ✅ V1 |
| Imports déclenchés via API / connecteurs ERP / schedules | ❌ V2 |
| Mapping de colonnes par tenant | ❌ V2 |
| Format XLS (legacy Excel 97-2003) | ❌ V2 |
| Upload batch multi-fichiers | ❌ V2 |
| Colonne devise / multi-devises | ❌ V2 |
Validation référentiel branche |
❌ V2 |
| TTL staging configurable par tenant | ❌ V2 |
| Notification email/SMS à l'opérateur sur expiration staging | ❌ V2 |
| Suggestions de correction assistées par IA | ❌ V3 |
A.6 Questions ouvertes¶
date_echeancedans le passé >30 jours = BLOQUANT ou NON-BLOQUANT ? Certains cabinets pourraient vouloir importer des fichiers historiques à des fins de réconciliation. Recommandation : verrouiller à BLOQUANT pour le pilote V1 ; prévoir un flag de configuration tenant (Autoriser les échéances passées) en V2.- Validation référentiel
branche: si le moteur de campagne OP filtre par branche, des valeurs texte libres rendront le filtrage non fiable. Recommandation : V1 accepte toute chaîne non vide ; V2 introduit une table de référence par tenant. - Canal de la notification quotidienne "import en attente" : badge console OP uniquement (V1) ou aussi email/SMS à l'opérateur (V2) ?
- Lignes non-corrigeables dans le workshop (
identifiant_clientabsent, doublon intra-fichier,date_echeancedépassée) : faut-il afficher un bouton « Supprimer cette ligne » pour permettre à l'opérateur de retirer les lignes irrécupérables sans re-téléverser le fichier ? → À arbitrer avant développement de l'atelier.
A.7 Provisoirement validé¶
| Élément | Hypothèse de travail | Résolution requise |
|---|---|---|
| Rétention fichiers source : 12 mois | Fenêtre d'audit CIMA + principe de minimisation ARTCI convergent vers 12 mois | Avocat : confirmer sous loi n°2013-450 art. X + CIMA Rég. 01-24 |
| Base légale de l'ingestion | Sous-traitance des données du courtier en vertu du DPA plateforme-courtier | Confirmer avec avocat ; obligations de déclaration ARTCI pour l'éditeur de la plateforme (lacune audit juridique #3) |
| PII au repos - colonnes plain text + chiffrement disque | Acceptable pour le pilote V1 ; clients courtiers/assureurs doivent signer DPA reconnaissant le modèle shared-schema | Réévaluer avant >10 tenants ou si un tenant BYO-DB demande un chiffrement au niveau champ |
| Base légale CIMA Rég. 01-24 - positionnement prestataire technique | Asserté dans memo juridique ; ancré sur source secondaire (blog) | Validation avocat obligatoire avant gel architecture (lacune audit #1) |
Partie B - Spécifications Fonctionnelles¶
B.1 Règles fonctionnelles¶
ING-R-001 - Format d'import accepté [V1] [BACKEND]¶
SI le fichier téléversé a l'extension .csv ALORS parser en CSV (UTF-8 ; séparateur auto-détecté , ou ; ; première ligne = en-tête obligatoire).
SI le fichier a l'extension .xlsx ALORS parser la première feuille uniquement (XLSX) ; les cellules Excel de type Date sont auto-converties en chaîne DD/MM/AAAA ; première ligne = en-tête obligatoire.
SI le fichier a toute autre extension (.xls, .xlsm, .ods, .txt, etc.) ALORS rejeter immédiatement avec le code FORMAT_NON_SUPPORTE ; aucune entrée staging créée.
Validation complémentaire : vérifier les magic bytes du fichier indépendamment de l'extension pour détecter les uploads malveillants (ex. fichier .csv avec magic bytes OOXML).
ING-R-002 - Limites de taille et de volume [V1] [BACKEND]¶
SI le fichier téléversé dépasse 5 Mo (mesuré après upload, avant parsing) ALORS rejeter avec FICHIER_TROP_GRAND avant tout parsing ; aucune entrée staging.
SI le fichier parsé contient plus de 5 000 lignes de données (hors en-tête) ALORS rejeter avec TROP_DE_LIGNES ; aucune entrée staging.
ING-R-003 - Déduplication par hash de fichier [V1] [BACKEND]¶
Avant le parsing, calculer le SHA-256 du fichier brut téléversé.
SI le SHA-256 existe déjà dans ingestion_import pour le même tenant_id avec un statut ≠ PARSE_FAILED ALORS rejeter avec FICHIER_DEJA_IMPORTE en incluant dans la réponse : date de l'import précédent, heure, nom de l'opérateur. Aucune entrée staging créée.
Note : la déduplication par hash est par tenant_id - deux tenants différents peuvent importer le même fichier sans conflit.
ING-R-004 - Modèle de colonnes rigide [V1] [BACKEND]¶
SI la ligne d'en-tête du fichier ne contient pas toutes les colonnes obligatoires (correspondance exacte, insensible à la casse, caractères accentués en NFC canonique) ALORS rejeter avec EN_TETES_MANQUANTS en listant les colonnes manquantes. Aucune entrée staging.
Colonnes obligatoires : identifiant_client, nom, prenoms, telephone, date_naissance, identifiant_contrat, branche, date_echeance, montant_du, code_agence.
Colonnes optionnelles (ignorées si absentes, parsées si présentes) : email, numero_police, immatriculation, statut_source, informations_complementaires.
Toute colonne extra non présente dans la liste canonique : ignorée silencieusement ; journalisée en niveau INFO.
ING-R-005 - Normalisation du numéro de téléphone [V1] [BACKEND]¶
Pour chaque champ telephone d'une ligne :
1. Supprimer tous les espaces, tirets, points, parenthèses.
2. Si la chaîne commence par 00225 → remplacer par +225.
3. Si la chaîne est une séquence de 10 chiffres commençant par 0 (ex. 0712345678) → préfixer +225.
4. Si la chaîne commence par 225 sans + → préfixer +.
5. Le résultat doit correspondre au masque /^\+225[0-9]{10}$/ (format E.164 CI, 13 caractères).
SI le résultat ne correspond pas ALORS anomalie TELEPHONE_INVALIDE → BLOQUANT.
SI le champ est vide ALORS anomalie TELEPHONE_ABSENT → BLOQUANT.
Valeur stockée : toujours E.164 (+225XXXXXXXXXX).
ING-R-006 - Validation de la date de naissance [V1] [BACKEND]¶
Champ date_naissance :
SI c'est une cellule Excel de type Date → convertir en chaîne DD/MM/AAAA.
SI c'est une chaîne → valider le format DD/MM/AAAA (strict : 2 chiffres jour, 2 chiffres mois, 4 chiffres année).
SI la date résultante est dans le futur OU antérieure au 01/01/1900 ALORS anomalie DATE_NAISSANCE_INVALIDE → BLOQUANT.
SI le champ est vide ALORS anomalie DATE_NAISSANCE_ABSENTE → BLOQUANT.
Tout autre format de chaîne (AAAA-MM-JJ, MM/JJ/AAAA, etc.) → anomalie DATE_NAISSANCE_INVALIDE → BLOQUANT.
ING-R-007 - Validation de la date d'échéance [V1] [BACKEND]¶
Même validation de format que ING-R-006.
SI le champ est vide ALORS anomalie DATE_ECHEANCE_ABSENTE → BLOQUANT.
SI la date parsée est antérieure de plus de 30 jours à la date d'import ALORS anomalie ECHEANCE_DEPASSEE → BLOQUANT. (Provisoirement validé - pourra devenir un flag de configuration tenant en V2.)
ING-R-008 - Validation et normalisation du montant [V1] [BACKEND]¶
Champ montant_du :
Formats d'entrée acceptés : entier (15000), avec séparateur espace (15 000), avec séparateur point (15.000). Supprimer tous les caractères non-numériques, parser en entier.
SI le résultat est ≤ 0 ALORS anomalie MONTANT_INVALIDE → BLOQUANT.
SI le champ est vide ALORS anomalie MONTANT_ABSENT → BLOQUANT.
Stocké toujours en entier (unités XOF, 0 décimales). Devise XOF codée en dur pour V1 (CI uniquement) - pas de colonne devise dans le modèle V1.
ING-R-009 - Résolution du code agence [V1] [BACKEND]¶
Champ code_agence :
SI vide ALORS anomalie CODE_AGENCE_ABSENT → BLOQUANT.
SI non vide ALORS rechercher la valeur code_agence (insensible à la casse) dans la table agency pour ce tenant_id.
SI aucune correspondance ALORS anomalie CODE_AGENCE_INCONNU (valeur : le code inconnu) → BLOQUANT.
SI le rôle de l'opérateur importateur est OPERATOR (pas ADMIN) et que l'agency_id résolu n'est pas dans ses agences autorisées ALORS anomalie CODE_AGENCE_NON_AUTORISE → BLOQUANT (empêche un opérateur d'ingérer des lignes hors de son périmètre).
ING-R-010 - Identifiants et champs structurels obligatoires [V1] [BACKEND]¶
identifiant_client vide → anomalie IDENTIFIANT_CLIENT_ABSENT → BLOQUANT.
identifiant_contrat vide → anomalie IDENTIFIANT_CONTRAT_ABSENT → BLOQUANT.
branche vide → anomalie BRANCHE_ABSENTE → BLOQUANT.
nom vide → anomalie NOM_ABSENT → BLOQUANT.
prenoms vide → anomalie PRENOMS_ABSENT → BLOQUANT.
ING-R-011 - Doublons intra-fichier [V1] [BACKEND]¶
SI le même tuple (identifiant_client, identifiant_contrat) apparaît plus d'une fois dans le fichier courant ALORS anomalie DOUBLON_INTRA_FICHIER sur la 2e occurrence et suivantes → BLOQUANT. Seule la première occurrence est stagée ; les doublons sont listés dans le rapport d'anomalies. Ce champ n'est pas corrigeable dans l'atelier (le fichier source doit être corrigé).
ING-R-012 - Clé de déduplication inter-fichiers [V1] [BACKEND]¶
La clé de déduplication d'un contrat entre plusieurs imports est (tenant_id, identifiant_client, identifiant_contrat). La date_echeance et le montant_du sont des données du contrat, pas des critères d'identité. Un import ultérieur avec la même clé mais un montant_du ou une date_echeance différents déclenche une mise à jour silencieuse + audit (voir ING-R-013) et une anomalie de surveillance (voir ING-R-014).
ING-R-013 - Champs optionnels [V1] [BACKEND]¶
email absent → anomalie EMAIL_ABSENT → NON-BLOQUANT.
numero_police absent → anomalie NUMERO_POLICE_ABSENT → NON-BLOQUANT.
Les anomalies non-bloquantes sont stockées dans la liste d'anomalies de la ligne stagée mais ne bloquent pas le commit. Elles sont visibles dans l'atelier à titre informatif.
ING-R-014 - Comportement tout-ou-rien au commit [V1] [BACKEND]¶
SI une ligne stagée a le statut BLOCKED au moment du commit ALORS le commit est rejeté dans sa totalité ; aucune ligne n'est promue dans les tables de production ; une erreur française est retournée indiquant le nombre d'anomalies bloquantes restantes.
SI toutes les lignes stagées sont PENDING_CLEAN ou CORRECTED (aucune ligne BLOCKED) ALORS la totalité est committée en une seule transaction DB :
1. Upsert customer par (tenant_id, source_customer_id) (ING-R-015).
2. Upsert contract par (tenant_id, source_customer_id, source_contract_id) mettant à jour date_echeance, montant_du, branche, statut_source (ING-R-016).
3. Insertion d'un enregistrement ingestion_import_row par ligne (traçabilité).
4. Insertion de l'entrée audit ING_COMMITTED.
5. Publication de l'événement domaine ImportCommitted.
6. Suppression de toutes les lignes staging de cet import.
7. Mise à jour ingestion_import.status → COMMITTED.
Si la transaction échoue (erreur DB), l'entrée staging est préservée et le commit est retentable.
ING-R-015 - Upsert client (Customer) [V1] [BACKEND]¶
SI (tenant_id, source_customer_id) existe dans customer ALORS :
- Mettre à jour nom et prenoms silencieusement ; émettre l'entrée d'audit ING_CUSTOMER_UPDATED avec le diff.
- SI la date_naissance du fichier diffère de la valeur stockée ALORS créer une anomalie DATE_NAISSANCE_MODIFIEE → SURVEILLANCE (la ligne reste commitable, la divergence est signalée dans la vue anomalies).
SI (tenant_id, source_customer_id) n'existe pas ALORS insérer une nouvelle ligne customer ; émettre l'entrée d'audit ING_CUSTOMER_CREATED.
ING-R-016 - Upsert contrat (Contract) [V1] [BACKEND]¶
SI (tenant_id, source_customer_id, source_contract_id) existe dans contract ALORS :
- Mettre à jour date_echeance, montant_du, branche, agency_id, statut_source, informations_complementaires, numero_police, immatriculation silencieusement.
- SI date_echeance ou montant_du ont changé ALORS créer une anomalie DONNEES_CONTRAT_MODIFIEES → SURVEILLANCE avec les valeurs avant/après ; émettre l'entrée d'audit ING_CONTRACT_UPDATED avec le diff.
SI (tenant_id, source_customer_id, source_contract_id) n'existe pas ALORS insérer une nouvelle ligne contract ; émettre l'entrée d'audit ING_CONTRACT_CREATED.
ING-R-017 - Permissions d'import [V1] [BACKEND]¶
Rôle ADMIN : peut importer des fichiers contenant des lignes de n'importe quelle agence du tenant.
Rôle OPERATOR : peut importer des fichiers contenant des lignes uniquement des agences qui lui sont attribuées. Les lignes avec un code_agence hors périmètre → anomalie CODE_AGENCE_NON_AUTORISE → BLOQUANT.
Rôle READ : ne peut pas téléverser de fichier ; l'endpoint d'upload retourne HTTP 403.
ING-R-018 - Expiration du staging [V1] [BACKEND]¶
Un job système s'exécute quotidiennement à 02h00 heure locale :
POUR CHAQUE ingestion_import avec statut IN (STAGED_CLEAN, STAGED_WITH_ANOMALIES) ET created_at < MAINTENANT() - INTERVALLE '7 jours' :
1. Supprimer les lignes staging associées.
2. Mettre à jour ingestion_import.status → EXPIRED.
3. Émettre l'entrée d'audit ING_STAGING_EXPIRED.
4. Afficher un badge dans la console OP (« Import en attente expiré - [nom fichier] du [date] ») jusqu'à fermeture par l'opérateur.
ING-R-019 - Signalement quotidien des imports en attente [V1] [OPERATOR]¶
SI un ingestion_import d'un tenant a le statut STAGED_WITH_ANOMALIES et created_at entre 1 jour et 6 jours (non expiré) ALORS un badge d'alerte est affiché dans la console OP (« Import en attente de correction depuis N jour(s) - [nom fichier] »). V1 : badge console uniquement. V2 : notification email/SMS opérateur.
B.2 Machines à états¶
B.2.1 - Cycle de vie d'un Import (ingestion_import)¶
stateDiagram-v2
[*] --> UPLOADING : Opérateur téléverse le fichier
UPLOADING --> PARSE_FAILED : Format / taille / hash invalide
UPLOADING --> STAGED_CLEAN : Parse OK, 0 anomalie bloquante
UPLOADING --> STAGED_WITH_ANOMALIES : Parse OK, ≥1 anomalie bloquante
STAGED_CLEAN --> COMMITTED : Opérateur confirme le commit
STAGED_CLEAN --> ABANDONED : Opérateur annule
STAGED_CLEAN --> EXPIRED : Job sweep 7 jours
STAGED_WITH_ANOMALIES --> STAGED_CLEAN : Toutes anomalies bloquantes corrigées
STAGED_WITH_ANOMALIES --> ABANDONED : Opérateur annule
STAGED_WITH_ANOMALIES --> EXPIRED : Job sweep 7 jours
COMMITTED --> [*]
ABANDONED --> [*]
EXPIRED --> [*]
PARSE_FAILED --> [*]
B.2.2 - Cycle de vie d'une Ligne stagée (ingestion_staged_row)¶
stateDiagram-v2
[*] --> BLOCKED : Parse : ≥1 anomalie bloquante
[*] --> PENDING_CLEAN : Parse : 0 anomalie bloquante (non-bloquantes ou aucune)
BLOCKED --> CORRECTED : Opérateur corrige tous les champs bloquants ; re-validation réussie
BLOCKED --> BLOCKED : Opérateur corrige ; re-validation échoue (anomalie persistante ou nouvelle)
PENDING_CLEAN --> COMMITTED_ROW : Import parent commité
CORRECTED --> COMMITTED_ROW : Import parent commité
BLOCKED --> PURGED : Import parent abandonné ou expiré
PENDING_CLEAN --> PURGED : Import parent abandonné ou expiré
CORRECTED --> PURGED : Import parent abandonné ou expiré
COMMITTED_ROW --> [*]
PURGED --> [*]
B.3 Table de décision - Classification des anomalies¶
| Champ / condition | Code anomalie | Classe | Corrigeable dans l'atelier |
|---|---|---|---|
identifiant_client vide |
IDENTIFIANT_CLIENT_ABSENT |
BLOQUANT | Non - re-téléverser |
identifiant_contrat vide |
IDENTIFIANT_CONTRAT_ABSENT |
BLOQUANT | Non - re-téléverser |
nom vide |
NOM_ABSENT |
BLOQUANT | Non - re-téléverser |
prenoms vide |
PRENOMS_ABSENT |
BLOQUANT | Non - re-téléverser |
telephone vide |
TELEPHONE_ABSENT |
BLOQUANT | Oui |
telephone invalide après normalisation |
TELEPHONE_INVALIDE |
BLOQUANT | Oui |
date_naissance absente |
DATE_NAISSANCE_ABSENTE |
BLOQUANT | Oui |
date_naissance format invalide ou date impossible |
DATE_NAISSANCE_INVALIDE |
BLOQUANT | Oui |
montant_du absent |
MONTANT_ABSENT |
BLOQUANT | Oui |
montant_du ≤ 0 |
MONTANT_INVALIDE |
BLOQUANT | Oui |
date_echeance absente |
DATE_ECHEANCE_ABSENTE |
BLOQUANT | Non - re-téléverser |
date_echeance > 30 jours dans le passé |
ECHEANCE_DEPASSEE |
BLOQUANT | Non - re-téléverser |
branche vide |
BRANCHE_ABSENTE |
BLOQUANT | Non - re-téléverser |
code_agence vide |
CODE_AGENCE_ABSENT |
BLOQUANT | Oui |
code_agence inconnu dans la table agence du tenant |
CODE_AGENCE_INCONNU |
BLOQUANT | Oui |
code_agence hors portée de l'opérateur (rôle OPERATOR) |
CODE_AGENCE_NON_AUTORISE |
BLOQUANT | Non - escalade admin |
Doublon intra-fichier (même identifiant_client + identifiant_contrat) |
DOUBLON_INTRA_FICHIER |
BLOQUANT | Non - re-téléverser |
email absent |
EMAIL_ABSENT |
NON-BLOQUANT | - |
numero_police absent |
NUMERO_POLICE_ABSENT |
NON-BLOQUANT | - |
date_naissance du fichier ≠ valeur stockée pour ce client |
DATE_NAISSANCE_MODIFIEE |
SURVEILLANCE | - (commit auto + alerte anomalie) |
montant_du ou date_echeance du fichier ≠ valeur stockée pour ce contrat |
DONNEES_CONTRAT_MODIFIEES |
SURVEILLANCE | - (commit auto + alerte anomalie) |
Règles de classification :
- BLOQUANT : empêche le commit. L'import reste STAGED_WITH_ANOMALIES tant qu'au moins une ligne est BLOCKED.
- NON-BLOQUANT : ligne commitable avec avertissement. Visible dans l'atelier et dans le rapport d'import.
- SURVEILLANCE : ligne commitable ; anomalie promue dans la vue anomalies de la console OP pour revue humaine.
B.4 Spécification du modèle de colonnes canonique¶
Fichier modèle V1 : modele_import_papillon.xlsx (téléchargeable depuis l'interface)
| # | Nom de colonne | Obligatoire | Type | Format attendu | Anomalie si absent/invalide |
|---|---|---|---|---|---|
| 1 | identifiant_client |
✅ | Chaîne | Non vide, max 100 car. | IDENTIFIANT_CLIENT_ABSENT → BLOQUANT |
| 2 | nom |
✅ | Chaîne | Non vide, max 100 car. | NOM_ABSENT → BLOQUANT |
| 3 | prenoms |
✅ | Chaîne | Non vide, max 150 car. | PRENOMS_ABSENT → BLOQUANT |
| 4 | telephone |
✅ | Chaîne | Normalisé en E.164 +225XXXXXXXXXX | TELEPHONE_ABSENT/INVALIDE → BLOQUANT |
| 5 | date_naissance |
✅ | Date | DD/MM/AAAA (cellule Date Excel acceptée) | DATE_NAISSANCE_ABSENTE/INVALIDE → BLOQUANT |
| 6 | identifiant_contrat |
✅ | Chaîne | Non vide, max 100 car. | IDENTIFIANT_CONTRAT_ABSENT → BLOQUANT |
| 7 | branche |
✅ | Chaîne | Non vide, max 100 car. (texte libre V1) | BRANCHE_ABSENTE → BLOQUANT |
| 8 | date_echeance |
✅ | Date | DD/MM/AAAA (cellule Date Excel acceptée) | DATE_ECHEANCE_ABSENTE / ECHEANCE_DEPASSEE → BLOQUANT |
| 9 | montant_du |
✅ | Entier | Entier positif XOF ; séparateurs espace ou point acceptés | MONTANT_ABSENT/INVALIDE → BLOQUANT |
| 10 | code_agence |
✅ | Chaîne | Doit exister dans agency du tenant (insensible à la casse) |
CODE_AGENCE_ABSENT/INCONNU/NON_AUTORISE → BLOQUANT |
| 11 | email |
❌ | Chaîne | Format email ou vide | EMAIL_ABSENT → NON-BLOQUANT |
| 12 | numero_police |
❌ | Chaîne | Max 100 car. | NUMERO_POLICE_ABSENT → NON-BLOQUANT |
| 13 | immatriculation |
❌ | Chaîne | Max 50 car. | - |
| 14 | statut_source |
❌ | Chaîne | Max 50 car. (statut dans le système source) | - |
| 15 | informations_complementaires |
❌ | Chaîne | Max 500 car. (texte libre pour templates notification) | - |
Devise : XOF codé en dur pour V1 (CI uniquement). Aucune colonne devise dans le modèle V1. La colonne devise + profil pays sera introduite en V2.
B.5 Modèle de données¶
B.5.1 Entités propres au module ING¶
ingestion_import - Entête d'un import
| Colonne | Type | Contrainte |
|---|---|---|
id |
UUID | PK |
tenant_id |
UUID | FK → tenant, NOT NULL |
uploaded_by |
UUID | FK → user, NOT NULL |
filename |
VARCHAR(255) | NOT NULL |
file_hash_sha256 |
CHAR(64) | NOT NULL ; UNIQUE par tenant_id |
file_mime |
VARCHAR(100) | text/csv ou OOXML |
file_size_bytes |
INTEGER | NOT NULL |
file_minio_path |
TEXT | Convention : /<tenant_id>/ingestion/<import_id>/<filename> |
status |
ENUM | UPLOADING, PARSE_FAILED, STAGED_CLEAN, STAGED_WITH_ANOMALIES, COMMITTED, ABANDONED, EXPIRED |
row_count_total |
INTEGER | Défini après parsing |
row_count_blocked |
INTEGER | |
row_count_non_blocking |
INTEGER | |
row_count_monitoring |
INTEGER | |
row_count_clean |
INTEGER | |
committed_created |
INTEGER | Défini au commit |
committed_updated |
INTEGER | Défini au commit |
committed_skipped |
INTEGER | Défini au commit |
parse_error_detail |
TEXT (JSON) | Pour PARSE_FAILED uniquement |
created_at |
TIMESTAMPTZ | NOT NULL |
expires_at |
TIMESTAMPTZ | created_at + 7 jours ; défini à l'insertion |
committed_at |
TIMESTAMPTZ | NULL jusqu'au commit |
ingestion_staged_row - Ligne en attente de validation dans l'atelier
| Colonne | Type | Contrainte |
|---|---|---|
id |
UUID | PK |
import_id |
UUID | FK → ingestion_import |
tenant_id |
UUID | NOT NULL (dénormalisé pour isolation) |
row_number |
INTEGER | Numéro de ligne 1-based dans le fichier source |
status |
ENUM | BLOCKED, PENDING_CLEAN, CORRECTED, COMMITTED_ROW, PURGED |
source_customer_id |
VARCHAR(100) | Tel qu'importé |
nom |
VARCHAR(100) | |
prenoms |
VARCHAR(150) | |
telephone_raw |
VARCHAR(50) | Tel qu'importé |
telephone_normalized |
VARCHAR(15) | E.164 après normalisation |
date_naissance |
DATE | Parsée ou NULL |
source_contract_id |
VARCHAR(100) | |
branche |
VARCHAR(100) | |
date_echeance |
DATE | |
montant_du |
INTEGER | XOF entier |
agency_id |
UUID | FK → agency ; NULL si non résolu |
code_agence_raw |
VARCHAR(50) | Tel qu'importé |
email |
VARCHAR(255) | |
numero_police |
VARCHAR(100) | |
immatriculation |
VARCHAR(50) | |
statut_source |
VARCHAR(50) | |
informations_complementaires |
VARCHAR(500) | |
anomalies |
JSONB | Tableau de {code, champ, valeur, classe, corrigeable} |
corrected_fields |
JSONB | Champs corrigés dans l'atelier avec valeurs avant/après |
last_corrected_at |
TIMESTAMPTZ |
ingestion_import_row - Trace immuable après commit (un enregistrement par ligne committée)
| Colonne | Type | Contrainte |
|---|---|---|
id |
UUID | PK |
import_id |
UUID | FK → ingestion_import |
tenant_id |
UUID | NOT NULL |
customer_id |
UUID | FK → customer |
contract_id |
UUID | FK → contract |
action |
ENUM | CREATED, UPDATED, SKIPPED |
anomalies_at_commit |
JSONB | Anomalies non-bloquantes + surveillance restantes au commit |
committed_at |
TIMESTAMPTZ |
B.5.2 Entités partagées (upsertées par ING, lues par tous les modules)¶
Per V1 PRD §27 - ING écrit mais ne détient pas la gouvernance de schéma de ces entités.
customer - Champs minimaux écrits par ING :
tenant_id, source_customer_id (clé de dédup), nom, prenoms, telephone (E.164), date_naissance, email (optionnel), created_at, updated_at.
contract - Champs minimaux écrits par ING :
tenant_id, agency_id, customer_id, source_contract_id (clé de dédup avec tenant_id + customer_id), branche, date_echeance, montant_du (entier XOF), numero_police, immatriculation, statut_source, informations_complementaires, import_id (dernier import ayant touché ce contrat), created_at, updated_at.
B.6 Implications multi-tenant¶
| Préoccupation | Règle V1 |
|---|---|
| Isolation des lignes | Chaque requête sur ingestion_import, ingestion_staged_row, ingestion_import_row DOIT inclure WHERE tenant_id = :tenantId issu du contexte tenant de la requête. |
| Unicité du hash fichier | file_hash_sha256 unique par tenant_id, pas globalement. Deux tenants différents peuvent importer le même fichier sans conflit. |
| Résolution du code agence | Toujours résolue dans la table agency du tenant. Pas de namespace agence global. |
| Shared DB / shared schema (défaut) | Toutes les tables ING coexistent ; isolation par tenant_id à la couche application. |
| BYO-DB | Tables ING dans le PostgreSQL propre au tenant. Routing résolu à la requête. Schéma identique. |
| Convention chemin MinIO | /<tenant_id>/ingestion/<import_id>/<nom_fichier_original> |
| Pas de UNIQUE global sur noms | file_hash_sha256 unique par tenant, pas globalement. Aucune contrainte globale sur des champs d'identité qui supposerait un tenant unique. |
B.7 Dépendances inter-modules¶
ING consomme :¶
| Dépendance | Module source | Raison |
|---|---|---|
Table agency (lecture) |
TENANT | Résoudre code_agence → agency_id ; vérifier la portée de l'opérateur |
Table user / token Keycloak (lecture) |
AUTH | Identifier l'opérateur importateur ; vérifier rôle et agences attribuées |
| Résolveur de contexte tenant | AUTH | Peupler tenant_id sur chaque requête |
| Client MinIO | Infrastructure | Stocker et récupérer les fichiers source |
| Client Redis | Infrastructure | Rate-limit upload (10 tentatives/opérateur/heure) |
ING produit (publié vers) :¶
| Dépendance | Module cible | Mécanisme |
|---|---|---|
Événement domaine ImportCommitted |
OP | Spring Modulith @ApplicationEvent ; payload : {importId, tenantId, agencyIds[], rowCountCreated, rowCountUpdated, rowCountSkipped} |
Lignes customer (upsert) |
OP, CP, PAY, AUDIT | Écriture directe DB dans la table partagée customer |
Lignes contract (upsert) |
OP, CP, PAY, AUDIT | Écriture directe DB dans la table partagée contract |
| Entrées audit log | AUDIT | Via l'API du module audit-log |
API ING exposée (consommée par le frontend operator-console) :¶
| Endpoint | Méthode | Description |
|---|---|---|
/api/ingestion/imports |
POST (multipart) |
Téléverser + parser un fichier ; retourne le résumé de l'import stagé |
/api/ingestion/imports |
GET |
Historique des imports paginé, scopé tenant |
/api/ingestion/imports/{importId} |
GET |
Détails de l'import + compteurs + résumé anomalies |
/api/ingestion/imports/{importId}/rows |
GET |
Lignes stagées paginées ; filtrables par statut |
/api/ingestion/imports/{importId}/rows/{rowId} |
PATCH |
Corriger un champ dans l'atelier (re-validation immédiate) |
/api/ingestion/imports/{importId}/commit |
POST |
Commettre l'import stagé |
/api/ingestion/imports/{importId}/abandon |
POST |
Abandonner et purger le staging |
/api/ingestion/template |
GET |
Télécharger le modèle XLSX canonique |
/api/ingestion/imports/{importId}/source-file |
GET |
URL signée MinIO du fichier source (fenêtre 12 mois ; URL signée ≤15 min) |
B.8 Événements d'audit¶
(Per V1 PRD §18 - chaque événement doit comporter au minimum : identifiant d'événement, horodatage, acteur initiateur, entité concernée, résultat.)
| Code événement | Déclencheur | Champs obligatoires |
|---|---|---|
ING_UPLOAD_RECEIVED |
Fichier reçu par le serveur (avant parsing) | tenant_id, user_id, filename, file_size_bytes, file_hash |
ING_PARSE_FAILED |
Fichier rejeté avant staging | tenant_id, user_id, filename, reason_code, detail |
ING_STAGED |
Entrée staging créée après parsing | tenant_id, user_id, import_id, row_count_total, row_count_blocked, row_count_non_blocking |
ING_ROW_CORRECTED |
Opérateur corrige un champ dans l'atelier | tenant_id, user_id, import_id, staged_row_id, field_name, old_value, new_value |
ING_COMMITTED |
Import commité avec succès | tenant_id, user_id, import_id, rows_created, rows_updated, rows_skipped |
ING_ABANDONED |
Opérateur annule le staging | tenant_id, user_id, import_id |
ING_STAGING_EXPIRED |
Job sweep 7 jours purge le staging | tenant_id, import_id (acteur = système) |
ING_CUSTOMER_CREATED |
Nouveau client upsert au commit | tenant_id, import_id, customer_id, source_customer_id |
ING_CUSTOMER_UPDATED |
Client existant mis à jour au commit | tenant_id, import_id, customer_id, diff |
ING_DOB_DRIFT_DETECTED |
Date de naissance fichier ≠ valeur stockée (SURVEILLANCE) | tenant_id, import_id, customer_id, old_year (AAAA uniquement), new_year (AAAA uniquement) |
ING_CONTRACT_CREATED |
Nouveau contrat upsert au commit | tenant_id, import_id, contract_id, source_contract_id |
ING_CONTRACT_UPDATED |
Contrat existant mis à jour au commit | tenant_id, import_id, contract_id, diff (champs modifiés + valeurs avant/après) |
Note sur la minimisation PII dans l'audit : seule l'année (AAAA) de la date de naissance est journalisée dans l'audit - jamais la date complète. Principe de minimisation, loi n°2013-450.
Partie C - Contraintes et frontières¶
C.1 Conformité réglementaire¶
CIMA Rég. 01-24¶
- La plateforme agit en tant que prestataire technique pour le compte du courtier/assureur qui est le responsable de traitement. ING traite leurs données clients et contrats en vertu d'un accord de sous-traitance B2B (DPA plateforme-courtier). (Provisoirement validé - lacune audit juridique #1 : position prestataire technique à valider par avocat.)
- Le flux d'import génère une piste de preuves (audit log + fichier original dans MinIO) suffisante pour reconstruire la lignée de données pour tout cycle de renouvellement (V1 PRD §16.5).
- Le module ING ne distribue ni ne gère de contrats d'assurance lui-même - il transforme et achemine uniquement des données pour l'intermédiaire agréé.
ARTCI / Loi n°2013-450¶
- Base légale : ING traite la DCP (données à caractère personnel) des clients - nom, téléphone, date de naissance - en qualité de sous-traitant pour le courtier (responsable de traitement). Le DPA plateforme-courtier doit couvrir l'ingestion comme activité de traitement. (Provisoirement validé - lacune audit #3 : formailités ARTCI pour l'éditeur à clarifier.)
- Minimisation : seuls les 10 champs obligatoires + optionnels définis dans le modèle canonique sont stockés. Le champ
informations_complementairesest limité à 500 caractères. - Rétention :
- Fichiers source : purgés de MinIO après 12 mois. (Provisoirement validé.)
- Lignes staging : purgées au commit, à l'abandon ou à l'expiration (7 jours).
ingestion_import_row(trace de commit) : conservée 12 mois. (Provisoirement validé.)- Date de naissance dans l'audit : seule l'année est retenue ; la date complète n'est jamais écrite dans l'audit trail.
Règlement CIMA cybersécurité (010-24 ou équivalent en vigueur)¶
- Chiffrement en transit (TLS 1.2+) sur tous les endpoints de téléversement/téléchargement.
- Fichiers MinIO stockés avec chiffrement côté serveur (SSE).
- (Lacune audit juridique #4 : numéro d'article précis et période transitoire à confirmer par avocat. Recommandation : prévoir un backlog item ING-SEC-001 une fois les obligations de base ancrées.)
C.2 Localisation¶
- Toute copie UI en français.
- Messages d'erreur : chaque
CODE_ANOMALIEest mappé à une phrase française lisible par un humain. Exemples : TELEPHONE_INVALIDE→ « Le numéro de téléphone « {valeur} » n'est pas un numéro ivoirien valide. Format attendu : +225 suivi de 10 chiffres. »CODE_AGENCE_INCONNU→ « Le code agence « {valeur} » n'existe pas dans votre configuration. Vérifiez les codes agence dans les paramètres de votre cabinet. »FICHIER_DEJA_IMPORTE→ « Ce fichier a déjà été importé le {date} à {heure} par {prénom nom}. »ECHEANCE_DEPASSEE→ « La date d'échéance {date} est dépassée depuis plus de 30 jours. »- Montants affichés :
15 000 XOF(espace comme séparateur de milliers, 0 décimales). - Dates affichées :
JJ/MM/AAAA. - Téléphone stocké en E.164 (
+225XXXXXXXXXX) ; affiché avec espaces pour la lisibilité (+225 07 12 34 56 78) dans l'atelier de correction. - Fichier modèle téléchargé sous le nom
modele_import_papillon.xlsx. - Pilule de statut import :
| Statut interne | Libellé français |
|---|---|
STAGED_CLEAN |
En attente de validation |
STAGED_WITH_ANOMALIES |
En attente - anomalies à corriger |
COMMITTED |
Importé |
ABANDONED |
Annulé |
EXPIRED |
Expiré |
PARSE_FAILED |
Échec de lecture |
C.3 Sécurité¶
- Isolation tenant : chaque endpoint ING extrait le
tenant_iddu token Keycloak de l'opérateur authentifié - jamais depuis une entrée utilisateur. - Contrôle de rôle : téléversement et commit nécessitent le rôle
ADMINouOPERATOR(ING-R-017). RôleREAD→ HTTP 403. - Validation du type de fichier : l'endpoint multipart valide le Content-Type + les magic bytes du fichier (indépendamment de l'extension). Rejeter un
.csvavec magic bytes OOXML, etc. - Accès MinIO : fichiers source accessibles uniquement via des URL signées côté serveur (pas d'URL publiques directes) ; durée de vie de l'URL signée ≤ 15 minutes (distinct de la période de rétention de 12 mois).
- Visibilité PII : l'atelier de correction masque la date de naissance après affichage (affiche
••/••/AAAAen liste ; révèle la valeur complète uniquement dans le champ d'édition actif). - Pas d'exposition de données client dans les messages d'erreur de hash : le message
FICHIER_DEJA_IMPORTErévèle uniquement la date, l'heure et le nom de l'opérateur importateur - jamais de données contrat. - Rate-limiting sur l'endpoint upload : max 10 tentatives de téléversement par opérateur par heure (compteur Redis par
user_id). - Idempotence : la déduplication par hash (ING-R-003) rend le re-téléversement du même fichier idempotent.
C.4 Connectivité¶
Le module ING est utilisé exclusivement du côté opérateur (desktop-first, online-first per V1 PRD §30.3). Aucune contrainte low-bandwidth ou reprenabilité spécifique à ING.
- Le téléversement est un POST multipart HTTP unique ; la limite de 5 Mo garantit la complétion en quelques secondes même sur une connexion de bureau lente.
- Si la requête d'upload expire côté client, l'opérateur re-téléverse ; la vérification du hash fichier prévient un double import.
- Les imports stagés survivent aux redémarrages serveur (stockés en PostgreSQL) ; l'opérateur peut reprendre l'atelier de correction après une déconnexion.
- L'atelier de correction ne fait pas d'UI optimiste (pas de file client, pas d'IndexedDB) : chaque correction de champ est un aller-retour serveur. Acceptable côté opérateur (en ligne).
C.5 Hors périmètre V1¶
| Élément | Cible |
|---|---|
| Imports déclenchés via API / connecteurs ERP / schedulers | V2 |
| Mapping de colonnes flexible par tenant | V2 |
| Format XLS (legacy Excel 97-2003) | V2 |
| Upload batch multi-fichiers | V2 |
| Colonne devise / multi-devises | V2 |
Validation référentiel branche (liste de valeurs) |
V2 |
| TTL staging configurable par tenant | V2 |
| Notification email/SMS à l'opérateur sur expiration staging | V2 |
| Suppression de ligne individuelle dans l'atelier sans re-téléversement | V2 (question ouverte A.6 #4) |
| Atelier de correction en mode hors-ligne | Hors périmètre |
| Import pour des filiales étrangères (tenants séparés) | V2 - flux d'onboarding tenant distinct |
| Suggestions de correction assistées par IA | V3 |
Document produit par le personnage ANALYST (Claude Code) - session du 2026-04-30. À soumettre à l'approbation du Founder/PO avant de passer à la session ARCHITECT.