Skip to content

Releases: smartpointer-com/wealthdb

Website images

Website images Pre-release
Pre-release

Choose a tag to compare

@ptu-gh ptu-gh released this 31 Oct 12:54

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

Choose a tag to compare

@ptu-gh ptu-gh released this 30 Oct 10:31

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

  1. make all. Gold migrations 0159 to 0171 apply on the first
    read-write open.
  2. schwab-web load, firstcitizens load, ubs-web load: each
    re-reads what it already kept. UBS's own rates need one ubs-web download --only rates (a live session).
  3. wealthdb reload schwab fidelity svb ubs firstcitizens, plus each
    Plaid source.
  4. wealthdb web refresh, wealthdb mcp restart.
  5. make demo, make demo-web.

Breaking

  • Dashboards need gold schema 0171.
  • yield.withholding: a country may name accounts (source, account
    id, rate); account wins over wrapper over default. A bad value
    names its place.
  • account_overrides takes account_kind and tax_wrapper_from.
  • A schwab source may name schwab-web alone; 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_gains reads a missing class as
    unknown; serving_cash_history keeps 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-file stops a collector run; viac exits 1.
  • First Citizens login --check signs in: ask first.
  • Playwright 1.64.1 (Chromium 156); make update moves the pin, the
    pip pins and the user agents together.
  • A Metabase bump sets MB_SHA256 in web/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 reimbursement nets against the spending it repays: a reimburse
    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 -a carries the cash-flow boundary across the rebuild.
  • SVB's month-end workbook reaches returns stated as 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 as Cash <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.
  • prune keeps 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 lint follows the ruff pin; make update stops on a pip error;
    make test writes nothing into the data root; images carry no tests.
  • MCP returns view=stated sorts 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-ro skill is a third shorter.

Docs

  • Plan notes read as present limits; the custody kind, what
    report_yield_received holds and the override ledger match the
    code; exposure precedence is stated once (EXPOSURE.md §9).

v0.7.1

Choose a tag to compare

@ptu-gh ptu-gh released this 27 Oct 08:49

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

  1. 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 use wealthdb reload without a source, compact or init:
    under DuckDB 2.0 they write a file the dashboards cannot open. From
    this release on, wealthdb compact rewrites such a file, and web refresh refuses to serve one and says so.
  2. Run ./collectors/firstcitizens/firstcitizens load and
    ./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.
  3. Run ./collectors/refdata/refdata load --force once. It re-reads
    the GLEIF answers already kept; nothing is downloaded.
  4. 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.
  5. wealthdb web refresh, then wealthdb 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).
  6. make demo, then make 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) and mark_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 "run wealthdb web refresh".
  • The web_transactions view is gone. Its table,
    serving_transactions, and the report_transactions model 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. A match
    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 leave journal and 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 as transfer_in or transfer_out by
    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, so report_gains comes 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 test runs 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 -p prints 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 reload resets 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 all builds the engine and the images together and skips an
    image whose inputs did not change. make test runs 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 the plaid
    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

Choose a tag to compare

