feat(balance): schema & migrations v14/v15 + types (#210) #219

Merged
maximus merged 1 commit from issue-210-schema-migrations into main 2026-06-10 01:07:45 +00:00
Owner

Resolves #210

Fondations de schema pour la phase « detail par titre » (Etape 2), purement additives. v1-v13 jamais editees, checksums intacts. Version max courante = 13 → ajout de v14 + v15.

Migrations

  • v14 (balance_holdings_schema.sql, expose via BALANCE_HOLDINGS_SCHEMA, applique tel quel par la migration inline) :
    • balance_securities — catalogue d instruments ; symbol normalise (upper/trim) + COLLATE NOCASE UNIQUE (anti-doublons de casse, caveat SEC/ARCH) ; asset_type CHECK (stock|crypto) ; currency DEFAULT CAD ; timestamps.
    • balance_snapshot_holdings — detail par titre d une ligne de snapshot ; FK snapshot_line_idbalance_snapshot_lines (CASCADE), security_idbalance_securities (RESTRICT) ; value denormalise ; book_cost/price_source/price_fetched_at nullable ; UNIQUE(snapshot_line_id, security_id) + 2 index.
  • v15 (inline) : balance_accounts.kind (simple|detailed, NOT NULL DEFAULT simple, CHECK) + detailed_since DATE ; backfill kind=detailed pour les comptes sous categorie priced. Deux ADD COLUMN distincts (contrainte SQLite), idempotent.

Parite & types

  • consolidated_schema.sql (nouveaux profils) a parite : 2 tables + 2 index + colonnes kind/detailed_since + backfill v15 (no-op pour les 4 starters simples, reproduit pour un futur starter price).
  • Types TS : BalanceAccountKind, BalanceSecurity, BalanceSnapshotHolding (+ variante jointe WithSecurity) ; BalanceAccount gagne kind + detailed_since? ; BalanceAccountWithCategory expose kind ET category_kind.

Decisions

  • balance_schema.sql (reference v9) non modifie : c est le SQL checksum-locked de v9 ; le toucher casserait le checksum. La nouvelle reference du v14 est balance_holdings_schema.sql (le v9 reste fige a cote de sa migration inline). MEDIUM.
  • IF NOT EXISTS / ADD COLUMN additif pour l idempotence (convention v9..v13).
  • Pas d entree CHANGELOG : fondations schema/types sans comportement utilisateur visible (l Etape 2 sort dans les PRs UI suivantes #214/#216 + le doc PR #218).

Quality gate

  • cargo check : PASS
  • cargo test : PASS — 89 tests (+9 nouveaux v14/v15 + parite consolidee ; le test chaine v9→v13 reste vert)
  • npm run build (tsc + vite) : PASS
  • npm test (vitest) : PASS — 552 tests

Generated autonomously by /autopilot run of 2026-06-06

Resolves #210 Fondations de schema pour la phase « detail par titre » (Etape 2), purement additives. v1-v13 jamais editees, checksums intacts. Version max courante = 13 → ajout de v14 + v15. ## Migrations - **v14** (`balance_holdings_schema.sql`, expose via `BALANCE_HOLDINGS_SCHEMA`, applique tel quel par la migration inline) : - `balance_securities` — catalogue d instruments ; `symbol` normalise (upper/trim) + `COLLATE NOCASE UNIQUE` (anti-doublons de casse, caveat SEC/ARCH) ; `asset_type` CHECK (stock|crypto) ; `currency` DEFAULT CAD ; timestamps. - `balance_snapshot_holdings` — detail par titre d une ligne de snapshot ; FK `snapshot_line_id` → `balance_snapshot_lines` (CASCADE), `security_id` → `balance_securities` (RESTRICT) ; `value` denormalise ; `book_cost`/`price_source`/`price_fetched_at` nullable ; `UNIQUE(snapshot_line_id, security_id)` + 2 index. - **v15** (inline) : `balance_accounts.kind` (simple|detailed, NOT NULL DEFAULT simple, CHECK) + `detailed_since DATE` ; backfill `kind=detailed` pour les comptes sous categorie `priced`. Deux ADD COLUMN distincts (contrainte SQLite), idempotent. ## Parite & types - `consolidated_schema.sql` (nouveaux profils) a parite : 2 tables + 2 index + colonnes `kind`/`detailed_since` + backfill v15 (no-op pour les 4 starters simples, reproduit pour un futur starter price). - Types TS : `BalanceAccountKind`, `BalanceSecurity`, `BalanceSnapshotHolding` (+ variante jointe `WithSecurity`) ; `BalanceAccount` gagne `kind` + `detailed_since?` ; `BalanceAccountWithCategory` expose `kind` ET `category_kind`. ## Decisions - `balance_schema.sql` (reference v9) **non modifie** : c est le SQL checksum-locked de v9 ; le toucher casserait le checksum. La nouvelle reference du v14 est `balance_holdings_schema.sql` (le v9 reste fige a cote de sa migration inline). MEDIUM. - `IF NOT EXISTS` / `ADD COLUMN` additif pour l idempotence (convention v9..v13). - Pas d entree CHANGELOG : fondations schema/types sans comportement utilisateur visible (l Etape 2 sort dans les PRs UI suivantes #214/#216 + le doc PR #218). ## Quality gate - `cargo check` : PASS - `cargo test` : PASS — 89 tests (+9 nouveaux v14/v15 + parite consolidee ; le test chaine v9→v13 reste vert) - `npm run build` (tsc + vite) : PASS - `npm test` (vitest) : PASS — 552 tests Generated autonomously by /autopilot run of 2026-06-06
maximus added the
status:review
autopilot:pending-human
labels 2026-06-06 16:53:19 +00:00
maximus added 1 commit 2026-06-06 16:53:19 +00:00
feat(balance): schema & migrations v14/v15 + types (securities, holdings, account.kind) (#210)
All checks were successful
PR Check / rust (pull_request) Successful in 23m1s
PR Check / frontend (pull_request) Successful in 2m25s
a1c3dafcd0
Foundations for 'détail par titre' (Étape 2), purely additive — v1-v13 are
untouched and their checksums stay intact.

v14 (balance_holdings_schema.sql, applied verbatim via BALANCE_HOLDINGS_SCHEMA):
- balance_securities: instrument catalogue, symbol normalized + COLLATE NOCASE
  UNIQUE (no case-dupes), asset_type CHECK ('stock','crypto'), currency CAD.
- balance_snapshot_holdings: per-security breakdown of a snapshot line, FK to
  balance_snapshot_lines (CASCADE) + balance_securities (RESTRICT), value
  denormalized, UNIQUE(snapshot_line_id, security_id) + 2 indexes.

v15 (inline): balance_accounts gains kind ('simple'|'detailed', NOT NULL
DEFAULT 'simple', CHECK) + detailed_since DATE; backfills kind='detailed' on
accounts under a priced category. Two single-column ADDs (SQLite), idempotent.

consolidated_schema.sql brought to parity: 2 tables + 2 indexes + kind/
detailed_since columns + the v15 backfill (no-op for the 4 simple starters,
reproduced for future priced starters).

TS types: BalanceAccountKind, BalanceSecurity, BalanceSnapshotHolding (+
WithSecurity join variant); BalanceAccount gains kind + detailed_since;
BalanceAccountWithCategory exposes both kind and category_kind.

Tests: +9 (v14/v15 via V14_SQL/V15_SQL consts + db_through_v13 helper, plus a
consolidated parity test). cargo test 89 passed, npm build + 552 vitest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Adversarial review — PR #219 (issue #210) — Bilan Étape 2, schema & migrations v14/v15 + types

Verdict : APPROVE (1 suggestion importante à traiter avant les PRs consommatrices, 2 mineures)

Fondations propres, purement additives, et exceptionnellement bien testées. La sécurité des migrations est vérifiée à la source.

Vérifié ✓

Migration safety (v1-v13 intactes)

  • Diff lib.rs = 3 hunks, tous additifs (@@ -203,6 +203,53, @@ -1793,6 +1840, @@ -1899,5 +2310). Les entrées v1→v13 ne sont pas touchées ; v14/v15 sont appended. balance_schema.sql (= SQL checksum-locké de v9) non modifié. Aucun risque de casser le SHA-384 sur les profils existants. 5 fichiers seulement.

Idempotence

  • v14 : CREATE TABLE IF NOT EXISTS ×2 + CREATE INDEX IF NOT EXISTS ×2. OK.
  • v15 : deux ADD COLUMN distincts (contrainte SQLite mono-colonne), backfill UPDATE … WHERE … IN (SELECT … kind='priced') naturellement idempotent.

Schema correctness

  • symbol TEXT NOT NULL COLLATE NOCASE UNIQUE ✓ ; asset_type … CHECK (… IN ('stock','crypto')) ✓ ; kind … CHECK (… IN ('simple','detailed')) ✓.
  • FK : snapshot_line_id … ON DELETE CASCADE ✓ ; security_id … ON DELETE RESTRICT ✓. UNIQUE(snapshot_line_id, security_id) ✓. 2 index présents ✓.
  • FK sur balance_snapshot_lines(id) (pas snapshot+account) — justifié par UNIQUE(snapshot_id, account_id) de la table lignes. Bon choix.
  • Ordre de création dans consolidated : snapshot_lines (238) et securities (275) avant holdings (285). OK.

Parité consolidated_schema (drift-proof)

  • Diff normalisé des deux blocs CREATE TABLE (inline balance_holdings_schema.sql vs consolidated) : byte-identiques. Les 2 index et le backfill v15 sont reproduits. Le backfill consolidated cible le même WHERE kind='priced' que l'inline. Pas de divergence silencieuse.

Types

  • BalanceAccountKind, BalanceSecurity, BalanceSnapshotHolding (+WithSecurity) ✓. BalanceAccountWithCategory extends BalanceAccount → hérite kind/detailed_since, et category_kind (ligne 682) coexiste avec kind. Conforme.

Tests (vrais, pas juste compile)

  • 9 tests Rust qui exercent réellement : existence tables/index, collision NOCASE, rejet CHECK asset_type/kind, CASCADE+RESTRICT (avec PRAGMA foreign_keys=ON confirmé dans fresh_db/consolidated_db), UNIQUE, backfill priced→detailed vs cash→simple, default 'simple', parité consolidated. V14_SQL = BALANCE_HOLDINGS_SCHEMA (même const) = drift-proof par construction.

🟡 Suggestion importante (non bloquante au scope « fondations », mais à corriger avant tout consommateur — #211/#214/#216)

balance.service.ts : BalanceAccount.kind déclaré requis mais jamais SELECT.

  • listBalanceAccounts (l.306-310) et getBalanceAccount (l.324) sélectionnent des colonnes explicites sans a.kind ni a.detailed_since.
  • Les casts db.select<BalanceAccount[]>(…) / <BalanceAccountWithCategory[]>(…) sont des assertions de type → tsc ne bronche pas (CI verte attendue), mais à l'exécution account.kind === undefined alors que le type garantit un BalanceAccountKind non-nullable.
  • Conséquence : le premier consommateur qui fait if (account.kind === 'detailed') obtiendra silencieusement false. Le kind requis (vs ?) rend le piège plus tranchant.
  • Fix trivial, dans ce même fichier : ajouter a.kind, a.detailed_since aux deux SELECTs (les colonnes existent après v15). À faire ici idéalement, ou au plus tard en ouverture de #211 — à ne pas oublier.

🟢 Mineur

  1. BalanceSnapshotHolding.price_source typé string | null avec doc 'manual' | 'maximus-api' | NULL — un union littéral serait plus strict (cohérent avec le pattern BalanceAccountKind). Cosmétique.
  2. Pas d'entrée CHANGELOG — justifié (aucun comportement utilisateur visible à ce stade). OK avec la convention.

Excellent travail de fondation. Les commentaires inline (caveats SEC/ARCH, rationale denormalisation/FK) sont de qualité.

## Adversarial review — PR #219 (issue #210) — Bilan Étape 2, schema & migrations v14/v15 + types **Verdict : APPROVE** (1 suggestion importante à traiter avant les PRs consommatrices, 2 mineures) Fondations propres, purement additives, et exceptionnellement bien testées. La sécurité des migrations est vérifiée à la source. ### Vérifié ✓ **Migration safety (v1-v13 intactes)** - Diff lib.rs = 3 hunks, tous additifs (`@@ -203,6 +203,53`, `@@ -1793,6 +1840`, `@@ -1899,5 +2310`). Les entrées v1→v13 ne sont pas touchées ; v14/v15 sont appended. `balance_schema.sql` (= SQL checksum-locké de v9) non modifié. Aucun risque de casser le SHA-384 sur les profils existants. 5 fichiers seulement. **Idempotence** - v14 : `CREATE TABLE IF NOT EXISTS` ×2 + `CREATE INDEX IF NOT EXISTS` ×2. OK. - v15 : deux `ADD COLUMN` distincts (contrainte SQLite mono-colonne), backfill `UPDATE … WHERE … IN (SELECT … kind='priced')` naturellement idempotent. **Schema correctness** - `symbol TEXT NOT NULL COLLATE NOCASE UNIQUE` ✓ ; `asset_type … CHECK (… IN ('stock','crypto'))` ✓ ; `kind … CHECK (… IN ('simple','detailed'))` ✓. - FK : `snapshot_line_id … ON DELETE CASCADE` ✓ ; `security_id … ON DELETE RESTRICT` ✓. `UNIQUE(snapshot_line_id, security_id)` ✓. 2 index présents ✓. - FK sur `balance_snapshot_lines(id)` (pas snapshot+account) — justifié par `UNIQUE(snapshot_id, account_id)` de la table lignes. Bon choix. - Ordre de création dans consolidated : `snapshot_lines` (238) et `securities` (275) avant `holdings` (285). OK. **Parité consolidated_schema (drift-proof)** - Diff normalisé des deux blocs `CREATE TABLE` (inline `balance_holdings_schema.sql` vs consolidated) : **byte-identiques**. Les 2 index et le backfill v15 sont reproduits. Le backfill consolidated cible le même `WHERE kind='priced'` que l'inline. Pas de divergence silencieuse. **Types** - `BalanceAccountKind`, `BalanceSecurity`, `BalanceSnapshotHolding` (+`WithSecurity`) ✓. `BalanceAccountWithCategory extends BalanceAccount` → hérite `kind`/`detailed_since`, et `category_kind` (ligne 682) coexiste avec `kind`. Conforme. **Tests (vrais, pas juste compile)** - 9 tests Rust qui exercent réellement : existence tables/index, collision NOCASE, rejet CHECK asset_type/kind, CASCADE+RESTRICT (avec `PRAGMA foreign_keys=ON` confirmé dans `fresh_db`/`consolidated_db`), UNIQUE, backfill priced→detailed vs cash→simple, default 'simple', parité consolidated. `V14_SQL = BALANCE_HOLDINGS_SCHEMA` (même const) = drift-proof par construction. --- ### 🟡 Suggestion importante (non bloquante au scope « fondations », mais à corriger avant tout consommateur — #211/#214/#216) **`balance.service.ts` : `BalanceAccount.kind` déclaré requis mais jamais SELECT.** - `listBalanceAccounts` (l.306-310) et `getBalanceAccount` (l.324) sélectionnent des colonnes explicites **sans** `a.kind` ni `a.detailed_since`. - Les casts `db.select<BalanceAccount[]>(…)` / `<BalanceAccountWithCategory[]>(…)` sont des assertions de type → tsc ne bronche pas (CI verte attendue), mais à l'exécution `account.kind === undefined` alors que le type garantit un `BalanceAccountKind` **non-nullable**. - Conséquence : le premier consommateur qui fait `if (account.kind === 'detailed')` obtiendra silencieusement `false`. Le `kind` requis (vs `?`) rend le piège plus tranchant. - **Fix trivial, dans ce même fichier** : ajouter `a.kind, a.detailed_since` aux deux SELECTs (les colonnes existent après v15). À faire ici idéalement, ou au plus tard en ouverture de #211 — à ne pas oublier. ### 🟢 Mineur 1. `BalanceSnapshotHolding.price_source` typé `string | null` avec doc `'manual' | 'maximus-api' | NULL` — un union littéral serait plus strict (cohérent avec le pattern `BalanceAccountKind`). Cosmétique. 2. Pas d'entrée CHANGELOG — justifié (aucun comportement utilisateur visible à ce stade). OK avec la convention. Excellent travail de fondation. Les commentaires inline (caveats SEC/ARCH, rationale denormalisation/FK) sont de qualité.
maximus merged commit 604b97fc4d into main 2026-06-10 01:07:45 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#219
No description provided.