feat(reports): getCategoryOverTime -> arbre tendances (sidecar id-keyed) + resultats (#264) #268
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#268
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-264-overtime-tree"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Resolves #264
Generated autonomously by /autopilot run of 2026-07-07
Base: #263 (generic leaf-driven tree builder). Stacked PR — review/merge #263 first.
What
Sidecar approach (resolves the 3 review caveats).
getCategoryOverTimegains an id-keyed hierarchicaltree: OverTimeRow[], built via the genericbuildLeafDrivenTree(#263) as a newbuildOverTimeTreespecialisation — leaf-driven, income-first, no top-N / no "Other" on the tree path. The name-keyed pivot is left untouched, so the chart and the dashboard render identically.computeOverTimeResultsnow consumes the tree leaves (neveris_parentsubtotals), grouped by each leaf'scategory_id-resolved type → section subtotals + Result-before/net lines are exact.Pivot unchanged (proof)
getCategoryOverTimekeeps the exact same top-N + "Other" pivot loop;data.data / categories / colors / categoryIds / typesare byte-identical (only a sidecartreefield is added to the return).CategoryOverTimeChart.tsxdiff = comment only (no logic) → still reads the top-N-capped pivot, series stay bounded.DashboardPage.tsx= not touched;useDashboard.ts/useTrends.tsdiffs = onlytree: []added to the initial-state literal (the chart never readstree).getCategoryOverTimeasserts the pivot still yields["Food","Other"]while the tree carries every category.OverTimeRow shape (for #265)
Composes with
collapsibleRows/useCollapsibleGroups(same snake_case fields asCategoryDelta) for the #265 hierarchy + collapse work.Also
CategoryOverTimeTablerenders the tree leaves (every category, id-safe) instead of the top-N pivot — delivers the "all categories in the table" decision. Hierarchy nesting + collapse + interleaved Result stay for #265.SELECT ... FROM categories) has no interpolation.Decisions
buildOverTimeTree(exported) rather than inlining, so it is unit-tested directly likebuildCompareTreeand avoids a forwardconstreference.computeOverTimeResultsdropped itsvisibleCategories(name) param and the table droppedhiddenCategories: the table now shows all categories (per the #264 decision), and the chart keeps its own name-based hide. No visible regression (they are never shown together; toggled by view mode)..not.toThrow().Verification
npm run buildgreen (tsc + vite),tsc --noEmitclean.npm test→ 720 passed (713 baseline + 7). New: homonym-no-collision, leaves-only-not-parents,buildOverTimeTreesuite (nesting / income-first / grand-total invariance / orphan / cycle), pivot-stays-top-N + tree-has-all-categories./pr-review — PR #268 : arbre tendances id-keyed (sidecar) + résultats (#264)
Verdict : APPROVE
Résumé
Le sidecar
tree: OverTimeRow[]est ajouté àgetCategoryOverTimesans toucher au pivot name-keyed (chart + dashboard inchangés, byte-identiques), etcomputeOverTimeResultsconsomme désormais les feuilles de l'arbre (id-keyed, toutes catégories) → sous-totaux et lignes de Résultat exacts, homonymes séparés. Implémentation propre, bien testée, aucune migration DB.Points vérifiés
data/categories/colors/categoryIds/typesrestent byte-identiques, seultrees'ajoute au retour.CategoryOverTimeChart= diff commentaire seul. Prouvé par le test « pivot stays top-N + tree has all categories » (categories === ["Food","Other"],data[0] === {Food:300, Other:150}).treeest un champ requis deCategoryOverTimeData; les deux seuls littéraux de construction (useTrends/useDashboardinitial state) reçoiventtree: [], et les reducers propagent leaction.payloadcomplet → jamaisdata.tree === undefinedau rendu (sinonfor (const row of data.tree)planterait).tsccouvre tout site oublié.computeOverTimeResultsbucketise les feuilles parcategory_typerésolu par id (jamais par nom) et exclut lesis_parent→ pas de double comptage parent/enfant, homonymes de types différents séparés, et un « petit » revenu/transfert n'est plus compté en dépense. Invariance grand-total (feuilles vs plat) testée.buildOverTimeTree: spécialisation fidèle debuildLeafDrivenTree(#263), miroir debuildCompareTree—monthly[]index-aligné surmonthCount, garde de cycleMAX_TREE_DEPTH, tri income→expense→transfer viaCOMPARE_TYPE_ORDER. Cycle A→B→A → root-less → retourne[](termine, ne throw pas), testé.not.toThrow().SELECT id,name,color,type,parent_id FROM categoriesn'a aucune interpolation ; le pivot reste paramétré$n. Aucun secret.Suggestions (non bloquantes)
useDashboardappellegetCategoryOverTimedans unPromise.all, donc chaque chargement du tableau de bord exécute maintenant leSELECT ... FROM categories+buildOverTimeTreealors que le chart du dashboard ne lit jamaistree. Coût négligeable (petite table catégories), mais c'est du travail inutile ; un flag opt-in (includeTree) l'éviterait si ça devenait sensible.hiddenCategoriesn'affecte plus le tableau : masquer des catégories en vue chart puis basculer en vue tableau les réaffiche toutes. C'est la décision assumée de #264 (le tableau montre toutes les catégories), donc OK — simplement à garder en tête côté UX (l'état de masquage est partagé entre les deux vues).Note de merge (pile)
check.ymlne se déclenche que sur les PR versmain: ce maillon (baseissue-263) n'est pas CI-validé. Valider le tip cumulé en local avant merge (tsc + vite build, vitest, cargo check/test) — le corps de PR annonce build vert + 720 vitest ; à confirmer sur le tip empilé (#267 → #268, voire #269).Review adversariale — analyse statique du diff head-vs-base (#268) + lecture de
buildLeafDrivenTree(#263) et des reducers/consommateurs. Suite non exécutée localement (contexte review).Mergée localement dans
main(pile #266/#267/#268/#269, tipfe87313). Fermée manuellement — merge local d'une pile stackée (l'API merge Forgejo échoue sur les bases non-main), tip cumulé validé build + 728 vitest verts.Pull request closed