Repository navigation
Releases: smartpointer-com/wealthdb
Release list
Website images
The screenshots on the project page. The Pages workflow downloads them at deploy time, so they stay out of the git history.
v0.7.2
v0.7.2 ties up the loose ends of v0.7 and polishes the surface. Cash
accounts read the rates the banks state. Private-market quarterly
reports feed the exposure look-through. The Income dashboard counts
every income line, and every dashboard's pickers reach every card they
can. The MCP tools and the agent skill say less and answer in one
call. Logs carry no identifiers.
Upgrading
make all. Gold migrations 0159 to 0171 apply on the first
read-write open.schwab-web load,firstcitizens load,ubs-web load: each
re-reads what it already kept. UBS's own rates need oneubs-web download --only rates(a live session).wealthdb reload schwab fidelity svb ubs firstcitizens, plus each
Plaid source.wealthdb web refresh,wealthdb mcp restart.make demo,make demo-web.
Breaking
- Dashboards need gold schema 0171.
yield.withholding: a country may nameaccounts(source, account
id, rate); account wins over wrapper overdefault. A bad value
names its place.account_overridestakesaccount_kindandtax_wrapper_from.- A
schwabsource may nameschwab-webalone; accounts are then
keyed by full number. - Collectors state a cash rate under the canonical keys
interest_rate,interest_tiers,interest_valid_from,
interest_side,interest_conditions. - Columns:
fund_compositions.origin,.valid_until,
fund_lines.region,report_cashflow_plan.silver_source_id,
.account_external_id;web_gainsreads a missing class as
unknown;serving_cash_historykeeps each account's cash per day
and currency. - Income cards renamed: "Income in the window", "Income received",
"Income expected", "Monthly average income"; the twin's yield figure
is "Holdings' income yield". Old names retire at the next provision. - A missing or broken
--env-filestops a collector run; viac exits 1. - First Citizens
login --checksigns in: ask first. - Playwright 1.64.1 (Chromium 156);
make updatemoves the pin, the
pip pins and the user agents together. - A Metabase bump sets
MB_SHA256inweb/Dockerfile.
Yield
- Stated cash rates on the balance: Schwab's sweep, First Citizens'
statement-day rate, Fidelity's core-fund SEC yield, UBS's credit
conditions with tiers. A later-dated rate leaves the one in force. - A card account takes no rate; a liability's rate stays in its terms.
- A per-account withholding rate for a holder whose treaty status
differs from the wrapper's. - Yield inputs read a fund once per instant, not per currency; DV01
sums in a fixed order. The yield pass is about a tenth faster.
Exposure
- ubs-web parses each private-markets quarterly report. A reconciled
report is the fund's region split and its composition until the next
report; before the first, and in an unreconciled quarter, the
declaration applies. A ledger record of the same or a later day wins;
the legal situs follows the same precedence. - Weights above 100 read the excess as cash in the fund's currency,
like a leveraged proxy's leg. - The situs reads a private holding's declared country on every day no
composition of its own is in force. - Exposure ranks every security the look-through reaches in "Top
securities".
Income, spending, cash flow
- A
reimbursementnets against the spending it repays: areimburse
ledger line, else the latest outflow to the payee within a year, else
the payee's merchant category. One no road links leaves both bases. - A deposit on a private-market custody account is capital going in.
- Cross-currency own-account moves pair when a narrative names the
other account and the amounts agree at the day's rate within 1%. - Corporate-action cash carries its stated sign at Fidelity, SVB and
Schwab. - Fidelity: a tax payment is a withdrawal, a tax-prep fee a fee.
- A deposit stated as a holding reads at its balance in the memo and
the coverage report. reload -acarries the cash-flow boundary across the rebuild.- SVB's month-end workbook reaches
returns statedas stated periods. - Plaid: a loosely typed municipal bond reads as a bond; an investment
account with no holding counts at its balance.
Dashboards
- Income counts every income line unless the Type picker leaves it
out, under a new Tax wrapper picker; the expected half adds the cash
flow projection's run-rate for what the holdings do not pay; income
after withholding is every line less the tax. - Gains takes an Asset class picker; unclassed sales count under
unknown, so the classes add up. - Wealth Overview: the headline figures read the latest snapshot
whatever the Time range; a click on the asset class mix opens
Allocation at that day. - Allocation counts cash as a position in every breakdown: the
currency card includes each cash balance in its own currency, and
Top 100 positions lists each account's cash asCash <CCY>. - Allocation reads a day after the history as its last day.
- Every card description is one sentence, at most one caveat, and
names the pickers that do not reach it.
Collectors and tooling
- Logs show an id's last four, no cookie, no raw browser error.
prunekeeps a run folder with no status.- ubs-web reads interest conditions from what the browser renders,
under a route guard; a details redirect is checked against the
allow-list. - One run-selection helper for the loaders; one bad folder no longer
aborts an equityzen load. make lintfollows the ruff pin;make updatestops on a pip error;
make testwrites nothing into the data root; images carry no tests.- MCP
returns view=statedsorts by value. - One legal-form list for the exposure matcher, the bond reader and the
spending fence. - The MCP tool texts are an eighth shorter and name their columns;
the instructions say one call answers, no re-run to confirm, no
hand totals over a calendar or transactions view. - The
wealthdb-roskill is a third shorter.
Docs
- Plan notes read as present limits; the
custodykind, what
report_yield_receivedholds and the override ledger match the
code; exposure precedence is stated once (EXPOSURE.md §9).
v0.7.1
v0.7.1 fixes figures that came out wrong, closes two names that -p
printed and every known defect left after v0.7.0, and makes the
refresh after a load faster. Transfers pair across accounts first,
Schwab's bad overnight quotes and its withholding read right, an
account transfer between brokers reaches the matcher, and a bank
account a run no longer lists ends. The held and sold split of a gain
is the same on every refresh, and a refresh no longer stops on DuckDB's
internal error. The Wealth Overview becomes an overview: beside net
worth it charts the asset classes over time and shows a figure or two
from every other dashboard. A refresh whose inputs did not change takes
seconds.
Upgrading
- Rebuild with
make all. Gold migrations 0150 and 0153 to 0158 apply
on the first read-write open. A gold file the engine creates is in
the format the dashboards' driver reads. Until this release runs, do
not usewealthdb reloadwithout a source,compactorinit:
under DuckDB 2.0 they write a file the dashboards cannot open. From
this release on,wealthdb compactrewrites such a file, andweb refreshrefuses to serve one and says so. - Run
./collectors/firstcitizens/firstcitizens loadand
./collectors/raiffeisen_at/raiffeisen_at load. Each adds a table of
the accounts every run listed and fills it from the runs already
kept. A gold load of either source refuses to run before this step,
and its message says so. - Run
./collectors/refdata/refdata load --forceonce. It re-reads
the GLEIF answers already kept; nothing is downloaded. - Reload the changed sources in one call, which runs the enrichment,
lot, exposure and yield passes once:wealthdb reload schwab ubs firstcitizens raiffeisen_at refdata, plus every source of kind
fidelity(Fidelity and SVB) and each Plaid source. The Plaid
adapter re-reads its silver. wealthdb web refresh, thenwealthdb mcp restart. The first
refresh rebuilds every derived table; the held and sold split of a
few gain rows moves (realized, unrealized and their sum stay).make demo, thenmake demo-web.
Breaking for scripts, config and dashboards
kind: "auto"is gone: no detection existed. A config that names it
fails at load; the setup wizard and validation name the registered
kinds.- Schwab:
mark_spike_pct(default 10) andmark_revert_pct(default
2) on the source set when an overnight mark counts as a bad quote. - The Wealth Overview drops "Cash vs positions over time", "Investment
income by month", "Fees & taxes by month" and "Asset class split",
and their privacy variants. The cash split over time is Exposure's
"Asset classes over time", the income the Income dashboard's, the
fees and taxes Cash Flow's operating outflows. The overview's twin
also drops "Received income" and "Net spend", which read 100 by
construction there. - The dashboards need gold schema 0154. An older snapshot stops the
provisioning with "runwealthdb web refresh". - The
web_transactionsview is gone. Its table,
serving_transactions, and thereport_transactionsmodel stay.
Fixes
- The matcher pairs a transfer across accounts first. Out and back on
one account pairs only as a reversal: legs that read as out and back,
by one signature or reversal wording, within the tolerance. Amatch
line in the transfer overrides still pairs any two. - Schwab: a mark that moves more than 10% from the previous mark and
comes back within 2% of it at one of the next two observations is a
bad quote. The observations between read the previous mark, tagged in
valued_at. A move that stays is never held. - Schwab: non-resident withholding, prior-year and foreign tax,
depositary fees and cash in lieu leavejournaland reach income,
spending and the cash flow. A cash journal between two accounts stays
a cash move. An instrument's class takes refdata's OpenFIGI type
first, so a crypto trust reads as a fund. - An account transfer between brokers, and a transfer between two
Fidelity or SVB accounts, reads astransfer_inortransfer_outby
its signs, so the matcher pairs its two legs. A row whose signs
disagree stays a journal. - A UBS payee cut at the bank's 35-character line takes its remainder
back from the next line, where that line is the rest of a name. - First Citizens and Raiffeisen Austria state each account at every
run that listed it. An account the bank stops listing ends one and a
half of its cadences later; a day's ledger close holds the day's end. - A gain's held and sold split converts a sold lot's start value at the
rate of the newest snapshot before the bucket. Before, it took any
account's rate, and two refreshes of one gold could split a holding
kept in several accounts differently. A holding's sold
lots also sum in a fixed order, soreport_gainscomes out the same
to the bit on every refresh. - The engine turns DuckDB 2.0's caching operator off. On a gold with a
sizeable history it could throw "dict-surviving cache: global
dictionary column N changed identity" in the gains phase. The defect
is DuckDB's (duckdb#27940);
the setting goes once a DuckDB release fixes it. - A failed refresh repeats the engine's own error line and exit status
and says whether a snapshot still serves. It no longer guesses at a
busy or unwritable gold. A gold copy in a storage format the
dashboards' driver cannot read is refused at the snapshot, naming
wealthdb compact. - A security that GLEIF links and a name also matches keeps one link,
GLEIF's, so its size, sector and country come from one share. - A share named on two lines of one size fund takes the heavier line's
size; it no longer depends on the order of the last load. - The three lists of company legal forms are one, so the UBS payee
rejoin, the bond description reader and the spending fence agree. make testruns a Docker collector's tests on a copy of its
checkout. On the VM's file share a file's inode number could change
under memory pressure and stop the suite with "import file mismatch".
Privacy
- A Plaid account's own name is its nickname, so
-pprints it as a
pseudonym; its display name is the institution with the mask. - A cash-flow coverage row whose label is its account's nickname masks
under-p.
Dashboards
- The Wealth Overview shows, beside net worth, the stalest source.
Below a full-width "Net worth over time", "Asset classes over time"
stacks each day's asset classes as shares of that day's total, from
the same holdings history; the Asset class picker narrows the mix to
the chosen classes, which then sum to 100, and a click opens
Allocation. - Under "Over the time range": received income, net spend, the spend
trend, net cash flow, the savings rate and the realized gain. Under
"At the range's end, today and ahead": the unrealized gain, the
returns year to date (TWR and MWR), the income yield, the income
expected over twelve months and the projected net cash flow. Each is
its own dashboard's card. The overview's pickers reach it where that
card has the picker, and its description names the pickers that do
not. A click opens its dashboard with the pickers both share; a
year-to-date return opens Returns at its year. - The privacy twin shows the same figures as their dashboards' twins
do. The gains read as a percent of their cost basis, and a click on
them opens nothing, as Gains has no twin. - Allocation's As of day defaults to the last day the holdings history
reaches, not to today, so a gold not refreshed today shows its latest
holdings instead of empty cards. Its "Top 100 positions" shows a
stock's size (large, mid, small), the one Exposure gives the share. - Metabase's hourly field sync no longer logs a warning on the list
columns of the yield tables.
Exposure and reference data
- An issuer whose legal name is in another script reads as its legal
name in English, else GLEIF's transliteration. The legal name stays
in the record. Holdings that name the issuer in Latin now join its
LEI. - The divergence check compares a proxy only on what its own file
states. A proxy whose file gives no sector or country is no longer
reported as diverging on it.
Speed
- A refresh does only the work that changed. When nothing was loaded
since the last one, it reuses the derived tables and sends Metabase
nothing, so it takes about three seconds. After a load, a refresh
takes about a quarter less time: the exposure joins hash, the yield
writes bind one list per column, and the wait for Metabase polls
every quarter second. - Naming several sources to
reloadresets and loads each of them,
then runs the enrichment, lot, exposure and yield passes once. Seven
sources reload in about 40 percent less time than seven single
reloads. make allbuilds the engine and the images together and skips an
image whose inputs did not change.make testruns the suites
together.- Each pass of a refresh, a load and a reload prints how long it ran,
and a refresh times its steps outside the materialize too: the config
read, the engine's key check and open, the container's stop and
start, the wait for Metabase and provisioning.
Docs
- fidelity-web, schwab-web and chase say that a personal Plaid account
reads balances, holdings and transactions through theplaid
collector, where Plaid covers the institution. - First Citizens: an incoming wire's row names no originator on any surface the collector reads.
v0.7.0
wealthdb now shows what the holdings earn and how they move with
interest rates. The new yield report gives each holding's income
yield, implied yield and realized yield, its duration and DV01, and a
cash balance against its currency's benchmark, per currency and entity,
as of any day. income gains forward views: what the holdings are
expected to pay, month by month, beside what was received, and cashflow projection sets that income against the household's average outflows to
project the months ahead. A dividend calendar read from the fund
issuers, FINRA, SIX, JPX and the SEC gives each payment the day that
decides who is paid. exposure groups the debt by credit, maturity and
coupon, the stocks by cycle, and everything by issuer. Privacy mode
names every value a person typed by a pseudonym. All of it is on the
command line, over MCP, in the read-only skill and on the dashboards.
Upgrading
- Rebuild with
make all. Gold migrations 0134 to 0149 apply on the
first read-write open. Two collectors re-parse their documents on
their next load:fidelity-webits supplied statements, andsvb
every statement, which runs OCR again on the deposit and mortgage
statements and takes a while. - Fetch the reference data the reports read.
refdata: its silver moves to version 3. Run
./collectors/refdata/refdata load --forceonce to re-read the
bronze already kept into the new columns, thendownload --backfillfor the facts the wanted list names.sec-nmfp(SEC
money-market filings, the same terms as the other SEC providers)
is one more provider to enable inrefdata.cfg. To read the
dividend calendar, also namevanguardbeside the issuers and
finra,sixandjpxaftersec-edgar. Enabling one accepts
its source's terms, quoted in the collector's README.finra
needs a free FINRA Public API credential in
~/.secrets/finra.env(FINRA_CLIENT_ID,FINRA_CLIENT_SECRET);
without it the provider asks FINRA nothing. Thenrefdata download,refdata loadandwealthdb load refdata.fredfetches the Treasury curve beside the exchange rates:
./collectors/fred/fred download --lookback 2015-01-01,load,
thenwealthdb load fred.curves, a new collector, fetches the euro and franc curves from
the ECB and SNB data portals. It needs no key. Add acurves
silver source towealthdb.cfg, then./collectors/curves/curves download --lookback 2015-01-01,loadandwealthdb load curves. Enabling it accepts the portals' terms, quoted in its
README (the SNB's: non-commercial use, with the source named).
- Add a
yieldblock towealthdb.cfg:curvesnames the series
per currency and tenor.withholding,rating_funds,
conventions,cycles,settlement_cyclesandhorizon_months
are optional, as iscashflow.projection.lookback_months. Without
a block the reports still run: a currency with no curve has no
benchmark and no forward path, and says so. - Describe what no source states, if you want: the declarations
ledger takesrate,rating,dividendandtermsrecords.
wealthdb yield coverage -f ndjsonprints a skeleton for every
holding still missing one. - Run
wealthdb reload fidelityandwealthdb reload ubs. They link,
gross and reverse what is already loaded, and book a UBS
confirmation's function, rates and copies anew. Reload UBS before
its next incremental load. An installation with thesvbsideload
also runs./collectors/svb/svb loadandwealthdb reload svb: the
month-end workbook's marks are renamed, and a config or ledger
entry that named the mark's gold key moves to the new one,
fidelity-hist:5b9a9118c2681265. - Run a
wealthdb load(wealthdb load refdatawill do) before
wealthdb web refresh: the load builds the learned record-day
gaps. Thenwealthdb web refreshandwealthdb mcp restart. The
refresh fills the yield and projection tables and takes a
few minutes more. make demorebuilds the demo household, which holds every kind the
reports read and now states a dividend calendar.
Breaking for scripts and config
- A Schwab fund whose name abbreviates "Fixed Income" ("Fxd Inc",
"Fixed Inc") reads as fixed income.wealthdb reload schwabapplies
it to the snapshots already loaded. - The refdata wanted list is version 3. The collector and the engine
must come from the same build. refdata holds one credential,
FINRA's, read from the environment only. -pprints a typed name, key or id as a pseudonym (manual-1,
cointracking-2,nickname-1,category-1,angellist-1), stable
within one run or one MCP call. Before, some printed in full and
others as***.wealthdb cashflowhas a sixth view,projection, and the flags
--rates,--maturingand--shift. The other views refuse them.exposure credit,maturities,couponsandcyclesgain a
class_sharecolumn.exposure --periodrefusesissuers, as it
refusessecuritiesandfunds.yield coveragegains apaymentscolumn and arecord_day
section:stated,derived,learned,lag,noneand the
readings the window drops.- The Income dashboard has no Window picker. "Income by month" and
"Income by account" are retired. - MCP refusals of the
incomeandyieldtools read as the command
line's do, and start with the tool's name. - The
svbsideload's derived mark carries the marker
derived-workbook-markand the descriptionACCOUNT VALUE (WORKBOOK MARK); its gold instrument key changes with the description.
Yield
wealthdb yield <view>:global,sources,portfoliosand
accounts, one row per entity and currency with a value-weighted
total;positions;securitiesthrough funds;funds;cash
against its benchmark;curve, DV01 by tenor and by what holds it;
andcoverage.--convertedgives one row per entity,--perioda
series. Seewealthdb/docs/YIELD.md.- A bond's yield, duration and DV01 come from its terms: coupon,
maturity, frequency, call and rating, read from its source, its
statement, its description, OpenFIGI and the market's convention.
Bills, zeros, callable and floating notes, term deposits, tiered
savings, money-market funds, bond and equity funds, stocks and
mortgages each read the figures that fit them. - A cash rate is the one its bank states (UBS, Raiffeisen, VIAC,
Plaid), else a ledger record's. A stock's dividend is a ledger
record's, else its last payment at its frequency, else its filings'.
A payment counts per share of what its account held on the record
day, in today's units after a split or a depositary ratio change. A
special dividend stays out of the rate. A payer twice a year pays its
interim and its final, and reads its one rate once a year, tagged
partial, where the other half has no rate. - A dividend meets its holding under every id the source or the config
gives the instrument. A security held at several sources pools its
income and days. A bank's trades booked on a cash account move the
holding of their portfolio. - A fund's kind follows what it holds: filed rate risk or mostly debt
makes a bond fund, whatever the broker calls it. A floating-rate
fund floats: it resets with the curve and moves with--shiftand
--rates forward. A multi-asset fund has its rate risk measured as a
bond fund's and its income as an equity fund's. DV01 lands on the
currency of the rate it moves with. - Realized yield divides the year's income by the mean daily balance.
A position's is its security's, over every account, on the days
held, and readspartialwhere that is less than the year. A
balance that is thin most of the year reads no figure and the tag
thin. A holding that rounds to no cent has no row. - A floating rate never earns below zero, in the projection, under a
rate path or a shift, and on the dashboards. A balance a bank
charges for reads nothing, not a charge. - A
qualitycolumn says what each figure rests on, andcoverage
what is still missing.-phides amounts and ids.
Income
wealthdb income projection,calendar,sources,portfolios,
accountsandpositionsput the expected income beside what was
received, over last year, this year or the coming months: interest,
coupons, dividends, distributions, rent and pensions, and maturing
principal on request.income positionslists each holding's
expected payments, so it sums toincome projection.--rates constant|forwardholds today's rates or follows the forward
curve,--maturing cash|idle|rollsays what principal does at
maturity, and--shiftmoves every floating rate. The withholding
table gives what is withheld at source.- A distribution's frequency counts the months of the calendar paid per
month held. Two halves of a payer twice a year read as such; a fund
held under a year reads its stated or trailing yield; a quarterly
floating fund pays on its own days.
Dividend calendar
refdatareads each held fund's distributions from its issuer:
iShares (US and Swiss sites), SPDR (US and EMEA), Invesco (US, and
UCITS from run to run), Xtrackers, Amundi and Vanguard (US). It reads
an OTC depositary receipt's dividends from FINRA's OTC Daily List, a
Swiss share's from SIX, a Japanese share's from JPX's ex-rights list,
and the dividend dates a US company's filings tag from EDGAR. The
EDGAR reader parses a filing's inline XBRL documents only.- FINRA asks only the receipts held, only the fields it reads and only
the days after the newest event it has. It keeps a tally of each
month's bytes and stops before 8 GB of the 10 GB cap...
v0.6.0
wealthdb now shows what is really owned. The new exposure report looks
through every fund to the securities it holds, adds what is held
directly, and answers by asset class, sector, country, currency and
security, as of any day. It also says where each holding legally sits.
The fund data comes from public sources through a new collector,
refdata. The report is on the command line, over MCP, in the
read-only skill and in a new Exposure dashboard.
Upgrading
- Rebuild with
make all. Gold migrations 0125 to 0133 apply on the
first read-write open. Nothing re-parses and noreloadis needed. - Set up reference data. Add a
refdatasilver source to
wealthdb.cfg, and write~/.config/refdata.cfgwith the providers
to ask and thecontactthe SEC requires. Then
./collectors/refdata/refdata download(--backfillalso fetches
the months each fund was held),./collectors/refdata/refdata load
andwealthdb load refdata. Without it the report still runs: every
fund is then a leaf of its own andexposure coveragesays so. - Describe what no public source knows, if you want: the
exposure
block ofwealthdb.cfgand a declarations ledger.wealthdb exposure coverage -f ndjsonprints a skeleton record for every
holding still to declare. - Run
wealthdb web refresh, thenwealthdb mcp restart. The
dashboards read new serving tables that the refresh fills. A
running MCP server refuses a schema newer than its binary. make demorebuilds the demo household. It has a synthetic
reference-data source, so every exposure view and the dashboard
work on it.
Breaking for scripts and config
wealthdb web start,restartandrefreshexit 3 when the gold
snapshot is older than the dashboards need, and say to runweb refresh. Before, start printed a wrong hint and exited 0.- The transfer-override ledger refuses a column it does not know, as
the pins ledger does.noteis still allowed. - A collector's
prunerefuses--silver-db. A relative--data-dir
or--secrets-dirmounts that directory of the host; before, Docker
made a named volume of it. - A collector
loadnever reads a download without a manifest
(run.json), also in the passes that re-read the whole bronze tree. - UBS advice, custody and ADR handling charges are investment fees.
They were other bank fees. The spending figures move between those
two categories on the next load. - A malformed
relationships[].psn_start_overrideinwealthdb.cfg
fails every command at config load, not only the UBS one.
Exposure
wealthdb exposure <view>reports what is really owned, as of a day
or at the end of every period of a window (--period):classes,
sectors,sizes,countries,currencies,situs,securities,
fundsandcoverage. Every view sums to net worth asholdings globalgives it. Seewealthdb/docs/EXPOSURE.md.- The look-through replaces a fund by its lines, a fund of funds down
to three levels, and keeps what the weights leave unexplained as a
remainder. Each holding joins the fund composition nearest its day,
withinexposure.max_gap_days(default 400). - A security's sector and country come from what the sources say about
it: its issuer's filings, GLEIF, OpenFIGI, the fund lines that name
it. A depositary receipt reads as the share it stands for. A bond
held directly reads its type, else its description. Public equity is
sized large, mid or small by market cap, and where no market cap is
known, by the weights of broadsize_funds. countriesgives the country of risk;--country domicilethe
country of incorporation.situsgives the one country whose law
each holding is an interest under, with no fund looked through: a
fund at its domicile, a share at its issuer, cash at its bank, a
property where it stands, each with the basis it rests on.currenciescounts a metal and a coin as currencies of their own:
XAU, BTC.- A declarations ledger states what no public source can know: where a
private fund's value is, by region, country, currency, sector and
asset class, with shares; and where any holding legally sits. One
JSON record per line, dated to the document it comes from. A record
never goes stale, and a misspelt key is refused. - The config's
exposureblock namesproxies(a fund that stands in
for another, withalwaysandleverage),reference_funds,
size_funds,declarations,custody(who keeps each source's
assets, and in which country) andmax_gap_days. - What no source, declaration or proxy describes reads
unknown,
never a guess. Aqualitycolumn on every view and thecoverage
view say what the figures rest on: how each part of net worth is
classified, how old each composition is, the labels no map knows,
the securities no source describes, the private holdings no
declaration describes, and what each situs rests on. -phides every amount and every instrument id a holding is named
by; shares, names and labels stay.- The MCP server has an
exposuretool with the same views. The
read-only skill has recipes for allocation, sectors, countries, one
company through every fund, situs and coverage. - An Exposure dashboard in Metabase: net worth by asset class and by
sector, the classes over time, a world map of the countries, the
regions with their countries and over time, legal situs, the
currencies, public equity by size, direct against through funds, and
how the figures are classified over time. Every figure is a share,
so the privacy twin shows the same charts. Pickers: currency, time
range, source, tax wrapper and asset class, so real estate and its
mortgages can be left out, or public equity read alone.
Reference data
- The
refdatacollector fetches what each fund holds and what each
security is. It reads only what gold asks for:wealthdb refdata-wantedprints the funds held, the funds the config names,
the funds a held fund holds, and the securities held directly or
through funds. Seecollectors/refdata/README.md. - Providers: an inbox for a composition obtained some other way, SEC
N-PORT filings, SEC EDGAR, GLEIF and OpenFIGI, which run by default;
and, oncerefdata.cfgnames them, the issuers iShares, Xtrackers,
Invesco, Amundi, SPDR and Sprott, the report readers Swisscanto and
swissfunddata, and Fidelity 529 plans, which look through to the
plan's funds. Enabling a provider accepts its terms, quoted in the
README. - A fund without a daily file reads from its reports. A security a held
fund names but no source describes is looked up by the biggest share
of net worth first, andconstituent_floor_pctsets where to stop. - There is no login anywhere. Each provider sees which funds and
securities are asked about, and never an amount, an account or a
bank. The root README names it among what leaves the machine. --dry-runplans a run,--provider,--refand--limitnarrow
one, andlogin --checkasks each provider for its cheapest answer.- An engine too old for the wanted list, one that cannot run, or one
that times out is a plain error. An inbox file whose holdings go on
past a row of one cell is refused rather than read short.
Dashboards
- The dashboards read serving tables that
web refreshfills, instead
of running each report's whole history on every tile. The heaviest
tile went from 3.3 GB of memory to under 200 MB, and the Metabase
server no longer runs out of memory under a full set of tiles. - Wealth Overview and Allocation have a tax wrapper and an asset class
picker. An account that states no tax wrapper reads
taxable_personalon every dashboard, Gains and Exposure included. - Stacked bars and areas order their bands by size, largest at the
bottom, and the legend lists them in that order. The ordering is an
aggregate joined back, so a picker that contradicts a card's own
filter gives an empty chart instead of a driver error. - A holding's name, symbol and class read the same on every refresh.
- Six privacy models nothing read are retired, as are five Exposure
cards of earlier builds.
Reports and the engine
wealthdb helplists each report's views from the report itself.- The
categorizeaudit lists the pairs an override, a reference or a
stated counter asserts ahead of the named ones, so its cap never
drops them. - Config validation names the same first error on every run.
- A real gold can be checked after a
reload -a: the opt-in harness
asserts that the reload moved no money and that every position
carries an admitted class.wealthdb/docs/DESIGN.md§12.5 has the
three commands.
Collectors
- carta keeps its fund ledger across a
--no-documentsrun, and reads
a blob that is not a PDF as not downloaded. - ubs-web keeps every portfolio's trades when an export repeats.
- amex retries a statement rebuild that did not finish.
- swissquote skips a dump whose
run.jsoncannot be read, with a
warning, and loads the rest. - chase drops an export whose window or format pick did not take,
instead of saving it under the wrong name. - firstcitizens no longer submits an empty sign-in form. schwab-web
exits non-zero on an interrupt or an empty walk. - No 2FA code or device name reaches a log.
--helpstarts no VNC
server.
Build and docs
- A checkout pins its own image tag and demo household in
.image-tag
and.demo-root, so a second checkout works on images and a demo of
its own. The demo's containers are named after its root. - Docker collector tests run in their image with no secrets and no
data mount, so a fresh clone passesmake test.pipreruns only
when a venv's inputs change.make cleanreach...
v0.5.0
wealthdb now tracks cost basis. Gold holds the cost basis every source
states, rebuilds it with a lot engine where a source states none, and
reports realized and unrealized gains on the command line, over MCP and
in a new Gains dashboard.
Upgrading
Back up the data root first: several collectors re-read their bronze.
- Rebuild with
make all. Gold migrations 0113 to 0124 apply on the
first read-write open. - Load each collector's silver once from bronze, so the new cost-basis
columns and lot tables fill for past downloads:
./collectors/<name>/<name> load --forcefor angellist, carta,
cointracking, manual, schwab-api, swissquote, ubs-psn and ubs-web.
fidelity-web, schwab-web and svb re-read their archive by themselves
on the nextload, because their parsers changed. - Run
wealthdb reload -a. It fills the cost basis, the lots and the
lot engine's ledger for every source. - Run
wealthdb web refresh, thenwealthdb mcp restart. A running MCP
server refuses a schema newer than its binary.
Breaking for scripts
- Percentage headers no longer carry a
%.twr_%,twr_ann_%,
mwr_%,mwr_ann_%,share_%andsavings_rate_%are now
twr_pct,twr_ann_pct,mwr_pct,mwr_ann_pct,share_pctand
savings_rate_pct. This changes the keys of-f jsonand the headers
of-f csv. - Env files are sourced by bash. Quote and escape values the way bash
expects. A password with$, a backtick or a backslash belongs in
single quotes. - MFA waits (
--mfa-timeout) default to five minutes. - A Schwab web feed without the Schwab API feed is refused at load. The
web rows reach accounts only through the API's account numbers.
Cost basis
- Every book value in gold carries a stamp saying what kind of cost
basis it is: its origin, its method and whether purchase fees are in. - Gold stores each source's open lots and realized lots, one row per lot
per tax document. One set is marked primary, so a sale counts once. - The collectors read the cost basis their sources state:
- Fidelity and SVB statements, 1099-Bs and the positions page's lots;
- Schwab statements' tax lots, realized lots and 1099-Bs, and the API's
average cost; - UBS holdings' book cost and statement trades;
- Swissquote's average cost and bonds' accrued interest;
- Plaid's tax lots;
- the capital paid in for Carta, EquityZen, AngelList and the manual
book, with K-1 property distributions taken off.
- Market value includes accrued interest and declared dividends. The
accrued part is reported apart.
Lot engine
- Where a source states no cost basis, a lot engine rebuilds it from the
trades: the open lots, a cost basis on every position, and a realized
lot for every sale. It covers CoinTracking, the SVB statements, and
the history Fidelity and Schwab leave without lots. A stated figure is
never replaced. Seewealthdb/docs/LOTS.md. - First in first out by default. Last in first out, highest or lowest
cost first, or the average can be set per source, portfolio or account
in the newlotsblock of the config. - A fourth mode,
stated, covers the private markets: Carta, EquityZen,
AngelList and the manual book. Each sale realizes against the cost
basis the position states, pro rata by quantity, net of its fee. An
SPV's payout on AngelList is its sale. - The manual book takes an
exits.csv: what a holding brought on the day
it left the book. Gold books it as a sale. lots.basisin the config states the unit cost and acquisition date of
an instrument's lots the engine cannot cost, such as shares that reach
a custodian from a transfer agent.holding_aliasesin the config names a holding a source keys two ways,
so the gains reports read it as one holding.- Seeds, implied disposals and snapshot blips name where the history
does not explain a holding. A later statement's lots resolve a seed
back to its opening. wealthdb lots rebuildruns the engine by hand. Everyloadand
reloadruns it, and skips it when nothing changed.
Gains
wealthdb gains <view>reports realized and unrealized gains over a
window:summary,sources,portfolios,accounts,positions,
realized,lots,coverageandcheck. The aggregate views add
up. Aqualitycolumn names every gap, andcoveragesays per
account where the figures are blind. Seewealthdb/docs/GAINS.md.wealthdb gains checkcompares the rebuilt figures with what the
sources state.--missing-basis ignore|zeroreads a missing cost basis as blank or as
0. The config'slots.missing_basissets the default.wealthdb holdings positionsoffers the cost basis, its stamp, the
clean value and the unrealized gain as opt-in columns.wealthdb returnsshowsgain_<CCY>, the money behind the
percentages.- The MCP server has a
gainstool with the same views. - A Gains dashboard in Metabase: realized and unrealized gains, the gain
by month split into positions sold and held, the largest winners and
losers, gains by source, asset class and tax wrapper, a tax-year table,
and the cost basis coverage per source. A picker reads a missing cost
basis as blank or 0. It has no privacy twin.
Freshness
- Gold records when each source was last read, not only when its latest
fact is dated. A portal that marks quarterly and was downloaded today
is current. - The Data Freshness dashboard counts a source's age from its last read
and shows how long it has been quiet.wealthdb status, the MCP
statustool and the opt-inholdings sourcescolumnobserved_date
show the same date. - A holding that AngelList, Carta or EquityZen stopped listing ends at
the download that no longer listed it. A download counts only when it
read the whole portfolio. The other holdings keep their mark dates, in
an opt-invalued_datecolumn.
Reports and dashboards
- GBP is a fourth reporting currency beside USD, CHF and EUR. The
dashboards open in the config'sdefault_currency. - The Wealth Overview and Allocation dashboards have a currency picker.
- The cumulative return chart uses a linear percent axis.
- Every subcommand accepts flags after its positional arguments.
--
ends the flags. wealthdb configworks through the wrapper.
Collectors
- Loads skip downloads that never completed, and load each download in
one transaction, so a failed load leaves silver unchanged. - Collector code repeated across the fleet lives once in collectorkit.
exploreand the login pre-fills never log a typed or clicked field's
value. Error logs no longer echo session cookies or signed URLs.- The schwab-api refresh token is written 0600 from the first byte.
login --checkmakes an authenticated call. - schwab-web keeps one id for a transaction across overlapping exports,
and loads rows dated "as of" an earlier day. Statement holdings add up
to the printed account value. - fidelity-web: one failed phase no longer aborts the whole download.
- carta finds the portfolio when the app lands on another page.
- AngelList reads the portal's renamed GraphQL operations.
- chase waits longer for its sign-in form.
- UBS: incremental loads reach the business day of a new PSN report.
ubs-web keeps partial downloads it loaded. - firstcitizens and raiffeisen_at keep a quiet account's current
balance in the latest holdings. - Swissquote re-sends its push every minute.
- Swissquote and Relevate require pypdf 6.19 or newer.
Web
- The admin password reaches provisioning in the environment, never on
a command line. - Provisioning stops when a Metabase listing fails, instead of creating
duplicates.
Build and docs
- Gold migration 0124 drops report macros nothing reads.
make demobuilds the demo household with open and realized lots, and
a crypto account whose cost basis the lot engine rebuilds.- Every README, DESIGN and AGENTS doc was checked against the code. Each
mechanism is explained once and linked from the others.
v0.4.0
Upgrading
- Rebuild with
make all. Gold migration 0112 applies on the first
read-write open. It changes only the order of tied rows in
cashflow coverage. No reload is needed. - The MCP server is optional. It stays off until
wealthdb.cfghas an
mcpblock with"enabled": true. A config without the block behaves
as before. - A demo directory built by an earlier release has no
mcpblock. Run
make demobeforemake demo-mcp.
MCP server
- A new component,
mcp/, serves the read-only reports to AI agents
over the Model Context Protocol.
A model asks what was spent on groceries last year and gets the table
wealthdb spendingprints.wealthdb mcp startruns it in the engine image, on loopback port
3300.wealthdb mcp urlprints the URL and the header for a client.wealthdb mcp stdioserves one session to a client that starts the
server itself.status,restart,logsandstopmanage it.make demo-mcp
serves the demo household on port 3400.
- Eleven tools:
holdings,returns,transactions,spending,
income,cashflow,status,snapshots,resolutions,
categorizationsanddescribe. Every parameter is optional. The
tools filter, sort and page their rows, so a model needs no shell and
nojq. - The tools are written for small local models as well as large ones.
One call usually answers a question:- a filter picks the view that has its column;
- a result is one row per account, category or type for the whole
window, unless the call asks for monthly or yearly rows; - money sorts by size, whatever its sign;
- a misspelt column name resolves when one column is that close;
- every result states the dates and the currency it used.
- It reads the live database, read-only, for one call at a time. There
is nothing to refresh after a load. A test holds the report tools'
results to the CLI's, cell for cell. - Two endpoints.
/mcpserves full data./mcp/privacyapplies the
CLI's-pto every result, and no tool parameter can turn it off. It
hides amounts, quantities, account numbers and the names taken off
statements. Account names, holdings, categories and dates stay
legible.wealthdb mcp stdio --privacyis the same over stdio. - It is locked down by default:
- it has no tool that writes;
- every HTTP request needs a bearer token, generated on first start;
- turning the token off takes both
"auth": "none"and
"insecure": true, and the server warns while it is off; - it is published on loopback only, and refuses a foreign Host or
Origin; - the data and the config are mounted read-only, in a container with
a read-only file system and no capabilities.
- Claude Desktop runs it from its config file, over stdio, also on
another host over SSH. Its custom-connector dialog cannot reach a
local server.mcp/README.mdhas both configs. - After a later rebuild of the image, run
wealthdb mcp restart. A
running server keeps the old binary, andwealthdb mcp statussays
so. - See
mcp/README.md. Read its security section before you give a
model the URL.
Help
wealthdb,wealthdb helpand--helpsay what the database holds.
The listing puts the read-only reports first and says what question
each one answers. The write commands follow, labelled as such.wealthdb help <command>prints the command's full help, the same
text as<command> -h.wealthdb help <view>does the same for a
holdings view.- The help of each report says more:
- holdings include property, loans and private holdings, and a loan
is a negative value; - mortgage payments and plan contributions are in
cashflow flows,
not inspending; - in the line views, money leaving is negative;
- a category someone names, such as groceries, is a detailed one;
--period totalgives one figure for a whole window.
- holdings include property, loans and private holdings, and a loan
- The
wealthdb-roskill gains a recipe for each common question, and
rules for--period total, JSON keys and signs. It points an agent
without a shell to the MCP server.
Engine
- A command that finds the database locked by another one waits up to
nine seconds for it. It failed at once. This covers aloadthat
meets a report in progress, and a report that meets a call to the MCP
server. cashflow coveragelists tied rows in a fixed order, so two runs
print the same table.
Build and docs
- The engine gains one dependency, the official
MCP Go SDK, and the
modules it needs. The other Go dependencies are updated. make test-mcptests the server's lifecycle script, andmake test
runs it.AGENTS.mdgains rules for documentation, login flows, env files and
private narrative.
v0.3.3
Upgrading
-
Rebuild with
make all. Gold migrations 0109–0111 apply on the first
read-write open. No reload is needed: the nextloadre-applies the
categories. -
Config change. Five category values are retired. A config rule or
a pin that names one fails at load, and the error names the value to
use instead:retired use instead INCOME_WAGESINCOME_SALARY, orINCOME_GIG_ECONOMYfor gig-platform payINCOME_OTHER_INCOMEINCOME_OTHERINCOME_SELF_EMPLOYMENTINCOME_CONTRACTORINCOME_RENTINCOME_RENTALINCOME_ALIMONY_AND_CHILD_SUPPORTINCOME_CHILD_SUPPORT, orINCOME_ALIMONYfor maintenance from a former partnerEdit
wealthdb.cfgand the pins ledgers when you rebuild. The earlier
build refuses the new values, so make both changes at the same time.
Migration 0111 moves the stored verdicts the same way. A stored
verdict on the split value becomesINCOME_CHILD_SUPPORT. -
Optional:
wealthdb categorize income --allasks the model again about
every payer, so it can use the new income values. Without it, existing
verdicts stay as they are. -
A demo directory built by an earlier release needs
make demo.
make demo-rollrefuses it.
Plaid
- A new collector,
collectors/plaid, reads the banks, brokers and card
issuers that Plaid reaches:link --item NAMElinks one login at one institution. You sign in
on Plaid's own page, in your browser.login --checkreports the state of every link and changes nothing.downloadsaves accounts, balances, holdings, transactions and loan
terms.loadbuilds one silver database per link, andprunetrims
old downloads.
- It is read-only. A link requests data products only, never a payment
or a transfer product. - The app keys live in
~/.secrets/plaid.env, and each link has its own
token file there, mode 0600. download --refreshasks Plaid to fetch fresh holdings from the
institution first. Plaid bills that call on a paid plan, so it runs
only whenbilled_readsin$XDG_CONFIG_HOME/plaid.cfgnames it.- Gold reads each link as one source of the new silver kind
plaid.
Accounts take their kind from Plaid's account type, and rows take
their category from Plaid. - See
collectors/plaid/README.mdandwealthdb/docs/adapters/plaid.md.
Read the collector's security disclaimer before you add a credential.
Categories
- The category list is Plaid's Personal Finance Categories, version 2.
Its values and descriptions are copied word for word, under 13 of its
18 primaries. wealthdb adds a value only where Plaid has none, such as
investment fees, withholding tax, staking, rewards and alimony. - New values: late fees, cash-advance fees, salary, gig economy,
contractor, rental, child support, veterans benefits, long-term
disability and alimony. - Labels that change in reports:
- Wages becomes Salary.
- Rent becomes Rental.
- Self employment becomes Contractor.
- Alimony and child support becomes Child support or Alimony.
- In the cash flow statement, gig-economy and contractor pay are
earnings, rental income is yield, and child support, alimony,
veterans benefits and long-term disability are benefits. - A row the provider filed as a bank fee or a debt payment is no longer
read as a cash withdrawal. A built-in fee rule no longer overrides a
fee the provider named. - A card's fee that nothing else placed is a bank fee. It read as an
investment fee.
Build and docs
- Pins and transfer-override ledgers are read before gold opens for
writing, so a bad ledger fails before anything loads. reload -agives carried verdicts their current category names.- An env-file error no longer prints the line it failed on, which may
hold a secret.
v0.3.2
Upgrading
- Rebuild with
make all. Gold migrations 0107–0108 apply on the first
read-write open. No reload is needed.
Demo
make demobuilds an invented household into its own directory,
~/wealthdb-demoby default: three years of one family's accounts,
holdings, transactions, returns, spending, income and cash flow,
written as synthetic silver sources and loaded through the ordinary
initandload -a, so every report and dashboard shows real
behaviour.make demo-webserves its dashboards on
http://127.0.0.1:3100/in a container of their own, and
make demo-web-stopstops them.make demo-rolladds the days since the last build and loads only
those. History never changes between runs.WEALTHDB_DEMO_ROOT,AS_OF,SEEDandFINDINGS=1select the
directory, the end date, the random seed and a build with a few planted
imperfections for the diagnostic surfaces: uncategorised purchases, an
unpaired transfer, a coverage gap and a stale source.- Nothing in the demo comes from real data. Every person, institution,
merchant, company, fund and ticker is invented.demo/disjoint.py
lists every identifying value a demo gold shares with another gold,
anddemo/check_dashboards.pyruns every dashboard card against a
Metabase and reports failures. - A new silver kind,
synthetic, reads a silver shaped like the
canonical model itself: one table per record type, passed through
with the usual guards. It is generator-fed, its runs are append-only,
and a load takes only the runs past its watermark. A row's provider
category is a taxonomy value and is filed as such. See
wealthdb/docs/adapters/synthetic.md.
Reports
- Cash flow coverage: an account whose transactions reconcile exactly
with its balances readsmeasuredwith a gap of zero. It read
obscuredbefore. - Data Freshness dashboard: the declared accounts' source no longer
shows as the stalest source. It has no feed and no snapshot.
Build and docs
- Go dependencies updated.
- The README describes the demo and how to try it.
v0.3.1
Upgrading
- Rebuild and reload:
make all, thenwealthdb reload -a. Gold migrations
0104–0106 apply on the first read-write open. Many adapters changed what
they emit, so a plainloadis not enough. - AngelList: rebuild the silver once with
./angellist load --force. The
loader now follows the portal's own marks. - The Go module path is now
github.com/smartpointer-com/wealthdb/wealthdb. - Contributors: run
make hooksonce. A pre-commit hook then refuses a
commit thatmake lintfails on or rewrites.
New configuration
declared_accountsnames an account the household owns at an institution
no collector reads. Give it a kind and a tax wrapper, and moves to it stay
inside the household's cash.faron a spending or income rule names the account on the other side of
the move the rule places. It can name a declared account.spending.internal_transfer_matching.namestells the matcher which account
a narrative names as the other side of a move, so the two legs pair with
each other rather than with a nearby look-alike.
Reports
- Cash flow: the residual node
Untracked accountsis now
Unpaired transfers. It splits intoNo far accountand
Fenced-out account. Every row there is a missing counter-leg or an
account not yet declared. - Cash flow coverage reads in-kind transfers and stopped balance series
honestly. - Returns: cash accounts emit no rows of their own at any grain. A portfolio
or source that holds only cash accounts disappears with them. - Income: new type
INCOME_ENERGY_FEED_IN. Benefits no longer include tax
refunds. wealthdb versionprints the bare tag only for a clean build of a release
tag. Any other build prints<last release> nightly <commit>.
Sources
- UBS: accounts outside the MT940 delivery keep their export rows. A currency
conversion the statement describes books its other leg. Dividends paid into
such accounts are booked from the corporate-action confirmation. A renamed
security no longer duplicates its movement. - Schwab: short sales, covers, fees and withholdings get their own kinds. A
dividend clawback nets instead of booking twice. The api/web cut starts
where the api history starts. - Fidelity: a correction keeps its sign. A closed account's wind-up gets its
missing out-legs. Supplied-statement fees no longer double the feed's. - SVB: deposit accounts are typed
cash. Renames, conversions and reverse
splits book as corporate actions. - AngelList: an exit paid in shares pairs with the broker's receipt through
the equity-transfer ledger. Positions follow the portal's marks. - Chase: an export cheque takes the payee its statement prints.
- First Citizens: memo-posted items and re-issued ids are stored once. The
bank's own category reaches gold. - EquityZen: a distribution's fee is its own leg.
- Swissquote: login detection fixed.
- VIAC: reads the tagged decimals the wealth summary now sends.
Build and docs
make lintruns gofmt, go vet and ruff, and the tree passes it.- Collectors share money, statement-period, document and sign-in helpers in
collectorkit. - Agent rules live in
AGENTS.md.CLAUDE.mdis a symlink to it. - The README describes what the suite does, with examples.