Document Étape 1 of the balance audit (vehicle_type axis), already shipped in #202/#203/#204: - ADR 0014 (Accepted): fiscal envelope is an account attribute, the category is a pure asset class; Étape 2 (per-security detail) explicitly out of scope. - ADR 0012 marked Rejected (never accepted, not Superseded) + pointer to 0014. - User guide (markdown + in-app docs.balance i18n FR/EN): optional fiscal envelope, the two chart axes, type renaming, and the historical-reclass note. - CHANGELOG.md + CHANGELOG.fr.md [Unreleased]: Added (envelope field, envelope axis, collapsible returns) + Changed (asset-class category, CELI/REER reclass, rename no longer alters translation, historical-reclass note). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
7.8 KiB
Markdown
107 lines
7.8 KiB
Markdown
# ADR 0012 — Modèle à deux niveaux pour le Bilan (véhicules × compositions)
|
||
|
||
- Status: **Rejected** (jamais accepté ; reste à l'état de proposition)
|
||
- Date: 2026-05-01 (proposé) · 2026-06-01 (rejeté)
|
||
- Issue: #179
|
||
- Rejeté au profit de : [ADR 0014](0014-balance-vehicule-attribut.md)
|
||
|
||
> **Rejet (2026-06-01).** L'audit Bilan (`docs/audit-bilan-2026-05.md`) a retenu une **trajectoire additive** plutôt que ce modèle à deux tables surdimensionné : l'ADR 0014 fait du véhicule fiscal un simple **attribut nullable du compte** (`vehicle_type`) et de la catégorie une **pure classe d'actif**, sans réécriture de `/balance` ni migration massive. Le grain visé par 0012 (triplet véhicule × *composition*) restait par ailleurs au niveau **classe d'actif**, pas **titre** — il déplaçait le mur sans débloquer le détail par titre. *Rejected* (et non *Superseded*) : 0012 n'a jamais quitté l'état `Proposed`, donc aucune décision active n'est remplacée. La réflexion sur les groupements croisés (`GROUP BY véhicule` / `GROUP BY classe d'actif`) reste valable et est reprise par 0014.
|
||
|
||
## 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
|