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

7.8 KiB
Raw Permalink Blame History

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

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 — Modified Dietz par compte (modèle plat)
  • ADR 0010 — FK RESTRICT sur transferts (contrainte préservée par les deux modèles)
  • Issue #179 — Comptes de départ + cet ADR