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>
This commit is contained in:
@@ -0,0 +1,590 @@
|
||||
# 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;`) | ~30–90 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édit immobilier";"Cr&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 2–3 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) 1–2×/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)
|
||||
Reference in New Issue
Block a user