This article says where every number on the Finances overview comes from — Digits for the P&L, Mercury for cash, Dripos, Stripe and Bloom for income by stream, Ramp for card spend, and the Facility module for upcoming obligations — and how each is cut to the selected period. It also covers what the Café subtitle "Dripos POS · Central Time" stands for. Everything is as the code does it on 2026-09-01 (pepper-ui/backend/finance-data.js, served at /api/finances).
Periods and caching
The period chips select mtd, last-month or ytd. All ranges are computed on the Chicago date. The subtitle prints the label and the exact start – end dates.
| Chip | Range | "Prior period" for the deltas |
|---|---|---|
| Month to date | 1st of this month → today | the same day-span of last month, not the whole month — so day 3 is compared with 3 days, not 30 |
| Last month | the previous calendar month | the month before it |
| YTD {year} | 1 January → today | 1 January → the same date last year |
The server caches the assembled page for 15 minutes per period. The refresh control passes refresh=1 and bypasses it. Each source is fetched independently (Promise.allSettled), so one failing system leaves a gap in its own panel and nothing else.
P&L headline and expenses — Digits
Income, Expenses, Net and Margin come from the Digits profit-and-loss statement (connect.digits.com/v1/ledger/statement/profit-and-loss) for the range and for the prior range. OAuth tokens live in data/digits-tokens.json on the Mini and refresh themselves when within 5 minutes of expiry.
| Figure | Digits rows |
|---|---|
| Income | REVENUE + OTHER INCOME |
| Expenses | COST OF REVENUE + OPERATING EXPENSES + OTHER EXPENSES |
| Net | the NET INCOME row as Digits reports it |
| Margin | Net ÷ Income; its delta is in percentage points |
| Expenses by category | the top-level children of OPERATING EXPENSES (anything at or above $0.50), plus a "Payment Processing / COGS" line carrying COST OF REVENUE, sorted largest first. The percentage beside each is its share of income, not of total expenses. |
The four KPI deltas read "vs prior period"; a delta is blank when the prior figure is under $1. The sparkline under each KPI is the real six-month shape of that figure, drawn only when at least two real months exist.
Six-month trend
The trend is six full calendar months ending with the current one, each a separate Digits P&L call. A month whose Digits call failed carries no value. It is not plotted as zero — that drew an outage as a breakeven month. The curve bridges the gap from its nearest real neighbours, marks the bridged month with a hollow ring on the line, and the readout shows "—" there; a scrub between a real month and a failed one also shows "—". The headline above the chart ("Net income rising / cooling … while costs keep climbing / come down") is generated from the direction of the last two months and never carries a number, so it cannot disagree with the readout.
Income by stream
Streams are attributed by source system, one row each, with the source printed under the name. They are deliberately not keyed to Mercury's Café / Non-Café accounts: that split exists for Filip's profit participation and is expected to go away.
| Row | Source line | How it is computed |
|---|---|---|
| Café | Dripos | Dripos gross sales for the range, summed from the same 365-day daily report the Café module reads. "Dripos (unavailable)" with no total if that report fails. |
| Catering | Flashquotes + Bloom | Bloom payments on the Catering Events, Planned Barista Service and Coffee Tasting Experience workflows, plus the Flashquotes share of Stripe (below). |
| Events | Bloom · space rental | Bloom payments on the In-Café Private Events workflow. |
| Education | Bloom · classes | Bloom payments on the Coffee Classes workflow. |
| Other / Unattributed | Digits residual | Digits income minus everything attributed above, shown only when over $1. |
Bloom. Bloom workflows map one-to-one to income streams (confirmed with Maciej 2026-07-04). The backend pulls projects and invoices from api.bloom.io and sums each invoice's transactions dated inside the range, skipping payouts and refunds. Onboarding, Offboarding and Collaboration workflows are not income. Authentication is the bloom_token cookie in .bloom-session.json; when it has expired the three Bloom rows collapse into one "Catering & Events" row sourced from the Stripe pool, and the source line says "Stripe (Bloom session expired — run bloom-auth.py to split)".
Stripe and Flashquotes. Stripe settles both Flashquotes and Bloom, so a Stripe total cannot mark a stream on its own. The backend sums succeeded, non-refunded charges created in the range (up to five pages of 100), and treats Stripe total − all Bloom payments, clamped at zero, as the Flashquotes portion — which is always Catering.
Cold Brew Bikes is not a row yet. It joins when a direct-POS source for it exists; the stream list is extended as verticals come online.
Card spend — Ramp
Ramp transactions in the range (client-credentials token, up to five pages of 100), declined ones excluded, grouped by merchant name; the panel lists the top 8 merchants and the total. The count line is the number of transactions fetched. Ramp is the line-item truth for card expenses; Digits is the categorized truth.
Cash — Mercury
Every Mercury account from api.mercury.com/api/v1/accounts by nickname, with its available balance (current balance if available is absent), and the total. "Mercury unavailable." means that call failed. Beneath it, Upcoming obligations (60d) lists Facility items that are active, have a cost, and fall due within the next 60 days — the first 8 by due date.
Credentials
The Stripe, Mercury and Ramp keys are read from the process environment first and the main pepper/.env second (STRIPE_API_KEY, MERCURY_API_KEY, RAMP_CLIENT_ID, RAMP_CLIENT_SECRET). Digits tokens are the JSON file above; Bloom is the browser session file.
Café: "Dripos POS · Central Time"
The Café tabs carry this subtitle; the content audit slates it for trimming to the date alone. It means two things. Every Café figure is a Dripos sales-summary report for location 646, cached 3 minutes on the server and refreshed live only by the refresh button. And every day boundary — today's row, the last 7 days, the trailing 30 and 90 day windows, the trend presets — is a Central Time day, matching Dripos's own day boundaries; the page anchors on the Chicago date, not on UTC or the browser's zone. How those reports become forecasts and staffing estimates is in Café forecast and staffing model.