Files
MeeJayandClaude Opus 5 93f0689c1e Initial import: LifeTrack v1 (santé, vape, finances)
Tracker de vie auto-hébergé : suivi poids/calories/sport avec planning de
pesées, sevrage tabac (vape) avec modèle de coût DIY et économies, et
finances personnelles avec import de relevés bancaires.

Architecture : FastAPI + SQLAlchemy 2.0 + PostgreSQL 16, React 18 + TS +
Vite + Tailwind + ECharts, déploiement Docker Compose. Modules
auto-découverts des deux côtés (pkgutil / import.meta.glob) et framework
de connecteurs à deux voies (importeurs de fichiers + ingestion JSON)
pour brancher de nouvelles sources sans toucher au noyau.

Validé : 292 tests pytest, tsc + vite build, contrat API/web vérifié
contre le schéma OpenAPI, et déploiement Docker réel sur PostgreSQL 16
(28 tables, SPA servie par nginx, wizard de premier démarrage).

Documentation : README.md, docs/GUIDE.md, CONVENTIONS.md, et les
documents de conception et de recherche dans docs/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 10:48:57 +02:00

591 lines
51 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Module FINANCE — Recherche sur les sources de données et l'import
> Document de recherche pour LifeTrack (module Finance). Rédigé le 2026-08-13.
> Public : agents d'implémentation. Ce document est autoportant — **aucune recherche complémentaire n'est prévue**.
> Convention : prose en français, identifiants de code en anglais.
---
## Sommaire
1. [Vue d'ensemble et enseignements clés](#1-vue-densemble-et-enseignements-clés)
2. [Formats d'export des banques françaises (fichiers)](#2-formats-dexport-des-banques-françaises-fichiers)
3. [Export d'activité PayPal](#3-export-dactivité-paypal)
4. [Le format OFX en France (et QIF)](#4-le-format-ofx-en-france-et-qif)
5. [Agrégation PSD2 : état des lieux 2025/2026](#5-agrégation-psd2--état-des-lieux-20252026)
6. [Stratégies de déduplication](#6-stratégies-de-déduplication)
7. [Recommandations d'implémentation — v1 (import fichiers)](#7-recommandations-dimplémentation--v1-import-fichiers)
8. [Recommandations d'implémentation — v2 (synchronisation automatique)](#8-recommandations-dimplémentation--v2-synchronisation-automatique)
9. [Sources](#9-sources)
---
## 1. Vue d'ensemble et enseignements clés
### 1.1 Constats structurants
1. **Le "CSV bancaire français" n'existe pas** : chaque banque a son propre dialecte. Points communs majoritaires : séparateur `;`, virgule décimale, dates `JJ/MM/AAAA`, encodage `ISO-8859-1`/`ISO-8859-15` (Windows-1252 en pratique). Exceptions notables : BoursoBank (dates `AAAA-MM-JJ`), Revolut et N26 (séparateur `,`, point décimal, UTF-8).
2. **Beaucoup de fichiers ont un préambule** (lignes d'en-tête métier avant la ligne d'en-têtes de colonnes) : Crédit Agricole (nombre de lignes **variable**), Société Générale (1 ligne), La Banque Postale (~8 lignes), BNP (1 ligne de solde). Le mapper CSV générique doit donc savoir **sauter N lignes** et/ou **détecter la ligne d'en-tête**.
3. **Les profondeurs d'historique téléchargeable sont faibles** chez les banques traditionnelles (30 à 90 jours typiquement, 6 mois chez SG) : l'utilisateur devra importer régulièrement, d'où l'importance capitale de la **déduplication** et des **fenêtres de recouvrement** (mieux vaut réimporter large que de créer des trous).
4. **OFX est disponible mais imparfait en France** : versions SGML 1.x, encodages legacy, et surtout des `FITID` non fiables chez certaines banques (cas documenté LCL : FITID = type+date+montant ⇒ collisions). Le FITID est un bon signal de dédup, **jamais une garantie**.
5. **Bouleversement PSD2 2025** : GoCardless Bank Account Data (ex-Nordigen), la solution gratuite historique des self-hosters (utilisée par Firefly III et Actual Budget), **n'accepte plus de nouveaux comptes depuis juillet 2025** et est en cours d'extinction. La relève gratuite pour un particulier est **Enable Banking** (mode "restricted" gratuit sur ses propres comptes), désormais supportée par Firefly III et Actual Budget.
6. Firefly III et Actual Budget fournissent des modèles éprouvés de déduplication : identifiant externe prioritaire (`imported_id` / "external identifier"), puis hash de contenu, puis rapprochement flou (montant identique + date proche + libellé similaire). Nous reprenons cette hiérarchie.
### 1.2 Décision recommandée (résumé)
- **v1** : import par fichiers uniquement. Un **mapper CSV générique** (encodage, séparateur, préambule, mapping de colonnes, format de date, virgule décimale, colonnes débit/crédit vs montant signé) + **presets par banque** livrés en JSON + **parseur OFX** (lib Python `ofxparse`) + **preset PayPal**. Déduplication à 3 niveaux (voir §6.4). Saisie manuelle et règles de catégorisation.
- **v2** : connecteur **Enable Banking** (PSD2 officiel, gratuit en mode restreint pour ses propres comptes, couvre les grandes banques françaises) branché sur le **même pipeline de staging/dédup** que les fichiers. Connecteur GoCardless conservé en option pour les détenteurs de comptes historiques. `woob` documenté comme adaptateur optionnel "fragile", non prioritaire.
---
## 2. Formats d'export des banques françaises (fichiers)
### 2.0 Tableau de synthèse
| Banque | Formats | Séparateur CSV | Encodage | Format date | Décimale | Montant | Préambule | Historique |
|---|---|---|---|---|---|---|---|---|
| BoursoBank | CSV, OFX, QIF | `;` | UTF-8 | `AAAA-MM-JJ` | virgule (sauf col. solde : point !) | signé, 1 colonne | non | plusieurs années, période libre |
| Crédit Agricole | CSV, Excel, OFX, QIF (selon caisse) | `;` | ISO-8859-15 | `JJ/MM/AAAA` | virgule | 2 colonnes Débit/Crédit | oui, **variable** (fin = ligne `Date;`) | ~3090 j selon caisse |
| BNP Paribas | CSV, OFX, QIF, PDF | `;` | ISO-8859-1 | `JJ/MM/AAAA` | virgule (+ espace milliers) | signé, 1 colonne | 1 ligne (solde, HTML-échappée 2×) | ~90 j |
| Société Générale | CSV, QIF (revenu en 2020) | `;` | ISO-8859-1/15, CRLF | `JJ/MM/AAAA` | virgule | signé, 1 colonne | 1 ligne (`="compte"`;début;fin;) | ~6 mois |
| La Banque Postale | CSV, TSV, OFX | `;` | ISO-8859-15 | `JJ/MM/AAAA` | virgule | signé, 1 colonne | ~8 lignes (n° compte, soldes) | ~90 j |
| Caisse d'Épargne | CSV (OFX selon interfaces) | `;` | ISO-8859-1 | `JJ/MM/AAAA` | virgule | 2 colonnes Débit/Crédit (crédit préfixé `+`) | non (nouveau format) | limité (pas de solde dans le fichier) |
| Fortuneo | CSV, XLS, QIF, OFX | `;` | ISO-8859-1 / Windows-1252 (à détecter) | `JJ/MM/AAAA` | virgule | 2 colonnes Débit/Crédit | non | jusqu'à ~10 ans |
| Revolut | CSV, Excel, PDF | `,` | UTF-8 | `AAAA-MM-JJ HH:MM:SS` | point | signé + colonne `Fee` | non | historique complet |
| N26 | CSV | `,` | UTF-8 | `AAAA-MM-JJ` | point | signé, 1 colonne | non | historique complet |
| PayPal | CSV, TAB (QIF USD only) | `,` | UTF-8 | selon locale (`JJ/MM/AAAA` en FR) | selon locale (virgule en FR) | Gross/Fee/Net | non | 7 ans (tranches de 12 mois) |
Détails et pièges banque par banque ci-dessous. Les en-têtes cités sont **exacts** (issus de fichiers réels analysés dans des projets open-source, notamment `mincong-h/finance-toolkit`, et de documentations d'import OpenFlyers).
### 2.1 BoursoBank (ex-Boursorama Banque)
- **Accès** : espace client → historique du compte → sélection de période libre → « Exporter » ; formats **CSV, OFX, QIF** proposés. Tous les comptes sélectionnés sont regroupés dans **un seul fichier**.
- **Nom de fichier** : `export-operations-{JJ}-{MM}-{AAAA}_{hh}-{mm}-{ss}.csv` (date de génération).
- **Encodage** : UTF-8. **Séparateur** : `;`. Champs texte entre guillemets doubles.
- **En-tête exact (1re ligne)** :
```
dateOp;dateVal;label;category;categoryParent;amount;comment;accountNum;accountLabel;accountbalance
```
- **Exemple de ligne réelle** :
```
2021-08-17;2021-08-17;"Prime Parrainage";"Virements reçus";"Virements reçus";130,00;;001234;"BOURSORAMA BANQUE";226.68
```
- **Particularités / pièges** :
- Dates en **`AAAA-MM-JJ`** (seule banque française classique dans ce cas).
- `amount` utilise la **virgule** décimale, mais `accountbalance` utilise le **point** décimal dans le même fichier. Ne jamais parser les deux colonnes avec la même routine.
- La colonne `accountbalance` est un solde recalculé glissant, peu fiable : **l'ignorer** pour la comptabilité ; le solde de compte doit être saisi/rapproché séparément.
- `category`/`categoryParent` : catégorisation maison BoursoBank — utile comme **suggestion** de catégorie initiale au mapping.
- `accountNum` répété sur chaque ligne ⇒ permet de **router les lignes vers plusieurs comptes** LifeTrack depuis un fichier unique (le preset doit gérer un fichier multi-comptes).
- Le PEA n'est pas exportable.
### 2.2 Crédit Agricole
- **Accès** : espace client (par **caisse régionale** — les interfaces varient) → « Vos opérations » → « Télécharger vos opérations ». Formats selon caisse : **CSV, Excel (xls), OFX, QIF, TXT**. Historique en ligne court (souvent 30 à 90 jours).
- **Structure CSV** (modèle documenté par OpenFlyers) :
- **Encodage** : ISO-8859-15. **Séparateur** : `;`.
- **Préambule de longueur variable** (infos compte, période, solde). La fin du préambule est repérable par la ligne commençant par `Date;`.
- **En-tête** : `Date;Date valeur;Libellé;Débit Euros;Crédit Euros;`
- Dates `JJ/MM/AAAA`, virgule décimale, montants ventilés en **deux colonnes** Débit/Crédit.
- **Les libellés peuvent contenir des retours à la ligne** (champ multi-lignes entre guillemets) — le parseur CSV doit être configuré pour les champs quotés multi-lignes (le module Python `csv` le gère nativement si on ne pré-découpe pas par lignes).
- Un **pied de page** peut suivre les données (séparé par des lignes vides) — arrêter le parsing à la première ligne dont la 1re colonne n'est pas une date valide.
- **Implémentation preset** : `skip_until_header_startswith: "Date;"` + `stop_on_non_date_row: true`.
### 2.3 BNP Paribas
- **Accès** : mabanque.bnpparibas → « Virements et services » → « Téléchargement des opérations » (URL directe : `/fr/secure/virements-services/telechargement-des-operations` ; la page a été retirée de la navigation en juillet 2023 mais restait accessible en direct). Formats : **PDF, CSV, OFX, QIF**, fenêtre ~90 jours.
- **Nom de fichier** : type `E{digits}.csv` se terminant par les 4 derniers chiffres du compte (ex. `E0790170.csv`) ; regex utilisable : `E\d+{last4}\.csv`.
- **Structure CSV** (fichier réel) :
- **Encodage** : ISO-8859-1. **Séparateur** : `;`.
- **1re ligne = métadonnées de solde**, PAS un en-tête de colonnes :
```
"Cr&eacute;dit immobilier";"Cr&amp;eacute;dit immobilier";****1234;18/03/2022;;-123 456,78
```
Soit : libellé compte ; libellé compte (échappé HTML **deux fois** — il faut appliquer `html.unescape()` **2×**) ; n° compte masqué ; date d'export ; (vide) ; **solde** avec espace de milliers et virgule décimale.
- **Lignes suivantes = opérations**, sans ligne d'en-tête :
```
05/01/2022;;; AMORTISSEMENT PRET 1234;70,93
```
Colonnes : `date; (vide); (vide); libellé; montant_signé`. Date `JJ/MM/AAAA`, virgule décimale, espaces de milliers possibles.
- **Implémentation preset** : `header_rows: 1` (ligne solde à parser à part pour proposer un rapprochement de solde), `columns: [date, skip, skip, label, amount]`, `has_column_header: false`.
### 2.4 Société Générale
- **Accès** : espace client → « Gestion et suivi » / « Relevés et documents » → export **CSV** (~6 mois d'historique). Le **QIF** avait disparu à la refonte du site (2019) puis est **revenu en février 2020**.
- **Structure CSV** (fichier réel analysé par enodev.fr) :
- **Encodage** : ISO-8859-1 (ou -15), fins de ligne **CRLF**. **Séparateur** : `;`.
- **Ligne 1 (préambule)** : `="0201900016400270";17/05/2019;16/11/2019;` — n° de compte en notation Excel `="…"` (pour préserver les zéros de tête), puis début et fin de période.
- **Ligne 2 (en-tête)** : `date_comptabilisation;libellé_complet_operation;montant_operation;devise;`
- **Ligne de données** : `15/11/2019;CARTE X7527 15/11 METRO ;-14,90;EUR;`
- Date `JJ/MM/AAAA`, **montant signé** unique, virgule décimale, devise explicite, point-virgule terminal (colonne vide finale).
- Les libellés sont **paddés d'espaces** et peuvent s'étaler sur plusieurs lignes pour les virements/prélèvements ⇒ `strip()` + gestion des champs multi-lignes.
- **Implémentation preset** : `header_rows: 1` puis ligne d'en-têtes ; extraire le n° de compte de la ligne 1 via regex `="(\d+)"`.
### 2.5 La Banque Postale
- **Accès** : espace client → menu « OPÉRATIONS » → « Téléchargement d'opérations » → choix du compte → « Format CSV (compatible Excel) » (aussi **TSV** et **OFX** selon le type de compte, via le bouton « Télécharger le détail »). **Limite : ~90 jours** d'historique.
- **Structure CSV** (modèle documenté par OpenFlyers) :
- **Encodage** : ISO-8859-15. **Séparateur** : `;`. Pas de pied de page.
- **Préambule (~8 lignes)**, exploitables pour le rapprochement de solde :
```
Numéro Compte ;[numéro]
Type ;COMPTE
Compte tenu en ;euros
Date ;[date]
Solde (EUROS) ;[solde]
Solde (FRANCS) ;[solde]
```
- **En-tête de colonnes** : `Date;Libellé;Montant(EUROS);Montant(FRANCS)`
- Date `JJ/MM/AAAA`, **montant signé** (négatif = débit), virgule décimale. La colonne `Montant(FRANCS)` est un vestige à ignorer.
- Attention : l'export **TSV perd la date de valeur** et est découpé mois par mois — préférer CSV.
- **Implémentation preset** : `skip_until_header_startswith: "Date;"` (robuste face aux variations du préambule), colonne FRANCS ignorée.
### 2.6 Caisse d'Épargne (groupe BPCE)
- **Accès** : espace client → sur le compte, « Gérer » → « Télécharger les opérations » (page d'aide officielle : `aide.caisse-epargne.fr/contents/comment-exporter-mes-operations`). Le fichier **ne contient pas le solde**.
- **Noms de fichier observés** : `{DDMMYYYY}_{numéro}.csv` (ancien) et `{numéro}_{DDMMYYYY}_{DDMMYYYY}.csv` (récent, période début/fin). Regex preset : `\d*{account}_\d{8}_\d{8}\.csv` et `\d{8}_{account}\.csv`.
- **Structure CSV « nouveau format » (2024+, fichier réel)** :
- **Encodage** : ISO-8859-1. **Séparateur** : `;`. Pas de préambule.
- **En-tête exact** :
```
Date de comptabilisation;Libelle simplifie;Libelle operation;Reference;Informations complementaires;Type operation;Categorie;Sous categorie;Debit;Credit;Date operation;Date de valeur;Pointage operation
```
- **Exemple** :
```
15/11/2024;SUPERMARCHE;CB SUPERMARCHE CENTRAL FACT 141124;;;Carte bancaire;Alimentation;Hyper/supermarche;-45,50;;14/11/2024;15/11/2024;0
10/11/2024;EMPLOYEUR SA;VIR INST Employeur SA;REF123456;Salaire Novembre-;Virement recu;Revenus;Salaires;;+3500,00;09/11/2024;09/11/2024;0
```
- Dates `JJ/MM/AAAA` (3 colonnes de dates : comptabilisation / opération / valeur), virgule décimale, **Débit en négatif** dans sa colonne, **Crédit préfixé `+`** — le parseur de montants doit accepter `+` et `-`.
- `Categorie`/`Sous categorie` : suggestions de catégorisation. `Libelle simplifie` = nom de marchand nettoyé, excellent pour l'affichage et les règles.
- Banque Populaire (même groupe BPCE) a des exports proches (« Documents → Vos écritures et opérations → CSV ») — le preset CE servira de base si besoin.
### 2.7 Fortuneo
- **Accès** : espace client → historique du compte → export. Formats annoncés : **CSV, Excel, QIF, OFX**, avec un historique allant jusqu'à **10 ans** (le plus généreux des banques FR).
- **Nom de fichier** : `HistoriqueOperations_{compte}_du_JJ_MM_AAAA_au_JJ_MM_AAAA.csv`.
- **Structure CSV** (fichier réel) :
- **Séparateur** : `;`. **Encodage** : historiquement ISO-8859-1/Windows-1252 (des accents cassés sont observés si lu en UTF-8) ; des exports récents semblent être en UTF-8 ⇒ **toujours passer par la détection d'encodage** (voir §7.3).
- **En-tête exact** (noter la casse et le `;` final) :
```
Date opération;Date valeur;libellé;Débit;Crédit;
```
- **Exemple** : `13/12/2019;13/12/2019;CARTE 12/12 FNAC METZ;-6,4;`
- Dates `JJ/MM/AAAA`, virgule décimale, **Débit déjà signé négatif**, montants parfois sans zéro final (`-6,4`), espaces de milliers possibles.
- **Pièges** : les opérations **carte à débit différé** sont isolées puis intégrées à la liste le dernier jour du mois (risque de « trou » puis d'apparition tardive ⇒ importance de la fenêtre de recouvrement) ; les opérations Bourse ne sont pas distinguées des crédits ordinaires.
### 2.8 Revolut
- **Accès** : app/web → Relevés (« Statements ») → export **CSV / Excel / PDF** par compte-devise et par période. Pas d'OFX/QIF. Historique complet disponible.
- **Nom de fichier** : `account-statement_{AAAA-MM-JJ}_{AAAA-MM-JJ}_{...}_{id}.csv`.
- **Structure CSV** (fichier réel) :
- **Séparateur** : `,`. **Encodage** : UTF-8. **Point décimal**.
- **En-tête exact** :
```
Type,Product,Started Date,Completed Date,Description,Amount,Fee,Currency,State,Balance
```
- **Exemple** : `TOPUP,Current,2024-01-05 14:00:40,2024-01-05 14:00:41,Payment from M Huang Mincong,10.00,0.00,USD,COMPLETED,74.43`
- Dates `AAAA-MM-JJ HH:MM:SS` (heure locale du compte).
- **Règles d'import** :
- **N'importer que `State == COMPLETED`** (les états `PENDING`/`REVERTED` changent ou disparaissent — source classique de doublons).
- `Amount` est **hors frais** ; l'impact réel sur le solde = `Amount - Fee` (Fee est positif). Deux stratégies : (a) créer une transaction unique de montant net, en notant le frais dans un champ `metadata` ; (b) créer deux transactions (opération + frais). Recommandé v1 : **montant net + note**, plus simple pour les budgets.
- Un compte Revolut = plusieurs devises ⇒ un export par devise ; modéliser un `account` LifeTrack par devise, ou stocker `currency` par transaction.
- `Type` utiles : `TOPUP`, `CARD_PAYMENT`, `TRANSFER`, `EXCHANGE`, `ATM`, `FEE` — mappables vers des catégories par défaut.
### 2.9 N26
- **Accès** : application web (app.n26.com) → téléchargement des activités (« Download activities ») par période. **CSV uniquement** (pas d'OFX/QIF).
- **Structure CSV actuelle (format 2023+)** :
- **Séparateur** : `,`. **Encodage** : UTF-8. **Point décimal**. Dates `AAAA-MM-JJ`. Champs quotés (sauf `Type`).
- **En-tête exact** :
```
"Booking Date","Value Date","Partner Name","Partner Iban",Type,"Payment Reference","Account Name","Amount (EUR)","Original Amount","Original Currency","Exchange Rate"
```
- `Partner Iban` = IBAN de la contrepartie (précieux pour les règles de catégorisation et la détection de virements internes). `Original Amount/Currency/Exchange Rate` renseignés pour les paiements hors EUR.
- **Ancien format (avant ~2023)**, à supporter en option dans le preset (des utilisateurs ont des archives) :
```
"Date","Payee","Account number","Transaction type","Payment reference","Amount (EUR)","Amount (Foreign Currency)","Type Foreign Currency","Exchange Rate"
```
- Le preset N26 doit **détecter la variante par la ligne d'en-tête**.
---
## 3. Export d'activité PayPal
### 3.1 Où et quoi
- **Accès** : paypal.com → Activité → « Télécharger » / Relevés → « Activité personnalisée » ; ou Rapports (comptes business) → « Activity Download ».
- **Formats** : **CSV**, **TAB**, PDF ; QIF (USD uniquement) et IIF (US uniquement) — ignorer QIF/IIF.
- **Limites** : historique **7 ans**, période max **12 mois par rapport**, **50 000 lignes max** par fichier (sinon ZIP multi-fichiers). Préréglages : « depuis le dernier téléchargement », mois écoulé, 3 mois, 6 mois…
- **Encodage** : UTF-8. **Séparateur** : `,` avec champs quotés.
- **Locale FR — piège majeur** : dates en `JJ/MM/AAAA` et **virgule décimale à l'intérieur des champs quotés** (ex. `"1 234,56"`). Un compte configuré en anglais exporte en `MM/DD/YYYY` avec point décimal. Le preset PayPal doit donc proposer le choix de locale (défaut FR).
### 3.2 Colonnes
Le rapport « Activity Download » est **personnalisable** (87 champs possibles). Champs **obligatoires** (toujours présents) :
```
Date, Time, TimeZone, Name, Type, Status, Currency, Gross, Fee, Net,
From Email Address, To Email Address, Transaction ID, Reference Txn ID,
Receipt ID, Balance Impact
```
Champs cochés par défaut (sélection) : `Balance`, `Subject`, `Note`, `Invoice Number`, `Country Code`, adresses, etc.
Sur les **comptes personnels**, l'export simplifié peut ne contenir qu'un sous-ensemble du type : `Date, Time, TimeZone, Name, Type, Status, Currency, Amount, Receipt ID, Balance` (une seule colonne `Amount` au lieu de Gross/Fee/Net). Le preset doit accepter **les deux variantes** (détection par en-tête).
### 3.3 Sémantique des montants et devises
- `Gross` = montant brut signé ; `Fee` = frais (négatif) ; **`Net = Gross + Fee`**.
- `Balance Impact` ∈ {`Credit`, `Debit`, `Memo`} : les lignes `Memo` (autorisations, paiements en attente, lignes informatives) **n'affectent pas le solde ⇒ les exclure de l'import**.
- `Status` : n'importer que `Completed` (exclure `Pending`, `Denied`, `Reversed`…).
- **Conversions de devises** : un achat en devise génère 23 lignes liées (`Type` contenant "Currency Conversion" / « Conversion de devise » : une ligne de débit dans la devise d'origine, une ligne de crédit en EUR, liées par `Reference Txn ID`). Stratégie v1 recommandée : **importer uniquement les lignes dont `Currency` == devise du compte LifeTrack (EUR) et `Balance Impact` != `Memo`**, ce qui capture l'effet net en euros sans doublonner.
- `Transaction ID` : identifiant alphanumérique 17 caractères, **unique et stable** ⇒ clé de déduplication idéale (`external_id`).
- `Reference Txn ID` : lie remboursements/conversions à la transaction d'origine — à stocker en métadonnée.
---
## 4. Le format OFX en France (et QIF)
### 4.1 Ce qu'on trouve réellement
- Les banques françaises qui proposent OFX (BoursoBank, BNP, La Banque Postale, Crédit Agricole selon caisse, Fortuneo…) livrent quasi toujours de l'**OFX 1.x SGML** (en-tête `OFXHEADER:100`, `DATA:OFXSGML`, `VERSION:102`), **pas** de l'OFX 2.x XML. Encodage souvent déclaré `ENCODING:USASCII`/`CHARSET:1252` mais réellement Windows-1252/Latin-1.
- Structure utile par transaction (`<STMTTRN>`) : `TRNTYPE` (DEBIT/CREDIT/XFER/…), `DTPOSTED` (`AAAAMMJJ`), `TRNAMT` (**point décimal**, signé), `FITID`, `NAME`, `MEMO` éventuel. Le bloc `<BANKACCTFROM>` donne banque/guichet/compte — parfait pour router vers le bon compte.
- **Avantages vs CSV** : pas d'ambiguïté de date/décimale, identifiant `FITID`, solde de fin (`<LEDGERBAL>`), n° de compte inclus.
### 4.2 Fiabilité du FITID — mise en garde
La spec OFX exige que le FITID identifie de façon unique une transaction **dans le périmètre d'un compte** et reste **stable entre téléchargements** (« FITIDs must be unique within the scope of […] an account » ; unicité inter-banques non garantie ⇒ clé = banque+compte+FITID). En pratique :
- **LCL (cas documenté par Akretion)** : FITID fabriqué comme `code_type + date JJMMAA + montant en centimes` (ex. `948 200423 -1275`) ⇒ **deux paiements CB du même montant le même jour = même FITID**. Violation flagrante, présente depuis au moins 2016.
- D'autres établissements (cas US documentés : Discover…) **régénèrent des FITID différents à chaque téléchargement** pour la même transaction.
**Conséquence pour LifeTrack** : traiter le FITID comme `external_id` de dédup **prioritaire mais non exclusif** — toujours doubler d'un hash de contenu, et ne jamais planter sur un FITID dupliqué à l'intérieur d'un même fichier (suffixer par un compteur d'occurrence, cf. §6.4).
### 4.3 QIF
Format texte Quicken sans identifiants, dates ambiguës (`D` au format local), pas de devise, pas de n° de compte. Fortuneo, SG, BNP, CA le proposent encore. **Ne pas l'implémenter en v1** (CSV + OFX couvrent tout) ; à garder en idée v3 si un utilisateur n'a que ça.
### 4.4 Bibliothèques Python
- **`ofxparse`** : tolérant, gère l'OFX 1.x SGML sale des banques (via BeautifulSoup/sgmllib). Maintenance faible mais c'est le standard de fait pour ce besoin. **Recommandé v1**, avec pré-traitement : détection/normalisation d'encodage avant parsing.
- **`ofxtools`** : plus strict/complet (OFX 2.x, typage), moins indulgent avec les fichiers non conformes français. Alternative si `ofxparse` pose problème.
---
## 5. Agrégation PSD2 : état des lieux 2025/2026
### 5.1 GoCardless Bank Account Data (ex-Nordigen) — en extinction
- Historiquement **LA** solution gratuite : jusqu'à **50 connexions bancaires/mois** gratuites, couverture de ~2500 banques UE dont toutes les grandes banques françaises, consentement PSD2 de 90 jours (180 pour certaines banques), jusqu'à 24 mois d'historique selon banque.
- **Limite de taux introduite en 2024 : ~4 appels de synchronisation par jour et par compte** (les importeurs comme Firefly III data importer ≥ 1.5.6 la gèrent).
- API simple : `secret_id`/`secret_key` → token ; `GET /institutions?country=fr` ; création d'une « requisition » (lien d'autorisation redirigeant vers la banque) ; puis `GET /accounts/{id}/transactions` (JSON, montants `transactionAmount.amount` + `currency`, `internalTransactionId`/`transactionId` pour la dédup).
- **⚠️ Depuis juillet 2025 : plus aucune inscription nouvelle** (« GoCardless has stopped accepting new Bank Account Data accounts », confirmé par la doc Actual Budget) ; le produit est en cours d'abandon. Les comptes existants continuent de fonctionner, sans garantie de durée.
- **Conclusion** : notre utilisateur ne pourra probablement **pas** créer de compte ⇒ GoCardless ne peut plus être le connecteur v2 par défaut ; il reste pertinent comme **connecteur optionnel** pour détenteurs de comptes historiques.
### 5.2 Enable Banking — la relève recommandée
- Agrégateur finlandais s'appuyant sur les **API PSD2 officielles** (~2500 banques, 29 pays). Utilisé comme alternative par les communautés Firefly III (tutoriel officiel `docs.firefly-iii.org/tutorials/data-importer/eb/`) et Actual Budget (guide de setup dédié).
- **Mode « restricted » gratuit, adapté à un particulier** : on enregistre une application de **production** dans le Control Panel (enablebanking.com), puis « **Activate by linking accounts** » : on autorise ses propres comptes via le portail Enable Banking + la page d'autorisation de la banque. L'application ne peut ensuite récupérer **que les comptes préalablement liés** (whitelist), tant qu'aucun contrat commercial n'est signé. C'est exactement le périmètre « mes propres comptes ».
- **Authentification API** : l'application possède une **clé privée RSA** ; chaque requête est signée par un **JWT RS256** (kid = application_id). Endpoints principaux : `POST /auth` (démarrage d'autorisation, URL de redirection), `POST /sessions` (échange du code), `GET /sessions/{id}`, `GET /accounts/{uid}/transactions` (pagination par `continuation_key`), `GET /accounts/{uid}/balances`.
- **Couverture France confirmée** (doc `enablebanking.com/docs/markets/fr/`) : BNP Paribas, **Crédit Agricole** (choix de la **caisse régionale** ; SCA via l'app « Ma Banque »), Société Générale, La Banque Postale, Crédit Mutuel, CIC, LCL, Banque Populaire, **Caisse d'Épargne**, etc. Les flux d'authentification français sont de type **redirect** avec SCA sur l'app mobile de la banque. BoursoBank/Fortuneo figurent dans le réseau PSD2 français (STET) ; **Revolut et N26** exposent aussi des API PSD2 européennes — vérifier leur présence exacte dans la liste d'ASPSP du Control Panel au moment de l'implémentation (non listés sur la page marché FR).
- **Contraintes PSD2 invariables** : consentement à renouveler (90 jours réglementaires, jusqu'à 180 selon banque), historique initial limité par la banque (souvent 90 jours à 24 mois au premier accès), chaque session doit être autorisée via API même en mode restreint.
### 5.3 Autres options commerciales (non retenues)
- **Powens** (ex-Budget Insight, adossé Crédit Mutuel Arkéa) : excellente couverture FR (y compris épargne/assurance-vie), mais **B2B, tarification sur contrat**, pas d'offre particulier.
- **Bridge** (ex-Bankin' B2B, adossé BPCE) : idem, B2B uniquement.
- **Tink** (Visa) : B2B, plus de free tier significatif pour un usage personnel.
- **SimpleFIN** : populaire chez Actual Budget mais **couvre les banques nord-américaines** — hors sujet pour la France.
### 5.4 woob (ex-weboob) — scraping open-source
- Framework Python AGPL de scraping bancaire (modules `boursorama`, `creditagricole`, `fortuneo`, etc.), moteur historique de **Kresus** (gestionnaire de finances self-hosted français). Projet actif (GitLab `woob/woob`).
- **Fragilité structurelle** : les modules cassent à chaque refonte des sites (ex. documenté : synchronisation BoursoBank cassée à l'automne 2025, `AttributeError` dans le module boursorama). Nécessite de stocker les **identifiants bancaires en clair côté serveur**, et le scraping peut déclencher des blocages / est contraire aux CGU de certaines banques.
- **Position LifeTrack** : ne pas en faire une dépendance cœur. Au mieux, un adaptateur optionnel v3 (« woob bridge » qui exporte du JSON/CSV consommé par notre pipeline d'import).
### 5.5 Ce que font Firefly III et Actual Budget (référence)
| App | Import fichiers | Sync bancaire |
|---|---|---|
| Firefly III (+ Data Importer) | CSV (mapper générique + configs communautaires JSON par banque, dépôt `firefly-iii/import-configurations` : profils `fr/boursorama`, `fr/fortuneo`…), camt.053 | GoCardless (legacy), **Enable Banking** (nouveau), SimpleFIN |
| Actual Budget | CSV/OFX/QFX/QIF/CAMT | GoCardless (legacy), SimpleFIN (US), **Enable Banking**, Pluggy.ai (Brésil) |
Enseignement : les deux références self-hosted ont pivoté **GoCardless → Enable Banking** pour l'Europe ; leur modèle « mapper générique + profils par banque en JSON versionnés » est exactement l'architecture retenue pour LifeTrack v1.
---
## 6. Stratégies de déduplication
Le besoin : l'utilisateur réimporte régulièrement des fichiers **qui se chevauchent** (historique court chez les banques FR), et pourra un jour cumuler fichier + sync API sur le même compte. Il faut être **idempotent** sans perdre de vraies transactions identiques (deux cafés à 2,50 € le même jour chez le même commerçant sont légitimes).
### 6.1 Ce que fait l'état de l'art
- **Firefly III** : deux mécanismes — (1) « content-based » : hash SHA-256 du JSON complet de la transaction soumise, comparé aux hashes existants (fragile : le hash change si la banque change la casse ou si le mapping change) ; (2) « identifier-based » : une colonne mappée sur `external_id`/`internal_reference` est recherchée avant import — « a very reliable way to detect duplicates ».
- **Actual Budget** : champ **`imported_id`** (FITID pour OFX, id GoCardless pour la sync) — « transactions with the same imported_id will never be added more than once » ; sinon **rapprochement flou** : même montant + date proche (fenêtre de quelques jours) + payee similaire ⇒ fusion proposée. Bug historique corrigé en 2024 : le fuzzy match ne doit **pas** fusionner deux transactions portant des `imported_id` différents (sauf quirk GoCardless) — règle à reprendre telle quelle.
### 6.2 Clés candidates
1. **`external_id` fourni par la source** : OFX `FITID` (voir caveats §4.2), PayPal `Transaction ID` (fiable), GoCardless `internalTransactionId` (fiable), Enable Banking `entry_reference` (fiabilité variable selon banque). Unicité à imposer **par (account_id, source_kind)**, jamais globalement.
2. **Hash de contenu** : les CSV français n'ont **aucun identifiant** ⇒ hash déterministe sur les champs stables : compte + date comptable + montant + libellé normalisé + devise.
3. **Rapprochement flou** (aide à la décision, jamais automatique en suppression) : même compte, même montant, date à ±3 jours, similarité de libellé (trigrammes `pg_trgm`) — utile parce que les banques FR **changent le libellé et la date** entre l'opération « en cours » et l'opération comptabilisée.
### 6.3 Pièges spécifiques observés
- **Libellés instables** : SG padde d'espaces ; CB « en cours » devient « CARTE 12/12 FNAC METZ » une fois comptabilisée ; Fortuneo intègre le débit différé en fin de mois. ⇒ normalisation agressive du libellé avant hash (cf. ci-dessous) et fenêtre de recouvrement.
- **Doublons légitimes intra-journée** : gérés par un **compteur d'occurrence** intégré au hash — technique éprouvée (les import-id YNAB/Actual sont suffixés d'un index d'occurrence). Dans un même fichier, la n-ième ligne strictement identique reçoit `occurrence = n`.
- **FITID dupliqué dans un même fichier OFX** (cas LCL) : appliquer le même compteur d'occurrence au FITID.
- **Balance/solde glissant** (Boursorama `accountbalance`) : ne jamais inclure de colonne de solde dans le hash.
- **Catégories fournies par la banque** (Bourso, CE) : ne pas les inclure dans le hash (elles changent au gré des algos de la banque).
### 6.4 Algorithme retenu pour LifeTrack
Pipeline d'import (fichier ou API) → table de **staging** → dédup → commit :
```text
for each parsed row:
1. Normalize: booking_date (ISO), amount (Decimal, 2 dec), label_norm, currency.
2. If source provides an external id:
ext_key = (account_id, source_kind, external_id [+ ":" + occurrence])
if exists in transactions.external_key -> mark DUPLICATE (skip)
3. Compute content hash:
basis = f"{account_id}|{booking_date}|{amount:+.2f}|{currency}|{label_norm}|{occurrence}"
dedup_hash = sha256(basis)
occurrence = index of this exact basis within the CURRENT import file (0,1,2…)
if dedup_hash exists in transactions -> mark DUPLICATE (skip)
4. Fuzzy pass (only for rows that survived 2 and 3):
candidates = same account, same amount, |date diff| <= 3 days,
similarity(label_norm) >= 0.5 (pg_trgm),
AND (candidate.external_id IS NULL OR row.external_id IS NULL)
if candidates -> mark NEEDS_REVIEW (user confirms merge/import in preview UI)
5. Else -> mark NEW
commit: user validates the preview (counts NEW / DUPLICATE / NEEDS_REVIEW), then batch insert.
```
Normalisation de libellé (`label_norm`) :
```python
import re, unicodedata
def normalize_label(raw: str) -> str:
s = unicodedata.normalize("NFKD", raw)
s = "".join(c for c in s if not unicodedata.combining(c)) # strip accents
s = s.upper()
s = re.sub(r"\s+", " ", s).strip() # collapse whitespace/newlines
return s
```
Parsing des montants français (couvre tous les cas observés : `-6,4`, `+3500,00`, `-123 456,78`, `1 234,56`, `226.68`) :
```python
from decimal import Decimal
def parse_amount(raw: str) -> Decimal:
s = raw.strip().replace(" ", "").replace("", "").replace(" ", "")
if "," in s:
s = s.replace(".", "").replace(",", ".") # French style
return Decimal(s) # accepts leading + or -
```
**Fenêtre de recouvrement** : encourager l'utilisateur (UI) à toujours exporter avec chevauchement (ex. « depuis 7 jours avant le dernier import ») ; côté serveur, la dédup rend le chevauchement inoffensif. Stocker par compte `last_imported_max_date` pour afficher « dernière opération connue : … » et suggérer la période d'export.
**Traçabilité** : chaque ligne insérée référence un `import_batch` (fichier source, profil utilisé, horodatage, checksum du fichier). Un batch est **annulable en bloc** (undo), et un même fichier (même checksum SHA-256) déjà importé est refusé d'emblée avec message clair.
---
## 7. Recommandations d'implémentation — v1 (import fichiers)
### 7.1 Périmètre v1
1. **Mapper CSV générique** avec presets par banque (BoursoBank, Crédit Agricole, BNP, SG, La Banque Postale, Caisse d'Épargne, Fortuneo, Revolut, N26, PayPal).
2. **Parseur OFX** (`ofxparse`) — couvre BoursoBank, BNP, LBP, CA, Fortuneo pour les utilisateurs qui préfèrent l'OFX.
3. **Preset PayPal** (variante business Gross/Fee/Net + variante perso Amount).
4. Saisie manuelle + import batch annulable + moteur de règles de catégorisation.
5. Pas de QIF, pas de PDF, pas de sync API en v1.
### 7.2 Modèle de données (SQLAlchemy — esquisse)
```python
class BankAccount(Base): # finance account, multi-user ready
id: UUID; user_id: UUID
name: str; kind: str # checking|savings|card|paypal|cash
currency: str = "EUR"
iban_last4: str | None
last_imported_max_date: date | None
class ImportProfile(Base): # a saved CSV mapping (preset or user-defined)
id: UUID; user_id: UUID | None # None => built-in preset
slug: str # "boursobank", "credit-agricole", ...
config: JSONB # see 7.4
class ImportBatch(Base):
id: UUID; user_id: UUID; account_id: UUID | None
profile_slug: str | None; source_kind: str # csv|ofx|paypal|api-...
filename: str; file_sha256: str # reject identical re-upload
created_at: datetime
stats: JSONB # {new, duplicates, review}
status: str # pending|committed|rolled_back
class Transaction(Base):
id: UUID; account_id: UUID
booking_date: date; value_date: date | None
amount: Numeric(14, 2); currency: str
label_raw: str; label_norm: str
counterparty_iban: str | None
category_id: UUID | None
source_kind: str # csv|ofx|paypal|api-enablebanking|manual
external_id: str | None # FITID / PayPal Transaction ID / API id
occurrence: int = 0
dedup_hash: str # sha256 hex, UNIQUE per account
import_batch_id: UUID | None
metadata: JSONB # bank category, fee, reference_txn_id, state...
__table_args__ = (
UniqueConstraint("account_id", "dedup_hash"),
Index(..., "account_id", "source_kind", "external_id", unique=True,
postgresql_where=text("external_id IS NOT NULL")),
)
```
Activer l'extension **`pg_trgm`** pour le fuzzy match (`similarity(label_norm, :candidate)`).
### 7.3 Chaîne de lecture des fichiers
1. **Encodage** : lire les premiers Ko ; si BOM UTF-8 ⇒ `utf-8-sig` ; sinon tenter `utf-8` strict ; en cas d'échec, `charset-normalizer` avec repli forcé `cp1252` (couvre ISO-8859-1/15 pour nos banques). Ne jamais faire confiance à l'extension.
2. **Séparateur** : `csv.Sniffer` sur un échantillon, restreint à `;`, `,`, `\t` ; heuristique de départage : compter les occurrences hors guillemets sur les 5 premières lignes.
3. **Préambule** : trois stratégies configurables par profil : `header_rows: N` (fixe) ; `skip_until_header_startswith: "Date;"` (CA, LBP) ; `has_column_header: false` + mapping positionnel (BNP). Toujours afficher un **aperçu brut** des 20 premières lignes dans l'UI pour que l'utilisateur ajuste.
4. **Champs multi-lignes** : parser avec le module `csv` sur le flux complet (jamais de `split("\n")` préalable) — requis pour CA et SG.
5. **Fin de données** : option `stop_on_non_date_row` (CA : pied de page après lignes vides).
6. **Dates** : essai ordonné des formats candidats du profil (`%d/%m/%Y`, `%Y-%m-%d`, `%Y-%m-%d %H:%M:%S`, `%d.%m.%Y`) ; en mode générique, auto-détection sur l'échantillon avec désambiguïsation JJ/MM par la présence de valeurs > 12.
7. **Montants** : `parse_amount()` de §6.4 ; modes `signed_column`, `debit_credit_columns` (CA, CE, Fortuneo), `invert_sign` (option).
### 7.4 Schéma JSON d'un profil d'import (`ImportProfile.config`)
```json
{
"file": {
"encoding": "auto",
"delimiter": ";",
"quotechar": "\"",
"header_rows": 0,
"skip_until_header_startswith": null,
"has_column_header": true,
"stop_on_non_date_row": false,
"filename_regex": "export-operations-.*\\.csv"
},
"columns": {
"booking_date": "dateOp",
"value_date": "dateVal",
"label": "label",
"amount": "amount",
"debit": null,
"credit": null,
"currency": null,
"external_id": null,
"account_hint": "accountNum",
"bank_category": ["categoryParent", "category"],
"counterparty_iban": null
},
"parsing": {
"date_formats": ["%Y-%m-%d"],
"decimal_comma": true,
"invert_sign": false,
"row_filters": [{"column": "State", "op": "equals", "value": "COMPLETED"}]
},
"dedup": {"external_id_is_reliable": false}
}
```
Les presets sont livrés dans le repo (`api/app/finance/presets/*.json`), versionnés, et **clonables** par l'utilisateur pour ajustement (les banques changent leurs formats sans préavis — c'est certain à moyen terme). La détection automatique du preset combine `filename_regex` et **signature d'en-tête** (liste de noms de colonnes attendus, comparaison insensible à la casse/accents).
### 7.5 Contenu initial des presets (récapitulatif opérationnel)
| Preset | file | columns (essentiel) |
|---|---|---|
| `boursobank` | `;`, UTF-8, header ligne 1 | `dateOp`→booking, `dateVal`→value, `label`, `amount` (virgule), `accountNum`→routage multi-comptes, `category*`→suggestion ; ignorer `accountbalance` |
| `credit-agricole` | `;`, cp1252, `skip_until_header_startswith: "Date;"`, stop_on_non_date_row | `Date`, `Date valeur`, `Libellé` (multi-lignes), `Débit Euros`/`Crédit Euros` |
| `bnp-paribas` | `;`, latin-1, `header_rows: 1`, `has_column_header: false` | positions : 0=date, 3=label, 4=amount ; ligne 1 = solde (double `html.unescape`) |
| `societe-generale` | `;`, latin-1, `header_rows: 1` | `date_comptabilisation`, `libellé_complet_operation` (trim), `montant_operation`, `devise` |
| `banque-postale` | `;`, latin-9, `skip_until_header_startswith: "Date;"` | `Date`, `Libellé`, `Montant(EUROS)` ; ignorer `Montant(FRANCS)` ; préambule → solde affichable |
| `caisse-epargne` | `;`, latin-1, header ligne 1 | `Date operation`→booking, `Date de valeur`→value, `Libelle operation`→label (+`Libelle simplifie` en metadata), `Debit`/`Credit` (accepter `+`), `Categorie`/`Sous categorie`→suggestion |
| `fortuneo` | `;`, encodage **auto**, header ligne 1 (`;` final ⇒ colonne fantôme à ignorer) | `Date opération`, `Date valeur`, `libellé`, `Débit`/`Crédit` |
| `revolut` | `,`, UTF-8, point décimal | `Completed Date`→booking, `Description`, `Amount`+`Fee`→net, `Currency`, filtre `State == COMPLETED` |
| `n26` | `,`, UTF-8, point décimal, 2 variantes d'en-tête | `Booking Date`/`Value Date`, `Partner Name`+`Payment Reference`→label, `Partner Iban`, `Amount (EUR)` |
| `paypal` | `,`, UTF-8, locale FR (date `JJ/MM/AAAA`, virgule) | `Date`+`Time`, `Name`+`Type`→label, `Net` (ou `Amount`), `Currency`, `Transaction ID`→external_id (fiable), filtres `Status == Completed` et `Balance Impact != Memo` et `Currency == EUR` |
### 7.6 Import OFX
- Endpoint d'upload commun ; si le contenu commence par `OFXHEADER` ou `<?xml` + `<OFX>`, router vers le parseur OFX.
- Pré-traitement : décoder selon §7.3 puis passer à `ofxparse.OfxParser.parse()`.
- Mapper : `stmt.account.account_id`/`routing_number` → proposition de compte LifeTrack ; par transaction : `date`→booking_date, `amount` (Decimal, point), `payee`+`memo`→label, `id` (FITID)→external_id avec `external_id_is_reliable: false` (⇒ le hash de contenu reste co-vérifié) et compteur d'occurrence en cas de FITID dupliqué dans le fichier (cas LCL).
- Exploiter `<LEDGERBAL>` pour proposer un rapprochement de solde après import.
### 7.7 UI d'import (rappel UX, libellés FR)
Assistant en 4 étapes : **1. Fichier** (drag & drop, détection preset, choix du compte) → **2. Réglages** (aperçu brut, mapping colonnes éditable, formats) → **3. Prévisualisation** (tableau : Nouvelles / Doublons ignorés / À vérifier, avec diff pour les fuzzy matches) → **4. Confirmation** (stats du batch, bouton « Annuler cet import » disponible ensuite dans l'historique des imports).
---
## 8. Recommandations d'implémentation — v2 (synchronisation automatique)
### 8.1 Choix du fournisseur : Enable Banking (et pourquoi pas GoCardless)
- **GoCardless BAD est fermé aux nouveaux comptes depuis juillet 2025** (§5.1) ⇒ inutilisable pour un nouvel utilisateur. Garder un connecteur optionnel « legacy » n'est justifié que si peu coûteux (l'API est simple) ; le marquer *deprecated* dès le départ.
- **Enable Banking** coche toutes les cases pour LifeTrack : API PSD2 officielles, **gratuit en mode restreint sur ses propres comptes** (whitelist via « Activate by linking accounts »), couverture des banques du user (CA par caisse régionale, BNP, SG, LBP, CE…), déjà éprouvé par Firefly III et Actual Budget. Prérequis utilisateur : créer un compte enablebanking.com, une application de production, télécharger la **clé privée**, lier ses comptes dans le portail.
### 8.2 Architecture du connecteur (framework « connector »)
```python
class FinanceConnector(Protocol): # in the shared connector framework
slug: str
async def list_accounts(self) -> list[RemoteAccount]: ...
async def fetch_transactions(self, remote_account_id: str,
date_from: date) -> AsyncIterator[RawTransaction]: ...
async def fetch_balance(self, remote_account_id: str) -> Balance: ...
async def authorization_status(self) -> AuthStatus # consent expiry etc.
```
- **Config stockée chiffrée** (table `connector_credentials`) : `application_id`, clé privée PEM (chiffrée au repos avec la clé applicative), mapping `remote_account_uid → bank_account_id`.
- **Auth Enable Banking** : générer un JWT RS256 par requête (`iss`/`aud` selon doc, `kid = application_id`, exp courte). Flux de consentement : `POST /auth` → URL de redirection bancaire (ouvrir dans le navigateur, callback vers l'UI LifeTrack) → `POST /sessions` → stocker `session_id` + échéance de consentement.
- **Sync** : job planifié (APScheduler dans l'API) 12×/jour + bouton « Synchroniser maintenant ». `fetch_transactions(date_from = last_synced_date - 7 jours)` (fenêtre de recouvrement), pagination `continuation_key`, puis injection dans **le même pipeline staging + dédup** que les imports fichiers (`source_kind = "api-enablebanking"`, `external_id = entry_reference` si présent, hash de contenu sinon). Les statuts `PDNG` (pending) sont ignorés ou marqués provisoires ; n'entériner que `BOOK` (booked).
- **Gestion du consentement (UX critique)** : bannière « Consentement expire le JJ/MM » + relance guidée du flux d'autorisation (échéance ~90/180 jours selon banque). Prévoir l'état « connecteur en erreur d'auth » visible sur le dashboard.
- **Multi-source sur un même compte** (fichier + API) : la dédup §6.4 est l'unique garde-fou — raison de plus pour ne jamais court-circuiter le pipeline commun.
### 8.3 Hors périmètre v2 (documenté pour v3+)
- Adaptateur **woob** optionnel (fragile, credentials en clair — §5.4).
- Import **QIF** et relevés **PDF** (OCR/extraction — projets type `LBPExtract` montrent la faisabilité pour LBP).
- **camt.053** (XML ISO 20022) si un jour utile (Firefly l'accepte ; peu diffusé côté particuliers FR).
---
## 9. Sources
Formats bancaires :
- [ScanCompte — Exporter son relevé BoursoBank (CSV/PDF/OFX)](https://www.scancompte.com/banques/exporter-releve-boursorama) ; [What In My Pocket — Export BoursoBank](https://whatinmypocket.com/guides/exporter-releve-boursobank/)
- [mincong-h/finance-toolkit](https://github.com/mincong-h/finance-toolkit) — parseurs + fichiers d'exemple réels BNP / Boursorama / Fortuneo / Revolut / Caisse d'Épargne (en-têtes cités §2), docs `docs/boursorama.md`, `docs/bnp.md`, `docs/caisse-epargne.md`
- [OpenFlyers — Modèle CSV Crédit Agricole](https://doc4-fr.openflyers.com/Mod%C3%A8le-d'import-de-relev%C3%A9-bancaire-CSV-Cr%C3%A9dit-Agricole-avec-point-virgule) ; [Modèle CSV Banque Postale](https://doc4-fr.openflyers.com/Mod%C3%A8le-d'import-de-relev%C3%A9-bancaire-CSV-Banque-Postale-avec-point-virgule) ; [Modèle CSV Société Générale](https://doc4-fr.openflyers.com/Mod%C3%A8le-d'import-de-relev%C3%A9-bancaire-CSV-Soci%C3%A9t%C3%A9-G%C3%A9n%C3%A9rale-avec-point-virgule) ; [page générale exports banques](https://doc4-fr.openflyers.com/Exporter-un-relev%C3%A9-bancaire-depuis-un-site-internet-de-banque)
- [enodev.fr — Fin du QIF à la Société Générale (structure CSV réelle)](https://enodev.fr/posts/fin-du-qif-a-la-societe-generale.html)
- [ofxpress — BNP Paribas](https://ofxpress.fr/telecharger-votre-releve-bancaire-bnp-paribas/), [La Banque Postale](https://ofxpress.fr/telecharger-votre-releve-bancaire-la-banque-postale/), [Crédit Agricole](https://ofxpress.fr/telecharger-votre-releve-bancaire-credit-agricole/)
- [Aide Caisse d'Épargne — Comment exporter mes opérations](https://www.aide.caisse-epargne.fr/contents/comment-exporter-mes-operations)
- [statementsheet — Fortuneo (formats, 10 ans)](https://statementsheet.com/how-to-convert-fortuneo-bank-statement-to-excel-csv/) ; [kdecherf — Fortuneo + woob](https://kdecherf.com/blog/2022/11/13/importer-des-transactions-fortuneo-dans-homebank-avec-woob/)
- [Lido — comparatif exports banques FR](https://www.lido.app/fr/releve-bancaire-excel) ; [MoneyVox — profondeur d'historique CSV/OFX par banque](https://www.moneyvox.fr/forums/fil/maximum-historique-des-telechargements-csv-ofx-chez-votre-banque.35927/)
- N26 : [dekodi — colonnes CSV N26](https://manuals.dekodi.de/nexuspub/datenbereitstellungsbuch/n26.html), [KontoCSV N26](https://www.kontocsv.de/en/n26)
- Revolut/N26 (bank2ynab, formats confirmés) : [bank2ynab.conf](https://github.com/bank2ynab/bank2ynab/blob/develop/bank2ynab/data/bank2ynab.conf)
PayPal :
- [PayPal Developer — Activity Download report (champs, formats, limites)](https://developer.paypal.com/docs/reports/online-reports/activity-download/) ; [PDF spec PP_ActivityDownload](https://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/PP_ActivityDownload.pdf)
- [Putler — export PayPal 2025](https://www.putler.com/export-paypal-transactions/) ; [KontoCSV — PayPal CSV](https://www.kontocsv.de/en/guides/paypal-transactions-csv)
OFX / dédup :
- [Akretion — LCL ne respecte pas la norme OFX (FITID non uniques)](https://akretion.com/fr/blog/lcl-ne-respecte-pas-la-norme-ofx)
- [Quinthar — OFX FITIDs: Not as permanent as you might think](http://blog.quinthar.com/2008/12/ofx-fitids-not-as-permanent-as-you.html) ; [OFX spec (OpenExchange, unicité par compte)](https://xml.coverpages.org/OFEXFIN1.html)
- [Firefly III — Duplicate detection (référence)](https://docs.firefly-iii.org/references/data-importer/duplicate-detection/) (source : dépôt `firefly-iii/docs`)
- [Actual Budget — Importing transactions (imported_id + fuzzy)](https://actualbudget.org/docs/transactions/importing/) ; [PR #2991 — fuzzy match vs imported_id](https://github.com/actualbudget/actual/pull/2991)
PSD2 / agrégation :
- [Actual Budget — GoCardless setup (« stopped accepting new accounts » juillet 2025, 50 connexions, 4 syncs/jour)](https://actualbudget.org/docs/advanced/bank-sync/gocardless/)
- [OpenBankingTracker — Free & Indie Open Banking APIs 2026](https://www.openbankingtracker.com/guides/free-open-banking-apis)
- [Firefly III — tutoriel Enable Banking](https://docs.firefly-iii.org/tutorials/data-importer/eb/) ; [issue #10753 — Enable Banking comme alternative à GoCardless](https://github.com/firefly-iii/firefly-iii/issues/10753)
- [Enable Banking — Linked accounts / restricted mode](https://enablebanking.com/docs/api/linked-accounts/) ; [Enable Banking — marché France](https://enablebanking.com/docs/markets/fr/) ; [FAQ](https://enablebanking.com/docs/faq/)
- [Powens](https://www.powens.com/fr/plateforme/) ; [Bridge (openfinanceguide)](https://openfinanceguide.com/en/glossary/bridge) ; [BPI — mapping open banking FR 2025](https://bigmedia.bpifrance.fr/nos-actualites/mapping-2025-des-acteurs-francais-de-lopen-banking)
- [woob (GitLab)](https://gitlab.com/woob/woob) ; [issue #803 — sync BoursoBank cassée](https://gitlab.com/woob/woob/-/issues/803)
- [Firefly III — import-configurations communautaires (profils fr/boursorama, fr/fortuneo)](https://github.com/firefly-iii/import-configurations)