Simpl-Resultat/docs/adr/0012-balance-two-level-model.md
le king fu cd0a2b826f feat(balance): starter accounts + opt-in modal + ADR 0012
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
2026-05-02 11:59:45 -04:00

104 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `priced` rattaché à la catégorie `stock` → l'avantage fiscal CELI disparaît du modèle ;
- créer un compte `simple` rattaché à `tfsa` avec 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_accounts` existant en `(vehicle, composition)` selon une heuristique sur `category.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/accounts` et `/balance/snapshot`** : la grille de saisie passe d'une dimension à deux.
- **Adaptation des agrégateurs `balance.service.ts`** : `getSnapshotTotalsByDate` reste valide, mais `getSnapshotTotalsByCategoryAndDate` doit être dédoublé en `getSnapshotTotalsByVehicleAndDate` + `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 :
1. 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 ;
2. Au moins trois retours utilisateurs distincts décrivent le cas d'usage "actions à l'intérieur d'un CELI/REER" comme bloquant ;
3. 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).
## Liens
- [ADR 0008](0008-modified-dietz-pour-rendement.md) — Modified Dietz par compte (modèle plat)
- [ADR 0010](0010-fk-restrict-balance-transfers.md) — FK RESTRICT sur transferts (contrainte préservée par les deux modèles)
- Issue #179 — Comptes de départ + cet ADR