Part 1 — New profiles: seed 4 starter accounts in
consolidated_schema.sql (Compte chèque/CELI/REER/Compte
non-enregistré, currency CAD, is_active=1) right after the
balance_categories seeds. Categories resolved via SELECT subquery
on the seeded `key` values for robustness.
Part 2 — Existing profiles: StarterAccountsModal proposes the same
4 starters at first /balance visit. Default-checked checkboxes,
collision rule (case-insensitive trim name + matching category)
disables matches with a "Déjà présent" tooltip. The atomic helper
`proposeStarterAccounts` wraps the inserts in BEGIN/COMMIT (rolls
back on error). user_preferences.balance_starter_proposed records
{shown_at, accepted} so the modal never reappears, dismissed or
confirmed.
Part 3 — docs/adr/0012-balance-two-level-model.md (Proposed):
captures the future vehicles × compositions model for reflection,
no code change. Numbered 0012 because 0011 was already taken by
the providers-best-effort-yahoo ADR. Linked from architecture.md
ADR table and Bilan section.
Tests: StarterAccountsModal.test.tsx covers STARTER_ACCOUNTS shape,
getStarterCollisions (case-insensitive trim, category-scoped) and
proposeStarterAccounts (insert order, COMMIT, ROLLBACK on failure).
No render tests — mirrors the BalanceOnboardingCard pattern (no
jsdom configured).
Resolves #179
6.8 KiB
ADR 0012 — Modèle à deux niveaux pour le Bilan (véhicules × compositions)
- Status: Proposed
- Date: 2026-05-01
- Issue: #179
Contexte
Le Bilan modélise actuellement les comptes de manière plate : balance_accounts est rattaché à exactement une balance_categories, qui combine implicitement la nature fiscale du véhicule (CELI, REER, non enregistré) et la classe d'actif (encaisse, action, fonds, crypto). Les sept catégories seedées par Migration v9 sont des frères/sœurs au même niveau :
cash · tfsa · rrsp · fund · other · stock · crypto
Cette structure pose une limite expressive : un véhicule fiscal et une classe d'actif sont deux dimensions orthogonales, pas une hiérarchie. Un utilisateur qui détient une action d'Apple à l'intérieur d'un CELI n'a aujourd'hui que des choix dégradés :
- créer un compte
pricedrattaché à la catégoriestock→ l'avantage fiscal CELI disparaît du modèle ; - créer un compte
simplerattaché àtfsaavec un montant agrégé → la valeur de marché et le rendement réel par titre disparaissent ; - créer une catégorie utilisateur custom (
tfsa_stock) → l'arbre explose en N×M permutations.
Cette tension est visible mais reste tolérable au MVP — la plupart des utilisateurs commencent avec des comptes simples (chèque, CELI cotisations, REER cotisations) et n'investissent en titres cotés que plus tard. La question est néanmoins structurante pour la roadmap : un changement de modèle après livraison V1 nécessitera une migration de données massive et une réécriture quasi totale de /balance.
L'ADR 0012 documente la réflexion avant que le besoin devienne bloquant, sans engager de code.
Proposition — Modèle à deux niveaux
Remplacer balance_accounts → balance_categories par deux tables conceptuelles :
| Table | Rôle | Exemples |
|---|---|---|
balance_vehicles |
Véhicule fiscal / contenant | Compte chèque, CELI, REER, FERR, RPDB, non enregistré |
balance_compositions |
Classe d'actif détenue dans le véhicule | Encaisse, action, fonds indiciel, obligation, crypto |
Une ligne de snapshot devient un triplet (vehicle_id, composition_id, value) au lieu de l'actuel (account_id, value) :
balance_snapshot_lines
├── vehicle_id (FK balance_vehicles)
├── composition_id (FK balance_compositions)
├── quantity, unit_price (NULL pour compositions de type 'simple')
└── value
Bénéfices :
- Expressivité : un CELI avec 3 actions et un peu d'encaisse devient 4 lignes lisibles, additionnables, filtrables sur l'une OU l'autre dimension.
- Rapports croisés : "valeur totale en CELI" (somme par véhicule) ET "valeur totale en actions" (somme par composition) sont deux groupements naturels.
- Modified Dietz par véhicule ou par composition : les apports/retraits suivent le véhicule, le rendement suit la composition.
Alternatives considérées
A. Tagging multi-axes sur le modèle plat actuel
Garder balance_accounts plat, ajouter une table balance_account_tags libre. L'utilisateur tague chaque compte avec autant d'axes que voulu (tfsa, stock, apple, tech).
- ✅ Migration triviale (table additive).
- ❌ Aucune contrainte sur les combinaisons → la cohérence retombe sur l'utilisateur.
- ❌ Les rapports "actions dans CELI" deviennent une intersection de tags, beaucoup plus coûteuse à requêter et à expliquer.
- ❌ Risque d'arbres divergents entre profils — pas de vocabulaire partagé.
B. Sous-comptes sous comptes
Introduire balance_accounts.parent_id (auto-référence). Un compte Mon CELI (catégorie tfsa, simple) pourrait avoir des enfants Apple Inc. (catégorie stock, priced).
- ✅ Modèle hiérarchique familier (similaire aux catégories de transactions).
- ❌ La somme parent = somme enfants devient un invariant à maintenir → friction de saisie.
- ❌ Les snapshots doublent leur taille (ligne parent + lignes enfants) sans gain expressif réel : la nature fiscale du parent et la nature d'actif des enfants restent collées sur un seul axe.
- ❌ Profondeur d'arbre incertaine : on retombe sur le multi-axes mal déguisé.
C. Statu quo (modèle plat enrichi)
Garder le modèle actuel et accepter que les utilisateurs avancés créent des catégories user-définies pour les permutations qui les intéressent (tfsa_stock, rrsp_fund).
- ✅ Aucun coût de migration.
- ✅ Suffisant pour 80% des cas d'usage (utilisateurs avec des comptes simples).
- ❌ Friction documentée croissante au fur et à mesure que la base d'utilisateurs détient des portefeuilles diversifiés.
- ❌ La taxonomie utilisateur diverge entre profils, rendant tout futur partage ou agrégation cross-profil très coûteux.
Impact
Une adoption du modèle à deux niveaux implique, au minimum :
- Migration v12+ : décomposer chaque
balance_accountsexistant en(vehicle, composition)selon une heuristique surcategory.kind+category.asset_type. Migration v9 actuelle (7 catégories seedées) sera scindée en deux seeds. - Réécriture complète des écrans
/balance/accountset/balance/snapshot: la grille de saisie passe d'une dimension à deux. - Adaptation des agrégateurs
balance.service.ts:getSnapshotTotalsByDatereste valide, maisgetSnapshotTotalsByCategoryAndDatedoit être dédoublé engetSnapshotTotalsByVehicleAndDate+getSnapshotTotalsByCompositionAndDate. - Adaptation du calcul Modified Dietz : la pertinence du rendement par véhicule vs par composition doit être tranchée.
- Adaptation des graphiques : la pile actuelle (stacked-by-category) doit choisir un axe par défaut + offrir une bascule.
Cet impact est massif. La proposition n'est viable qu'après stabilisation du modèle plat actuel et collecte de retours utilisateurs réels confirmant le besoin.
Décision
Status: Proposed. L'équipe gèle la décision jusqu'à ce que les conditions de réévaluation soient réunies :
- La V1 du Bilan (issues #138 → #179) est livrée et utilisée en production sans régression majeure pendant au moins un cycle de release ;
- Au moins trois retours utilisateurs distincts décrivent le cas d'usage "actions à l'intérieur d'un CELI/REER" comme bloquant ;
- La fonctionnalité de price-fetching (Issue #143, ADR 0009) est livrée — sans elle, le modèle à deux niveaux résoudrait un problème (rendement par titre dans CELI) sans pouvoir l'exploiter.
À la prochaine évaluation, cet ADR passera à Accepted (avec plan de migration v12+) ou Rejected (au profit du statu quo + tagging optionnel).