diff --git a/spec-decisions-refonte-seed-categories-ipc.md b/spec-decisions-refonte-seed-categories-ipc.md new file mode 100644 index 0000000..2653721 --- /dev/null +++ b/spec-decisions-refonte-seed-categories-ipc.md @@ -0,0 +1,81 @@ +# Spec Decisions — Refonte du seed de catégories vers IPC Statistique Canada + +> Date: 2026-04-19 +> Projet: simpl-resultat +> Statut: Draft +> Slug: refonte-seed-categories-ipc + +## Contexte + +Le seed actuel (migration v2, `src-tauri/src/database/seed_categories.sql`) comprend 42 catégories sur 2 niveaux, structurées autour d'un axe cosmétique "récurrentes vs ponctuelles" sans logique métier derrière. Cette organisation : + +- Ne reflète aucun référentiel comptable standard (pas d'alignement possible avec Statistique Canada, CPA, ou benchmarks internationaux). +- Exploite seulement 2 des 3 niveaux de hiérarchie supportés par le code (`src/services/categoryService.ts:55-60`). +- Contient des catégories "fourre-tout" (Amazon, Projets) qui cassent la cohérence comptable. +- Propose des mots-clés limités aux fournisseurs rencontrés dans un jeu de données historique personnel. + +La refonte vise à s'aligner sur la **classification IPC de Statistique Canada (panier 2024)** qui regroupe les dépenses des ménages en 8 composantes principales officielles, étendues de granularité fine inspirée de Monarch Money (~60 catégories standard, 3 niveaux). + +Le spike `seed-standard` (archivé sous `~/claude-code/.spikes/archived/seed-standard/`) a livré 4 documents préparatoires : +- `NOTES.md` — synthèse et décisions +- `code/seed-proposal-v1.sql` — taxonomie v1 (139 catégories, 150+ keywords) +- `code/mapping-old-to-new.md` — table de correspondance v2→v1 +- `code/preview-page-mockup.md` — wireframes 3-étapes + +**Bénéficiaires** : tous les utilisateurs actuels et futurs de Simpl'Résultat — en particulier les ménages québécois/canadiens qui pourront éventuellement comparer leurs dépenses aux moyennes Statistique Canada. + +## Objectif + +Remplacer le seed de catégories par une taxonomie alignée IPC/Monarch (3 niveaux, 9 racines IPC + Revenus/Finances/Transferts, ~140 catégories, 150+ keywords fournisseurs canadiens), livrer une page de prévisualisation read-only (Livraison 1), puis une page de migration interactive à 3 étapes avec backup obligatoire via SREF (Livraison 2). Les profils existants migrent en opt-in conscient ; les nouveaux profils reçoivent v1 automatiquement. + +## Scope + +### IN +- Nouveau fichier `consolidated_schema.sql` avec seed v1 IPC appliqué aux **nouveaux profils** automatiquement. +- Livraison 1 (Option E) : page read-only `/paramètres/catégories/standard` — arbre navigable de la taxonomie v1 avec descriptions, compteurs, export PDF. Aucune modification de données. +- Livraison 2 (Option B) : page de migration 3 étapes (Découvrir / Simuler / Consentir) pour les profils existants. + - Étape Simuler : dry-run complet, calcul des mappings automatiques avec badges de confiance, choix manuel pour cas ambigus. + - Étape Consentir : backup SREF obligatoire (format AES-256-GCM existant) + vérification d'intégrité AVANT toute écriture destructive + migration SQL atomique. +- Algorithme de ventillage en 4 passes (keyword → supplier → défaut → revue utilisateur) pour catégories splittées (Transport commun → Bus/Train, Assurances → 4 branches). +- Préservation des catégories custom utilisateur sous un parent "Catégories personnalisées (migration)" créé automatiquement. +- Bannière dashboard one-shot post-MAJ + entrée permanente dans Paramètres pour inviter à découvrir E et potentiellement B. +- Bouton "Rétablir la sauvegarde" accessible pendant 90 jours post-migration. +- i18n des noms de catégories seed via clés techniques (ex: `seed.categories.alimentation.epicerie`) avec fallback au nom brut pour les catégories custom. +- Couverture de tests complète : unitaires (algo mapping), intégration (flow backup→migrate→rollback), régression (transactions/budgets/keywords post-migration), QA manuelle. + +### OUT (explicitement exclu) +- Attribut `transactions.frequency` (recurring/one_shot/unknown) et détection auto → issue séparée future. +- Attribut `categories.essentiality` (essential/discretionary/savings) et rapport 50/30/20 → issue séparée future. +- Peuplement de la colonne `categories.icon` (reste NULL, décision reportée à une future issue UI). +- Suppression de la colonne `categories.icon` (non nécessaire, hors scope). +- Comparaisons avec les moyennes Statistique Canada (infrastructure nécessaire pour télécharger des datasets IPC, analyser, afficher — hors scope v1). +- Migration automatique silencieuse des profils existants (principe : consentement explicite obligatoire). +- Suppression physique du fichier SREF backup (privacy-first : l'app ne gère pas les fichiers de l'utilisateur au-delà de la création). +- Traduction EN des catégories custom créées par l'utilisateur (restent dans la langue saisie). +- Version mobile / Simpl'Résultat Web (hors stack du projet aujourd'hui). + +## Decisions prises + +| Question | Décision | Raison | +|----------|----------|--------| +| Ordre de livraison (E read-only, B migration, ou fusion) | **E puis B en deux livraisons** | Permet de collecter du feedback sur la taxonomie via E avant d'investir dans l'UX migration B. Deux PRs testables indépendamment. | +| i18n des noms de catégories | **Clés i18n pour seed + noms libres pour custom** | Préserve l'expérience bilingue sans table de traduction BDD. Renderer : clé i18n si elle existe, sinon nom brut. | +| Nouveaux profils après MAJ | **v1 automatique** | Pas de friction pour un nouvel utilisateur sans historique. v1 devient le nouveau standard. | +| Rétention bannière "Rétablir la sauvegarde" | **90 jours** | Laisse le temps à plusieurs cycles mensuels pour détecter un problème. Le fichier SREF lui-même n'est jamais supprimé par l'app. | +| Granularité L3 (~90 feuilles) | **Toutes actives par défaut** | Simplicité, prévisibilité. L'utilisateur peut désactiver via `is_active=0` depuis les paramètres. Overhead négligeable (tree virtualisable). | +| Invitation des profils existants | **Bannière dashboard one-shot + entrée permanente dans Paramètres** | Équilibre entre découvrabilité et respect de l'utilisateur. Ni silent (adoption lente), ni modale bloquante (intrusif). | +| Catégories custom pendant la migration | **Préservées sous parent "Catégories personnalisées (migration)"** | Aucune perte de données ou de règles custom. L'utilisateur déplace à son rythme. Friction minimale à la migration. | +| Colonne `categories.icon` | **Garder NULL, décision reportée** | Ne pas élargir le scope. Issue UI séparée décidera du renderer (emojis / lucide-react / SVG). | +| Tests | **Unitaires + intégration + régression + QA manuelle** | Feature destructive sur données utilisateur → couverture complète justifiée. Algo ventillage = unitaires, migration end-to-end = intégration, non-régression = régression. | + +## References + +| Source | Pertinence | +|--------|------------| +| [IPC Statistique Canada — panier 2024](https://www150.statcan.gc.ca/n1/pub/62f0014m/62f0014m2025003-fra.htm) | Les 8 composantes principales officielles structurent les racines L1 v1. Référence "quasi-PCGR" pour les dépenses des ménages au Canada. | +| [Statistique Canada — Enquête sur les dépenses des ménages (EDM)](https://www23.statcan.gc.ca/imdb/p2SV_f.pl?Function=getSurvey&SDDS=3508) | Fournit la granularité L2/L3 avec >650 codes de classification détaillés. | +| [Monarch Money — Default Categories](https://help.monarch.com/hc/en-us/articles/360048883851-Default-Categories) | Benchmark 3 niveaux, ~60 catégories par défaut. Inspiration pour la granularité fine (ex: Food & Dining → Groceries, Restaurants, Coffee). | +| Spike `seed-standard` (archivé) | Analyse complète du modèle de données, taxonomie v1 draftée, mapping v2→v1 à 88% automatisable, wireframes 3-étapes. Input direct pour Phase 3b. | +| `src-tauri/src/commands/export_import_commands.rs:8-49` | Format SREF v0.1 + AES-256-GCM + Argon2id réutilisable tel quel pour le backup pre-migration. | +| `src/services/dataExportService.ts:7-10, 199` | Mode `transactions_with_categories` + `importCategoriesOnly()` pour le flow backup/restore. | +| `src/services/categoryService.ts:55-60, 68-74, 175-186` | Règles `is_inputable` auto-gérées + limite 3 niveaux enforced. Le nouveau seed doit respecter ces invariants. | diff --git a/spec-plan-refonte-seed-categories-ipc.md b/spec-plan-refonte-seed-categories-ipc.md new file mode 100644 index 0000000..595fa94 --- /dev/null +++ b/spec-plan-refonte-seed-categories-ipc.md @@ -0,0 +1,375 @@ +# Spec Plan — Refonte du seed de catégories vers IPC Statistique Canada + +> Date: 2026-04-19 +> Projet: simpl-resultat +> Statut: Draft +> Slug: refonte-seed-categories-ipc +> Decisions: [spec-decisions-refonte-seed-categories-ipc.md](./spec-decisions-refonte-seed-categories-ipc.md) + +## Design + +### UX / Interface + +#### Livraison 1 — Découverte (Option E) + +**Bannière dashboard** (première ouverture post-MAJ) +- Position : haut du dashboard, dismissable +- Texte : "Découvrez la nouvelle structure standard des catégories — inspirée de Statistique Canada" +- CTA : *Voir le guide* → navigue vers `/paramètres/categories/standard` +- Une fois dismiss, ne réapparaît plus (flag `user_preferences.categories_v1_banner_dismissed = true`) + +**Page `/paramètres/categories/standard`** (lecture seule) +- Bloc pédagogique en haut : pourquoi cette structure, lien vers source Statistique Canada +- Arbre navigable avec expand/collapse des branches +- Hover sur catégorie : tooltip avec description + exemples de fournisseurs (ex: "Metro, IGA, Maxi, Loblaws...") +- Compteur global : "9 catégories racines, ~40 sous-catégories, ~90 feuilles" +- Bouton recherche full-text +- Bouton export PDF +- **Aucune action destructive** — lecture pure + +**Entrée Paramètres** +- Dans `Paramètres > Gestion des catégories`, ajouter lien *Explorer la structure standard* + +#### Livraison 2 — Migration (Option B) + +Page 3 étapes séquentielles à `/paramètres/categories/migrer` : + +**Étape 1 — Découvrir** (reprend la page de Livraison 1 en lecture) +- CTA : *Continuer vers l'aperçu de migration* + +**Étape 2 — Simuler** (dry-run) +- Résumé impact : X catégories, Y transactions, Z règles, W budgets +- Table 3 colonnes : *Actuelle* | *Correspondance* | *v1 proposée* + - Badges confiance 🟢/🟡/🟠/🔴 + - Panneau latéral cliquable par ligne : liste des transactions affectées + possibilité de changer la cible +- Compteur "N décisions à prendre" + bouton suivant désactivé tant que toutes les 🟠 ne sont pas résolues +- Les choix sont persistés en mémoire (pas encore BDD) + +**Étape 3 — Consentir** +- Checklist explicite : "Je comprends que cette opération modifie mes catégories / Une sauvegarde sera créée avant tout changement / Je peux rétablir à tout moment" +- Bouton *Créer la sauvegarde et migrer* désactivé tant que checklist non cochée +- Pendant exécution : loader avec 4 étapes affichées (backup créé → vérifié → migration SQL → commit) +- Écran succès avec chemin du fichier SREF + CTA *Aller au tableau de bord* +- Écran échec backup : abort complet, aucune écriture, message clair + options (changer dossier / réessayer / annuler) + +**Bannière post-migration** (Paramètres > Catégories, 90 jours) +- "Migration appliquée le . Sauvegarde : " +- Bouton *Rétablir la sauvegarde* → modale confirmation → `importFullProfile()` en mode replace +- Bouton *Ne plus afficher* → flag dans `user_preferences.categories_migration_banner_dismissed` + +### Données + +#### Nouvelle migration SQL v8 (additive) +Nom : `v8__category_schema_version.sql` + +Ajoute : +- Colonne `categories.i18n_key TEXT NULL` — clé i18n technique pour les catégories seedées (ex: `seed.categories.alimentation.epicerie`). NULL pour les catégories custom → le renderer utilise `name` brut. +- Entrée dans `user_preferences` : `categories_schema_version` = `'v1'` ou `'v2'` (détermine quelle taxonomie le profil utilise). + +**Important** : cette migration **n'écrase pas** le seed v2 des profils existants. Elle ajoute juste la colonne `i18n_key` (NULL par défaut) et pose `categories_schema_version='v2'` pour tous les profils existants. + +#### `consolidated_schema.sql` mis à jour +- Contient le seed v1 complet (issu de `spike-archived/seed-standard/code/seed-proposal-v1.sql`) +- Pose `categories_schema_version='v1'` par défaut +- Les i18n_key sont peuplées au seed + +#### Clés i18n ajoutées +Dans `src/i18n/locales/fr.json` et `en.json`, nouveau namespace `categoriesSeed` : + +```json +{ + "categoriesSeed": { + "revenus": { "root": "Revenus", "emploi": { "root": "Emploi", "paie": "Paie régulière", ... } }, + "alimentation": { "root": "Alimentation", "epicerie": { "root": "Épicerie & marché", "reguliere": "Épicerie régulière", ... } }, + ... + } +} +``` + +Les `i18n_key` dans la BDD pointent vers ces clés : ex `categoriesSeed.alimentation.epicerie.reguliere`. + +#### Rien de neuf en schéma v2 : pas de colonnes `frequency` ni `essentiality` +Ces attributs sont hors scope (cf. spec-decisions). + +### Architecture + +#### Composants React (nouveaux) + +| Fichier | Rôle | +|---------|------| +| `src/pages/CategoriesStandardGuidePage.tsx` | Page read-only de Livraison 1 | +| `src/pages/CategoriesMigrationPage.tsx` | Page 3-étapes de Livraison 2 (routeur interne par étape) | +| `src/components/categories-migration/StepDiscover.tsx` | Étape 1 | +| `src/components/categories-migration/StepSimulate.tsx` | Étape 2 avec table 3 colonnes | +| `src/components/categories-migration/StepConsent.tsx` | Étape 3 avec checklist et loader | +| `src/components/categories-migration/MappingRow.tsx` | Ligne de table avec badge confiance + panneau latéral | +| `src/components/categories-migration/TransactionPreviewPanel.tsx` | Panneau latéral montrant transactions impactées | +| `src/components/dashboard/CategoriesV1DiscoveryBanner.tsx` | Bannière dashboard one-shot | +| `src/components/settings/CategoriesMigrationBackupBanner.tsx` | Bannière post-migration (90j) | + +#### Services (nouveaux) + +| Fichier | Rôle | +|---------|------| +| `src/services/categoryTaxonomyService.ts` | Source de vérité de la taxonomie v1 (structure hardcodée en TS, utilisée par StepDiscover + StepSimulate pour afficher l'arbre cible). Import depuis JSON bundle. | +| `src/services/categoryMappingService.ts` | Calcule le mapping v2→v1 avec badge de confiance. Implémente l'algo 4-passes (keyword → supplier → défaut → revue). Retourne une structure `MigrationPlan` en mémoire, sans écriture BDD. | +| `src/services/categoryMigrationService.ts` | Orchestre la migration : prend un `MigrationPlan` + `BackupResult` validé, exécute la transaction SQL atomique (INSERT catégories v1 → UPDATE transactions/budgets/keywords → DELETE catégories v2 non mappées → création parent "Catégories personnalisées (migration)" si custom détectées). | +| `src/services/categoryBackupService.ts` | Wrapper autour de `dataExportService` pour le flow pre-migration : crée un fichier SREF nommé `_avant-migration-.sref` dans `~/Documents/Simpl-Resultat/backups/`, vérifie l'intégrité (read-back + checksum), retourne `BackupResult` ou lève une erreur claire. | + +#### Hooks (nouveaux) + +| Fichier | Rôle | +|---------|------| +| `src/hooks/useCategoryTaxonomy.ts` | Charge la taxonomie v1 depuis le service (useMemo). | +| `src/hooks/useCategoryMigration.ts` | useReducer pour l'état de la page de migration (étape courante, mapping plan, backup result, erreurs). | + +#### Fichier JSON de taxonomie +`src/data/categoryTaxonomyV1.json` — structure hiérarchique de la taxonomie v1 utilisée par `categoryTaxonomyService.ts`. Régénéré depuis `spec-plan-*/code/seed-proposal-v1.sql` (source de vérité = le SQL, le JSON est dérivé pour l'UI). + +#### Flow d'intégration + +``` +Utilisateur clique "Créer la sauvegarde et migrer" + ↓ +useCategoryMigration dispatches START_MIGRATION + ↓ +categoryBackupService.createAndVerify() + → Tauri: pick_save_file + write_export_file + read_import_file + → Si échec : dispatch BACKUP_FAILED, abort, aucune écriture BDD + → Si succès : dispatch BACKUP_VERIFIED, retourne BackupResult + ↓ +categoryMigrationService.applyMigration(plan, backup) + → BEGIN TRANSACTION + → INSERT catégories v1 (IDs 1000+, i18n_key peuplées, is_inputable calculé) + → UPDATE transactions SET category_id = + → UPDATE budgets SET category_id = + → UPDATE keywords SET category_id = + → Si custom détectées : INSERT parent "Catégories personnalisées (migration)" + re-parent + → DELETE FROM categories WHERE id IN () + → UPDATE user_preferences SET value='v1' WHERE key='categories_schema_version' + → INSERT INTO user_preferences (key='last_categories_migration', value=) + → COMMIT + → Si erreur : ROLLBACK, backup reste disponible pour rétablissement + ↓ +dispatch MIGRATION_SUCCESS, affiche écran succès +``` + +## Plan de travail + +### Issue 1 — Seed v1 + i18n keys pour nouveaux profils [type:task] +Dépendances : aucune +- [ ] Ajouter migration SQL v8 : colonne `categories.i18n_key TEXT NULL` + `user_preferences('categories_schema_version', 'v2')` pour profils existants +- [ ] Mettre à jour `consolidated_schema.sql` avec le seed v1 complet (issu de `spike-archived/seed-standard/code/seed-proposal-v1.sql`) et poser `categories_schema_version='v1'` par défaut +- [ ] Créer `src/data/categoryTaxonomyV1.json` dérivé du SQL seed v1 +- [ ] Ajouter les clés i18n FR et EN dans `src/i18n/locales/{fr,en}.json` sous `categoriesSeed.*` +- [ ] Adapter le renderer CategoryTree/CategoryCombobox pour utiliser `i18n_key` si présent, fallback sur `name` +- [ ] Tests : création d'un nouveau profil → vérifier que le seed v1 est appliqué, que `categories_schema_version='v1'`, et que les noms s'affichent traduits FR/EN + +### Issue 2 — Service categoryTaxonomyService (source taxonomie v1 en TS) [type:task] +Dépendances : Issue 1 +- [ ] Créer `src/services/categoryTaxonomyService.ts` avec `getTaxonomyV1()` retournant l'arbre typé depuis le JSON +- [ ] Créer `src/hooks/useCategoryTaxonomy.ts` +- [ ] Exposer des helpers : `findByPath(path)`, `getLeaves()`, `getParentById(id)` + +### Issue 3 — Page "Guide des catégories standard" (Livraison 1) [type:feature] +Dépendances : Issue 2 +- [ ] Créer route `/paramètres/categories/standard` dans `src/App.tsx` +- [ ] Créer `src/pages/CategoriesStandardGuidePage.tsx` +- [ ] Implémenter l'arbre navigable avec expand/collapse, tooltips, compteur global +- [ ] Bouton recherche full-text +- [ ] Bouton export PDF (via `window.print()` avec feuille style dédiée, ou lib PDF léger) +- [ ] Ajouter lien dans `src/components/settings/CategoriesCard.tsx` (ou équivalent) + +### Issue 4 — Bannière dashboard one-shot + découverte [type:feature] +Dépendances : Issue 3 +- [ ] Créer `src/components/dashboard/CategoriesV1DiscoveryBanner.tsx` +- [ ] Ajouter flag `categories_v1_banner_dismissed` dans `user_preferences` +- [ ] Intégrer au `Dashboard.tsx` : affichée si `categories_schema_version='v2'` AND flag non-dismiss +- [ ] CTA dismissable vers la page Guide + +### Issue 5 — Service categoryMappingService (algo ventillage 4 passes) [type:task] +Dépendances : Issue 2 +- [ ] Créer `src/services/categoryMappingService.ts` +- [ ] Implémenter l'algo 4 passes (keyword → supplier → défaut → revue) +- [ ] Types : `MigrationPlan`, `MappingRow`, `ConfidenceBadge` +- [ ] Fonction `computeMigrationPlan(profileData): MigrationPlan` — pure, sans effet de bord BDD +- [ ] Mapping table encodée depuis `spike-archived/seed-standard/code/mapping-old-to-new.md` +- [ ] Détection des catégories custom (non présentes dans le seed v2) + +### Issue 6 — Service categoryBackupService + wrapper SREF pre-migration [type:task] +Dépendances : aucune (peut aller en parallèle avec Issue 5) +- [ ] Créer `src/services/categoryBackupService.ts` +- [ ] Fonction `createPreMigrationBackup(profile): Promise` : + - Génère nom fichier `_avant-migration-.sref` + - Emplacement par défaut `~/Documents/Simpl-Resultat/backups/` + - Appelle `dataExportService.performExport('transactions_with_categories', 'json', password)` + - Écrit via `write_export_file` (commande Tauri existante) + - Vérifie intégrité via `read_import_file` + checksum SHA-256 + - Retourne `BackupResult { path, size, checksum, verifiedAt }` ou throw +- [ ] Gérer erreurs : espace disque, permissions, chiffrement si profil a un PIN + +### Issue 7 — Page de migration 3-étapes (Livraison 2) [type:feature] +Dépendances : Issue 5, Issue 6 +- [ ] Créer route `/paramètres/categories/migrer` +- [ ] Créer `src/pages/CategoriesMigrationPage.tsx` avec routeur interne (step 1/2/3) +- [ ] Créer `src/components/categories-migration/` avec StepDiscover, StepSimulate, StepConsent, MappingRow, TransactionPreviewPanel +- [ ] Créer `src/hooks/useCategoryMigration.ts` (useReducer) +- [ ] Créer `src/services/categoryMigrationService.ts` avec `applyMigration(plan, backup)` : + - Transaction SQL atomique (BEGIN/COMMIT/ROLLBACK) + - INSERT v1 + UPDATE transactions/budgets/keywords + création parent custom + DELETE v2 non mappées + - Journal dans `user_preferences.last_categories_migration` +- [ ] Écran succès/échec avec chemin backup et options de rollback + +### Issue 8 — Bouton "Rétablir la sauvegarde" (90 jours) [type:feature] +Dépendances : Issue 6, Issue 7 +- [ ] Créer `src/components/settings/CategoriesMigrationBackupBanner.tsx` +- [ ] Afficher dans `Paramètres > Catégories` si `last_categories_migration.timestamp` < 90 jours et flag `banner_dismissed=false` +- [ ] Modale de confirmation +- [ ] Appel à `dataImportService.importFullProfile(path, { mode: 'replace' })` +- [ ] Post-rollback : mettre à jour `categories_schema_version='v2'` et `last_categories_migration.reverted_at` + +### Issue 9 — Tests complets [type:test] +Dépendances : Issues 1, 2, 5, 6, 7 +- [ ] Tests unitaires `categoryMappingService` (algo 4 passes, badges confiance, détection custom) +- [ ] Tests unitaires `categoryBackupService` (création, vérification, erreurs) +- [ ] Tests intégration : flow complet `plan → backup → migrate → verify → rollback` +- [ ] Tests régression : transactions/budgets/keywords préservés post-migration avec IDs remappés +- [ ] Tests régression : fixtures paramétrées (ancien seed v2 ET nouveau seed v1) sur budget, transactions, splits, auto-catégorisation +- [ ] QA manuelle : checklist dans `docs/qa-refonte-seed-categories-ipc.md` couvrant les 3 étapes UI, les cas nominal/échec/rollback + +### Ordre d'exécution + +``` +Livraison 1 (E read-only + seed nouveaux profils): + Issue 1 → Issue 2 → Issue 3 → Issue 4 + +Livraison 2 (B migration profils existants): + Issue 2 → Issue 5 ─┐ + ├→ Issue 7 → Issue 8 + Issue 6 ───────────┘ + +Tests: + Issues 1,2,5,6,7 → Issue 9 +``` + +Livraison 1 = Issues 1-4 (PR #1). Livraison 2 = Issues 5-9 (PR #2 ou series). + +## Fichiers concernés + +| Fichier | Action | Raison | +|---------|--------|--------| +| `src-tauri/src/lib.rs` | Modifier | Ajouter migration v8 (colonne `i18n_key` + pref `categories_schema_version`) | +| `src-tauri/src/database/migrations/v8__category_schema_version.sql` | Créer | Migration additive | +| `src-tauri/src/database/consolidated_schema.sql` | Modifier | Seed v1 complet pour nouveaux profils | +| `src/i18n/locales/fr.json` | Modifier | Nouveau namespace `categoriesSeed` + clés UI migration | +| `src/i18n/locales/en.json` | Modifier | Nouveau namespace `categoriesSeed` + clés UI migration | +| `src/data/categoryTaxonomyV1.json` | Créer | Dérivé du SQL seed v1 | +| `src/services/categoryTaxonomyService.ts` | Créer | Source taxonomie v1 côté TS | +| `src/services/categoryMappingService.ts` | Créer | Algo 4 passes | +| `src/services/categoryBackupService.ts` | Créer | Wrapper SREF pre-migration | +| `src/services/categoryMigrationService.ts` | Créer | Orchestration migration SQL atomique | +| `src/hooks/useCategoryTaxonomy.ts` | Créer | | +| `src/hooks/useCategoryMigration.ts` | Créer | useReducer état page migration | +| `src/pages/CategoriesStandardGuidePage.tsx` | Créer | Livraison 1 | +| `src/pages/CategoriesMigrationPage.tsx` | Créer | Livraison 2 | +| `src/components/categories-migration/*` | Créer | 5 composants (step 1/2/3 + mapping row + preview panel) | +| `src/components/dashboard/CategoriesV1DiscoveryBanner.tsx` | Créer | Bannière one-shot | +| `src/components/settings/CategoriesMigrationBackupBanner.tsx` | Créer | Bannière post-migration 90j | +| `src/components/settings/CategoriesCard.tsx` | Modifier | Ajouter lien vers page Guide + page Migrer | +| `src/pages/Dashboard.tsx` | Modifier | Intégrer la bannière découverte | +| `src/App.tsx` | Modifier | Nouvelles routes | +| `src/components/categories/CategoryTree.tsx` | Modifier | Support `i18n_key` fallback `name` | +| `src/components/categories/CategoryCombobox.tsx` | Modifier | Idem | +| `docs/architecture.md` | Modifier | Documenter nouveaux services, pages, migration v8 | +| `docs/adr/NNNN-refonte-seed-categories-ipc.md` | Créer | ADR pour le choix IPC + pattern prévisualisation-consentement | +| `docs/qa-refonte-seed-categories-ipc.md` | Créer | Checklist QA manuelle | +| `CHANGELOG.md` | Modifier | Entrée sous `[Unreleased]` — Added/Changed | +| `CHANGELOG.fr.md` | Modifier | Idem FR | + +## Plan de tests + +### Tests unitaires +- `categoryMappingService.computeMigrationPlan()` : chaque règle de mapping v2→v1 (18 haute / 12 moyenne / 3 basse / 1 aucune) retourne le bon badge et la bonne cible. +- Algo 4 passes : + - Pass 1 (keyword match) avec diverses combinaisons + - Pass 2 (supplier propagation) + - Pass 3 (fallback défaut) + - Pass 4 (flag "à réviser") +- Détection des catégories custom (absentes du seed v2) → bucket `preserved`. +- Détection des splits (ex: Transport en commun 28 v2 → Bus 1521 OR Train 1522 v1). +- `categoryBackupService.createPreMigrationBackup()` avec mocks Tauri : + - Succès normal : retourne BackupResult valide + - Échec write_export_file : throw erreur "Impossible de créer la sauvegarde" + - Échec integrity check : throw erreur "Sauvegarde corrompue" + - Profil avec PIN : chiffrement appliqué + +### Tests d'intégration +- Flow complet `plan → backup → migrate → verify` sur profil fixture v2 réaliste : + - Catégories v2 mappées correctement + - Transactions re-liées aux nouvelles catégories v1 + - Keywords re-liés + - Budgets re-liés + - Catégories custom regroupées sous "Catégories personnalisées (migration)" +- Flow `rollback` après migration : import SREF restaure l'état v2 exact (transactions, keywords, budgets, categories). +- Échec backup → abort → aucune écriture BDD (profil v2 intact). +- Échec migration SQL → ROLLBACK → profil v2 intact, backup reste disponible. + +### Tests de régression +Fixtures paramétrées v2 ET v1 pour couvrir : +- Auto-catégorisation (`categorizationService.applyKeywordToTransaction`) +- Budgets mensuels et agrégation parent/enfant (`budgetService.getBudgetVsActual`) +- Splits de transactions sur catégories multiples (`transactionService.splitTransaction`) +- Import CSV avec matching supplier/keyword +- Export/Import SREF (pas de régression sur le format) +- UI : `CategoryTree` et `CategoryCombobox` rendent correctement v2 et v1 + +## Critères d'acceptation + +### Livraison 1 +- [ ] Tout nouveau profil créé après la MAJ a le seed v1 appliqué (vérifié par SELECT sur la BDD d'un fresh profile). +- [ ] La bannière dashboard s'affiche sur les profils v2 existants au premier lancement post-MAJ. +- [ ] La bannière disparaît après dismiss et ne réapparaît plus (persistant). +- [ ] La page `/paramètres/categories/standard` affiche correctement l'arbre complet v1 avec FR/EN. +- [ ] Recherche full-text trouve les catégories par nom ou par mot-clé associé. +- [ ] Export PDF produit un document lisible de la taxonomie. +- [ ] Aucun changement aux données des profils v2 existants (test : avant/après MAJ, `SELECT * FROM categories` identique). + +### Livraison 2 +- [ ] Page `/paramètres/categories/migrer` est accessible depuis la page Guide et depuis Paramètres. +- [ ] Étape 2 affiche le bon badge de confiance pour chaque catégorie (validation sur fixture). +- [ ] Toutes les catégories 🟠 "split requis" bloquent l'avancement tant que non résolues. +- [ ] Backup SREF est créé et vérifié AVANT toute écriture BDD. +- [ ] Échec backup → abort → aucune écriture BDD (profil v2 intact). +- [ ] Migration succès → transactions, budgets, keywords tous re-liés correctement. +- [ ] Catégories custom préservées sous "Catégories personnalisées (migration)". +- [ ] Bannière post-migration visible pendant 90 jours, dismissable. +- [ ] Bouton *Rétablir la sauvegarde* fonctionne : restaure exactement l'état v2. + +### Global +- [ ] Tous les tests unitaires et intégration passent (`npm test`, `cargo test`). +- [ ] Type-check clean (`npm run build`). +- [ ] CHANGELOG mis à jour FR et EN. +- [ ] `docs/architecture.md` mis à jour. +- [ ] ADR rédigé. +- [ ] QA manuelle exécutée selon checklist `docs/qa-refonte-seed-categories-ipc.md`. + +## Edge cases et risques + +| Cas | Mitigation | +|-----|------------| +| Profil v2 avec 0 catégorie custom → mapping simple | Testé par fixture minimale | +| Profil v2 avec ≥50 catégories custom (utilisateur power) | UI pagine la liste dans l'étape 2 ; parent "Catégories personnalisées (migration)" absorbe tout | +| Utilisateur abandonne étape 2 en plein milieu | Aucune écriture BDD, le plan en mémoire est perdu — OK, aucun effet secondaire | +| Utilisateur abandonne étape 3 après checklist cochée mais avant backup | Bouton *Annuler* abort propre, aucune écriture | +| Espace disque insuffisant pour backup | `categoryBackupService` lève erreur claire → UI montre écran d'échec avec options "changer dossier / réessayer / annuler" | +| Migration SQL échoue au milieu | `ROLLBACK` automatique, backup reste disponible, UI affiche erreur + invite à rétablir | +| Utilisateur lance 2 instances de Simpl'Résultat en parallèle pendant la migration | SQLite lock naturel ; la 2e instance attend ; bas risque (app desktop mono-fenêtre en pratique) | +| Profil protégé par PIN : backup doit être chiffré | `categoryBackupService` récupère le PIN depuis le ProfileContext et passe en password à `write_export_file` | +| Utilisateur renomme/déplace le fichier SREF après migration | Le bouton *Rétablir* affiche un file picker si le chemin enregistré n'est plus valide | +| Déjà-migré : utilisateur re-lance la page de migration | Détection `categories_schema_version='v1'` → message "Votre profil utilise déjà la taxonomie v1" avec option "revoir la sauvegarde" uniquement | +| Utilisateur veut migrer APRÈS les 90 jours (bannière disparue) | Entrée permanente dans Paramètres > Catégories reste disponible → bouton *Explorer / Migrer* | +| Clé i18n manquante (typo dans le JSON) | Fallback sur le nom brut de la catégorie — pas de crash | +| Seed v1 manque une feuille qu'un utilisateur a en v2 (ex: "Projets") | Mapping badge 🔴 + prompt obligatoire étape 2, ou préservé en custom | +| Compatibilité forward : une future v2 du seed (refinement v1.1, v1.2) | `categories_schema_version` permet de détecter et ajouter des colonnes plus tard. Pattern réutilisable. | +| Performance : 139 catégories + 100+ keywords au seed pour un nouveau profil | < 200 ms sur SSD moderne, négligeable | +| Performance : migration de 5000 transactions | Transaction unique, < 2 s sur SSD moderne, loader visible pendant ce temps |