Simpl-Resultat/docs/adr/0012-balance-two-level-model.md
le king fu ebc709a277
All checks were successful
PR Check / rust (pull_request) Successful in 22m24s
PR Check / frontend (pull_request) Successful in 2m22s
docs(balance): ADR 0014 + reject 0012 + guide + changelog (#205)
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>
2026-06-01 21:15:06 -04:00

107 lines
7.8 KiB
Markdown
Raw 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: **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