@ptu-gh ptu-gh released this 24 Oct 21:03

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

  1. 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-web its supplied statements, and svb
    every statement, which runs OCR again on the deposit and mortgage
    statements and takes a while.
  2. Fetch the reference data the reports read.
    • refdata: its silver moves to version 3. Run
      ./collectors/refdata/refdata load --force once to re-read the
      bronze already kept into the new columns, then download --backfill for 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 in refdata.cfg. To read the
      dividend calendar, also name vanguard beside the issuers and
      finra, six and jpx after sec-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. Then refdata download, refdata load and wealthdb load refdata.
    • fred fetches the Treasury curve beside the exchange rates:
      ./collectors/fred/fred download --lookback 2015-01-01, load,
      then wealthdb load fred.
    • curves, a new collector, fetches the euro and franc curves from
      the ECB and SNB data portals. It needs no key. Add a curves
      silver source to wealthdb.cfg, then ./collectors/curves/curves download --lookback 2015-01-01, load and wealthdb load curves. Enabling it accepts the portals' terms, quoted in its
      README (the SNB's: non-commercial use, with the source named).
  3. Add a yield block to wealthdb.cfg: curves names the series
    per currency and tenor. withholding, rating_funds,
    conventions, cycles, settlement_cycles and horizon_months
    are optional, as is cashflow.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.
  4. Describe what no source states, if you want: the declarations
    ledger takes rate, rating, dividend and terms records.
    wealthdb yield coverage -f ndjson prints a skeleton for every
    holding still missing one.
  5. Run wealthdb reload fidelity and wealthdb 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 the svb sideload
    also runs ./collectors/svb/svb load and wealthdb 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.
  6. Run a wealthdb load (wealthdb load refdata will do) before
    wealthdb web refresh: the load builds the learned record-day
    gaps. Then wealthdb web refresh and wealthdb mcp restart. The
    refresh fills the yield and projection tables and takes a
    few minutes more.
  7. make demo rebuilds 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 schwab applies
    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.
  • -p prints 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 cashflow has a sixth view, projection, and the flags
    --rates, --maturing and --shift. The other views refuse them.
  • exposure credit, maturities, coupons and cycles gain a
    class_share column. exposure --period refuses issuers, as it
    refuses securities and funds.
  • yield coverage gains a payments column and a record_day
    section: stated, derived, learned, lag, none and 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 income and yield tools read as the command
    line's do, and start with the tool's name.
  • The svb sideload's derived mark carries the marker
    derived-workbook-mark and the description ACCOUNT VALUE (WORKBOOK MARK); its gold instrument key changes with the description.

Yield

  • wealthdb yield <view>: global, sources, portfolios and
    accounts, one row per entity and currency with a value-weighted
    total; positions; securities through funds; funds; cash
    against its benchmark; curve, DV01 by tenor and by what holds it;
    and coverage. --converted gives one row per entity, --period a
    series. See wealthdb/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 --shift and
    --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 reads partial where 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 quality column says what each figure rests on, and coverage
    what is still missing. -p hides amounts and ids.

Income

  • wealthdb income projection, calendar, sources, portfolios,
    accounts and positions put 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 positions lists each holding's
    expected payments, so it sums to income projection.
  • --rates constant|forward holds today's rates or follows the forward
    curve, --maturing cash|idle|roll says what principal does at
    maturity, and --shift moves 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

  • refdata reads 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...
Read more

v0.6.0

Choose a tag to compare

@ptu-gh ptu-gh released this 18 Oct 08:07

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

  1. Rebuild with make all. Gold migrations 0125 to 0133 apply on the
    first read-write open. Nothing re-parses and no reload is needed.
  2. Set up reference data. Add a refdata silver source to
    wealthdb.cfg, and write ~/.config/refdata.cfg with the providers
    to ask and the contact the SEC requires. Then
    ./collectors/refdata/refdata download (--backfill also fetches
    the months each fund was held), ./collectors/refdata/refdata load
    and wealthdb load refdata. Without it the report still runs: every
    fund is then a leaf of its own and exposure coverage says so.
  3. Describe what no public source knows, if you want: the exposure
    block of wealthdb.cfg and a declarations ledger. wealthdb exposure coverage -f ndjson prints a skeleton record for every
    holding still to declare.
  4. Run wealthdb web refresh, then wealthdb mcp restart. The
    dashboards read new serving tables that the refresh fills. A
    running MCP server refuses a schema newer than its binary.
  5. make demo rebuilds 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, restart and refresh exit 3 when the gold
    snapshot is older than the dashboards need, and say to run web 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. note is still allowed.
  • A collector's prune refuses --silver-db. A relative --data-dir
    or --secrets-dir mounts that directory of the host; before, Docker
    made a named volume of it.
  • A collector load never 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_override in wealthdb.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,
    funds and coverage. Every view sums to net worth as holdings global gives it. See wealthdb/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,
    within exposure.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 broad size_funds.
  • countries gives the country of risk; --country domicile the
    country of incorporation. situs gives 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.
  • currencies counts 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 exposure block names proxies (a fund that stands in
    for another, with always and leverage), reference_funds,
    size_funds, declarations, custody (who keeps each source's
    assets, and in which country) and max_gap_days.
  • What no source, declaration or proxy describes reads unknown,
    never a guess. A quality column on every view and the coverage
    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.
  • -p hides every amount and every instrument id a holding is named
    by; shares, names and labels stay.
  • The MCP server has an exposure tool 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 refdata collector fetches what each fund holds and what each
    security is. It reads only what gold asks for: wealthdb refdata-wanted prints the funds held, the funds the config names,
    the funds a held fund holds, and the securities held directly or
    through funds. See collectors/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, once refdata.cfg names 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, and constituent_floor_pct sets 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-run plans a run, --provider, --ref and --limit narrow
    one, and login --check asks 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 refresh fills, 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_personal on 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 help lists each report's views from the report itself.
  • The categorize audit 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-documents run, 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.json cannot 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. --help starts 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 passes make test. pip reruns only
    when a venv's inputs change. make clean reach...
Read more

v0.5.0

Choose a tag to compare

@ptu-gh ptu-gh released this 13 Oct 10:44

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.

  1. Rebuild with make all. Gold migrations 0113 to 0124 apply on the
    first read-write open.
  2. 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 --force for 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 next load, because their parsers changed.
  3. Run wealthdb reload -a. It fills the cost basis, the lots and the
    lot engine's ledger for every source.
  4. Run wealthdb web refresh, then wealthdb 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_% and savings_rate_% are now
    twr_pct, twr_ann_pct, mwr_pct, mwr_ann_pct, share_pct and
    savings_rate_pct. This changes the keys of -f json and 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. See wealthdb/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 new lots block 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.basis in 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_aliases in 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 rebuild runs the engine by hand. Every load and
    reload runs 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, coverage and check. The aggregate views add
    up. A quality column names every gap, and coverage says per
    account where the figures are blind. See wealthdb/docs/GAINS.md.
  • wealthdb gains check compares the rebuilt figures with what the
    sources state.
  • --missing-basis ignore|zero reads a missing cost basis as blank or as
    0. The config's lots.missing_basis sets the default.
  • wealthdb holdings positions offers the cost basis, its stamp, the
    clean value and the unrealized gain as opt-in columns.
  • wealthdb returns shows gain_<CCY>, the money behind the
    percentages.
  • The MCP server has a gains tool 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
    status tool and the opt-in holdings sources column observed_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-in valued_date column.

Reports and dashboards

  • GBP is a fourth reporting currency beside USD, CHF and EUR. The
    dashboards open in the config's default_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 config works 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.
  • explore and 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 --check makes 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 demo builds 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

Choose a tag to compare

@ptu-gh ptu-gh released this 06 Oct 19:52

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.cfg has an
    mcp block with "enabled": true. A config without the block behaves
    as before.
  • A demo directory built by an earlier release has no mcp block. Run
    make demo before make 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 spending prints.
    • wealthdb mcp start runs it in the engine image, on loopback port
      3300. wealthdb mcp url prints the URL and the header for a client.
    • wealthdb mcp stdio serves one session to a client that starts the
      server itself.
    • status, restart, logs and stop manage it. make demo-mcp
      serves the demo household on port 3400.
  • Eleven tools: holdings, returns, transactions, spending,
    income, cashflow, status, snapshots, resolutions,
    categorizations and describe. Every parameter is optional. The
    tools filter, sort and page their rows, so a model needs no shell and
    no jq.
  • 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. /mcp serves full data. /mcp/privacy applies the
    CLI's -p to 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 --privacy is 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.md has both configs.
  • After a later rebuild of the image, run wealthdb mcp restart. A
    running server keeps the old binary, and wealthdb mcp status says
    so.
  • See mcp/README.md. Read its security section before you give a
    model the URL.

Help

  • wealthdb, wealthdb help and --help say 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 in spending;
    • in the line views, money leaving is negative;
    • a category someone names, such as groceries, is a detailed one;
    • --period total gives one figure for a whole window.
  • The wealthdb-ro skill 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 a load that
    meets a report in progress, and a report that meets a call to the MCP
    server.
  • cashflow coverage lists 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-mcp tests the server's lifecycle script, and make test
    runs it.
  • AGENTS.md gains rules for documentation, login flows, env files and
    private narrative.

v0.3.3

Choose a tag to compare

@ptu-gh ptu-gh released this 06 Oct 19:52

Upgrading

  • Rebuild with make all. Gold migrations 0109–0111 apply on the first
    read-write open. No reload is needed: the next load re-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_WAGES INCOME_SALARY, or INCOME_GIG_ECONOMY for gig-platform pay
    INCOME_OTHER_INCOME INCOME_OTHER
    INCOME_SELF_EMPLOYMENT INCOME_CONTRACTOR
    INCOME_RENT INCOME_RENTAL
    INCOME_ALIMONY_AND_CHILD_SUPPORT INCOME_CHILD_SUPPORT, or INCOME_ALIMONY for maintenance from a former partner

    Edit wealthdb.cfg and 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 becomes INCOME_CHILD_SUPPORT.

  • Optional: wealthdb categorize income --all asks 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-roll refuses it.

Plaid

  • A new collector, collectors/plaid, reads the banks, brokers and card
    issuers that Plaid reaches:
    • link --item NAME links one login at one institution. You sign in
      on Plaid's own page, in your browser.
    • login --check reports the state of every link and changes nothing.
    • download saves accounts, balances, holdings, transactions and loan
      terms. load builds one silver database per link, and prune trims
      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 --refresh asks Plaid to fetch fresh holdings from the
    institution first. Plaid bills that call on a paid plan, so it runs
    only when billed_reads in $XDG_CONFIG_HOME/plaid.cfg names 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.md and wealthdb/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 -a gives 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

Choose a tag to compare

@ptu-gh ptu-gh released this 06 Oct 19:51

Upgrading

  • Rebuild with make all. Gold migrations 0107–0108 apply on the first
    read-write open. No reload is needed.

Demo

  • make demo builds an invented household into its own directory,
    ~/wealthdb-demo by 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
    init and load -a, so every report and dashboard shows real
    behaviour. make demo-web serves its dashboards on
    http://127.0.0.1:3100/ in a container of their own, and
    make demo-web-stop stops them.
  • make demo-roll adds the days since the last build and loads only
    those. History never changes between runs.
  • WEALTHDB_DEMO_ROOT, AS_OF, SEED and FINDINGS=1 select 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,
    and demo/check_dashboards.py runs 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 reads measured with a gap of zero. It read
    obscured before.
  • 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

Choose a tag to compare

@ptu-gh ptu-gh released this 06 Oct 19:51

Upgrading

  • Rebuild and reload: make all, then wealthdb reload -a. Gold migrations
    0104–0106 apply on the first read-write open. Many adapters changed what
    they emit, so a plain load is 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 hooks once. A pre-commit hook then refuses a
    commit that make lint fails on or rewrites.

New configuration

  • declared_accounts names 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.
  • far on 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.names tells 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 accounts is now
    Unpaired transfers. It splits into No far account and
    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 version prints 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 lint runs 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.md is a symlink to it.
  • The README describes what the suite does, with examples.