[WSAPI-13] Include household-visible accounts (Sarah's RRSP) in the balances sync #12

Merged
chris merged 10 commits from feature/WSAPI-13 into main 2026-09-27 09:33:14 -06:00
Owner

Plane: WSAPI-13

Summary

Adds household-visible Wealthsimple accounts (Sarah's RRSP, rrsp-ZFgB7yct0Q) to the nightly sync/balances, tagged scope: 'household', merged with own accounts by id (own wins, no double-counting — Chris's TFSA is not household-shared).

  • Holdings: FetchIdentityPositions with accountScope: HOUSEHOLD (new lib/wealthsimple/household).
  • Cash: FetchAccountsWithBalance 500s for household accounts, so cash = latest daily net liquidation value (FetchIdentityHistoricalFinancials) − holdings, priced with the same merged quote map used for valuation. Non-CAD / stale / unparseable NLV and non-CAD quotes fall back to cash 0 with a warning.
  • Status: new householdBalances job on /status; a household failure never fails the own-account sync, but SessionDeadError still halts it.
  • Carry-forward: if the household step fails, the previous cache's household accounts are reused (their own-only securities priced from the previous cache; own holdings never get stale prices), stamped with householdSyncedAt, and dropped after 7 days without a fresh sync.
  • API: scope and householdSyncedAt exposed on /positions, /accounts, /balance.

Limitation: a household account holding only cash (no positions) isn't discovered.

Verification

  • bun test: 455 pass, 0 fail; type-check, lint, format:check clean.
  • Three pre-PR review rounds (Opus); all confirmed findings addressed (bb91c7a, 59ba6c2, 8d65534).
  • No live Wealthsimple calls from the branch; household query shapes were verified live by read-only probes before implementation.

Follow-up

HOMELAB-151 — home-assistant-api counts household accounts ("RRSP (Sarah)") and flags stale household data.

🤖 Generated with Claude Code

Plane: WSAPI-13 ## Summary Adds household-visible Wealthsimple accounts (Sarah's RRSP, `rrsp-ZFgB7yct0Q`) to the nightly `sync/balances`, tagged `scope: 'household'`, merged with own accounts by id (own wins, no double-counting — Chris's TFSA is not household-shared). - **Holdings:** `FetchIdentityPositions` with `accountScope: HOUSEHOLD` (new `lib/wealthsimple/household`). - **Cash:** `FetchAccountsWithBalance` 500s for household accounts, so cash = latest daily net liquidation value (`FetchIdentityHistoricalFinancials`) − holdings, priced with the same merged quote map used for valuation. Non-CAD / stale / unparseable NLV and non-CAD quotes fall back to cash 0 with a warning. - **Status:** new `householdBalances` job on `/status`; a household failure never fails the own-account sync, but `SessionDeadError` still halts it. - **Carry-forward:** if the household step fails, the previous cache's household accounts are reused (their own-only securities priced from the previous cache; own holdings never get stale prices), stamped with `householdSyncedAt`, and dropped after 7 days without a fresh sync. - **API:** `scope` and `householdSyncedAt` exposed on `/positions`, `/accounts`, `/balance`. Limitation: a household account holding only cash (no positions) isn't discovered. ## Verification - `bun test`: 455 pass, 0 fail; type-check, lint, format:check clean. - Three pre-PR review rounds (Opus); all confirmed findings addressed (`bb91c7a`, `59ba6c2`, `8d65534`). - No live Wealthsimple calls from the branch; household query shapes were verified live by read-only probes before implementation. ## Follow-up HOMELAB-151 — home-assistant-api counts household accounts ("RRSP (Sarah)") and flags stale household data. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
FETCH_HOUSEHOLD_POSITIONS (FETCH_IDENTITY_POSITIONS + $accountScope) and
FETCH_IDENTITY_HISTORICAL_FINANCIALS, both verified live 2026-09-25 against
Sarah's RRSP, ahead of WSAPI-13's household-visible-accounts sync merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fetchHouseholdPositions (HOUSEHOLD-scope positions + quotes),
fetchLatestNetLiquidationValue (7-day historical NLV window),
householdOnlyAccounts (no-double-count grouping), and
synthesizeAccountBalances (derives cash from NLV minus priced holdings,
clamping small/negative noise) — the operation module sync/balances will
orchestrate next. lib/wealthsimple/securities now exports securityQuoteFrom
and its security schema so both modules share the same node-to-quote
mapping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CachedAccount (AccountBalances & { scope? }) in lib/account-balances-cache,
and AccountTotal/accountTotalFor thread a scope ('own' default, explicit
'household') through lib/wealthsimple/balances, so /balance's per-account
rows can carry which sync path produced them. Downstream test literals in
routes/balance and sync/balances updated to the new required field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lib/sync-status's JOBS gains 'householdBalances' alongside activityFeed
and balances, so a household-sync failure inside sync/balances reports
independently of the own-account balances job on GET /status.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
After the own-account fetch and quote resolution, sync/balances now runs
runHouseholdBalancesSync (sync/balances/household) in its own isolated
try/catch, mirroring the existing quotes-fetch isolation: on success the
household accounts and quotes merge into the own-account set (own quotes
win a duplicate securityId, requestedQuotes/pricedQuotes count the union),
tagged scope 'own'/'household', and householdBalances records success. On
any non-SessionDeadError failure the own-account sync still completes and
writes, and only householdBalances records failure; a SessionDeadError
still propagates and fails the whole run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each account row now carries scope: 'own' | 'household' ('own' for a
legacy cache document with no scope at all), so a consumer (e.g.
home-assistant-api's investments total) can tell a household-visible
account like Sarah's RRSP apart from Chris's own accounts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root CLAUDE.md: sync/balances architecture bullet, lib/sync-status,
lib/wealthsimple tree entry, and the API endpoints table now mention
scope and the householdBalances status job. lib/wealthsimple/CLAUDE.md
gains a full household/ operation bullet covering discovery, the
FetchAccountsWithBalance household error, NLV-derived cash, and the
accepted cash-only-account limitation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Behaviour/correctness:
- Isolate each household account's own net-liquidation-value fetch
  (sync/balances/household); a failure there no longer drops that
  account's holdings, only its cash estimate, and the failed account
  ids are named in the householdBalances failure message.
- Carry forward the previous cache's household accounts when the whole
  household step fails, so one bad night no longer drops them from
  /balance.
- Record householdBalances success/failure after the cache write, not
  inside the household try/catch, so a Mongo error recording status is
  never misreported as a household-fetch failure.
- Merge the own/household quote map (household first, own wins) before
  any cash is derived, so cash and holdings valuation always use
  identical prices.
- lib/wealthsimple/household: non-CAD or stale (>3 day) net liquidation
  values are warned about (the former treated as unavailable); the dead
  cursor variable is gone; a node that can't resolve a security but can
  still be attributed to one account now marks that account so its cash
  estimate isn't silently overstated.
- Household discovery excludes every own account id (open or closed),
  not just the open ones.

Design/DRY:
- lib/account-balances-cache normalizes a missing scope to 'own' in one
  place (get()); lib/wealthsimple/balances stays entirely scope-free
  again, with a single AccountScope type and CASH_SECURITY_ID_BY_CURRENCY/
  round2 exported for lib/wealthsimple/household to reuse instead of
  duplicating. synthesizeAccountBalances now calls holdingsValueFor
  directly instead of re-summing holdings by hand.

Conventions/docs:
- FETCH_HOUSEHOLD_POSITIONS is verbatim against FETCH_IDENTITY_POSITIONS
  again (a dropped __typename restored); FETCH_IDENTITY_HISTORICAL_FINANCIALS
  defaults $accountScope to OWN, matching the verified document.
- CLAUDE.md updated for the carry-forward behaviour and the household/
  module's schema reuse; trimmed comments made redundant by the above.

Tests: SessionDeadError from the household step, own-wins quote
precedence, per-account NLV failure isolation, carry-forward with and
without a prior cache, a pinned UTC date window (including a
near-midnight boundary), tightened warn assertions (accountId/message),
multi-page household positions, float-tail cash rounding, an unpriced
(null quoteV2) position kept in results, and the new NLV sanity checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes household carry-forward pricing/exclusion/staleness bugs (B1-B4), guards
the householdBalances success write (B5), tightens synthesizeAccountBalances'
non-CAD detection (B6), and cleans up two stale comments (N1, N2). Adds a
householdSyncedAt timestamp exposed on /positions, /accounts, and /balance so
a carried-forward household account can be told apart from a freshly-synced
one, and bounds how long it can be carried before being dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[WSAPI-13] Address round-3 pre-PR review findings (C1, C2, N3).
All checks were successful
server / check (pull_request) Successful in 28s
8d65534ea1
Narrows the household carry-forward's quote-map seeding (C1): it now starts
from this run's own quotes and adds a previous-cache quote only for a
security a carried household account holds and no own account holds this
run, instead of seeding from the entire previous securities lookup. The
broader seeding let a stale price silently repersist for an own holding
whenever this run's own-account quotes fetch degraded too (it shares the
FetchIdentityPositions operation with the household fetch, so their
failures correlate), breaking the degraded-run contract consumers rely on.

Also covers a legacy carried-forward account with no householdSyncedAt at
all (C2), and derives the 7-day drop log message from
HOUSEHOLD_CARRY_FORWARD_MAX_AGE_DAYS instead of a hardcoded string (N3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chris merged commit 43f441b330 into main 2026-09-27 09:33:14 -06:00
chris deleted branch feature/WSAPI-13 2026-09-27 09:33:14 -06:00
Sign in to join this conversation.
No reviewers
No labels
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
chris/wealthsimple-api!12
No description provided.