This article defines every number the Supply OS module of the Pepper console shows outside the three radar tabs: on-hand and the ~ mark, burn and days of cover, the reorder flag, variance and the leak flag, counts, the Order up proposal, margins, and the Dripos Supply Chain tabs. The radars (Amazon, Costco, Vendors) have their own article, Supply OS radars — Amazon Business, Costco, vendor cadence: sources and refresh. Everything here is read from the code on the pepper-ui branch as of 1 September 2026; file names are given so the next edit can be checked against the source.
Where each number lives
| Tab | What it shows | Backend |
|---|---|---|
| Reorder queue | InvenTree supplies below minimum and Dripos SC ingredients below par, by severity | pepper-ui/backend/inventory-data.js |
| Cover board | On-hand, burn per day, days of cover, reorder flag, offers | pepper-ui/backend/supply-ledger.js |
| Count | Guided stocktake sheet, writes verified counts | supply-count.js, kiosk write path in kiosk-data.js |
| Variance | Theoretical vs actual ingredient usage, leak flag | supply-variance.js, burn side from supply-burn.js |
| Order up | Weekly proposal: events, refills, cadence dues, dead stock | supply-proposal.js |
| Margins | Menu price vs recipe COGS, below-spec flags | supply-margin.js, margin-spec.json |
| Supplies · Ingredients · Coffee | Dripos Supply Chain stock and par levels | Dripos SC, read-only here |
The module subtitle reads "event ledger live, kiosk capturing since Jul 13". The event ledger is supply.db; the storage-room kiosk (iPad, tap-to-decrement) has been writing to it since 13 July (2026, inferred) [check: the subtitle says only 'Jul 13'; 2026 is inferred from the Supply OS build dates].
Reorder queue
The Reorder queue is the module's default tab (the last tab used is remembered per browser). It is built in pepper-ui/backend/inventory-data.js, cached for 5 minutes, from the two source systems directly — not from the ledger: InvenTree supplies whose stock is below their minimum or at zero, plus enabled Dripos Supply Chain ingredients with a par level above zero whose stock is below par or at zero (coffee-tagged ingredients list under the Coffee source). Severity is 2 when stock is at or below zero; otherwise (min − stock) ÷ min for supplies and (par − stock) ÷ par for ingredients. Rows sort by severity, most urgent first.
The tab label carries the row count. The two KPI cards split it: Out of stock is severity 2 or more, Below minimum is everything under 2; the Out / Low chips apply the same split. The Display menu groups by status (Out of stock / Below minimum), channel or source, and sorts by Most urgent (severity), Biggest deficit (target − stock) or name. Saved views (Out of stock, WebstaurantStore order, Dripos restock by default) live in the browser. The empty state reads Nothing below minimum anywhere. The queue compares stock with InvenTree minimums and Dripos pars; on-hand, burn and cover are the Cover board's numbers and do not feed it.
On-hand and the ~ mark
On-hand is a fold over the event ledger, not a number copied from InvenTree or Dripos. The most recent counted event for the item is the baseline; every non-count event after it (kiosk taps, derived sales, waste, receipts) is summed on top. Ledger items come from two sources: supplies (InvenTree) and ingredients (Dripos Supply Chain). A kiosk tap means one unit of issue opened (a box, case or bag); it writes the ledger and, live, the source system.
When the ledger was first seeded, each item got a counted event with actor seed taken from the live InvenTree/Dripos snapshot. Those source numbers are known to be inaccurate, so a seed baseline is a guess. The console shows it with a ~ prefix and the badge unverified until a human count lands, meaning a counted event whose actor is not seed. Nothing else clears the badge.
Burn, cover, reorder, dead stock
| Term | Definition | Source |
|---|---|---|
| Burn per day | Net consumption over the trailing 28 days, divided by 28. Sums the signed quantity of opened, sold_derived and wasted events, so an undo (+1 opened) cancels its mistaken tap. Never negative. | supply-ledger.js, burnPerDay(itemId, windowDays = 28) |
| Cover | On-hand ÷ burn per day, in days. Blank when burn is zero. | same, getSupplyBoard() |
| Reorder flag | Cover is below the item's lead days plus 4 safety days (SAFETY_DAYS = 4). Lead days are a per-item field on the ledger item (lead_days, default 3), so an item with no lead time set flags at cover under 7 days. | same |
| Dead stock | On-hand above zero and burn exactly zero over the window. Order up only lists dead stock for verified items. | same; supply-proposal.js |
The board is sorted by cover ascending, so the item closest to running out is first.
Where the sold_derived events come from
The burn engine runs nightly at 03:40 local, in-process inside the pepper-ui backend (com.blfo.pepper-ui, no separate launchd job). It multiplies Dripos product sales by the Dripos recipes and writes one sold_derived event per ingredient per day. Base recipes burn per product sale (Dripos report/productsales); option-scoped recipes burn per modifier selection (report/modifiersales), which is how one Hot Tea product tracks each tea leaf. Bar drinks with no recipe keep tap-based burn only. The run is idempotent per day and re-derives the last 3 days each night, so a missed night heals itself. File: supply-burn.js.
Offers
Each ledger item can carry several offers: vendor, label, URL, price, shipping and pack quantity. One is the default (starred), which is the source auto-reorder repeats. Unit cost = (price + shipping) ÷ pack quantity; the cheapest badge marks the offer with the lowest landed unit cost when it beats the default, and the saving percentage feeds Order up and the Monday digest. The panel states that every purchase of a variant adds an offer automatically; the purchase-intake job matches Ramp purchases to ledger items and learns offer SKUs over time (see AUTOMATION.md, com.pepper.purchase-intake). Offers can also be added or edited by hand through /api/supply/offers.
Variance and the leak flag
Variance = theoretical usage − actual usage, per ingredient, over the selected window (default 28 days).
- Theoretical is the recipe burn above: sales × recipes, from the 03:40 engine.
- Actual is the Dripos inventory delta from
report/ingredient-usage(closing − opening). Any query longer than a few days times out on Dripos's side (REPORT_TIMEOUT) and each one-day response is about 1 MB, so a nightly job at 03:50 captures one day at a time into theingredient_usage_dailytable insupply.db. The tab reads cached days only. A self-healing 35-day catch-up runs at boot and nightly; days already captured are skipped. The chip "n/m days captured" tells you how much of the window has actual data.
Negative variance means more was used than sales explain: over-pour, spoilage, comps, theft, or a wrong recipe quantity. Rows are only meaningful for ingredients that have a recipe. "No recipe" rows show physical movement that Dripos cannot explain from sales; they are listed under the All filter but never flagged.
Restock days are excluded from both sides. Dripos has no received-quantity field, so a delivery shows as a net stock increase that would read as a large negative consumption (a $900 phantom saving on a 15-bag restock, seen live on 15 July 2026). Any day where actual usage is positive is treated as a restock day, dropped from the totals, and labelled "restock day — excluded from variance" in the daily drawer.
Leak flag. A row is a leak only when both floors are crossed: variance at or beyond −10% and dollar variance at or beyond −$5 over the period, and the ingredient has a recipe. Percent alone flags noise on tiny quantities; dollars alone flag rounding on cheap items. The "leak $n / dd" chip is the sum of negative dollar variance over the window. Dollar variance uses the ingredient's Dripos unit cost (COST_PER_UNIT).
Counts
The Count tab is a guided stocktake: active ledger items grouped by area, each showing what the ledger currently believes. Enter what is physically on the shelf. Every saved count goes through the kiosk write path: a counted event in the ledger (the truth) plus a best-effort absolute sync to the source system, InvenTree or Dripos SC, so every surface shows the same number. Row states are Counted (counted since midnight today, shown green), Never (only a seed baseline exists, labelled "seed guess"), and Stale (a human count exists, but not today). Progress is items counted since the day started; nothing needs counting twice in a day. File: supply-count.js.
Order up — the proposal
Order up composes everything the ledger and radars know into a per-channel list a human can approve in one pass. Ordering stays human; every line links to its source. The same picture goes out as a Telegram digest on Mondays at 06:30 (only when the backend has TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID). File: supply-proposal.js.
| Group | Rule |
|---|---|
| Upcoming events · next 14 days | Flashquotes events in the next 14 days (EVENT_LOOKAHEAD_DAYS = 14) × the per-guest consumable model at the top of supply-proposal.js. Each model line matches a ledger item by case-insensitive substring on its name, gives pieces per guest, and a pack quantity to convert pieces into the ledger's unit of issue. Need is charged against on-hand before the weekend, not after; the Fabri-Kal stockout of July 2026 is the failure this closes. Edit the model in that file. |
| Burn refills | Items with burn where cover is at or below lead days + 7 (HORIZON_DAYS = 7). Suggested quantity refills to 21 days of burn (TARGET_COVER_DAYS = 21), capped by the item's shelf space (space_max), minimum 1. Shows the default offer's vendor and, where one exists, the cheaper offer. |
| Channel · due by cadence | Radar rows whose status is overdue or due soon, grouped by channel (Amazon, Costco, or the vendor name), with the usual quantity from the last order. See the radars article for how status is computed. |
| Dead stock | Verified items with on-hand above zero and zero burn. Value = on-hand × default offer unit cost; the group label carries the total sleeping. |
The digest also lists items where a cheaper offer beats the default, and products below margin spec (below).
Margins
COGS = base recipe quantities × current ingredient unit costs from Dripos (COST_PER_UNIT), using the same recipe cache as the burn engine and the same unit costs as Variance. Price is the Dripos product price. Only enabled, non-archived products with at least one recipe appear; the tab fills in as the recipe build lands. Rows are sorted worst margin first, which is where the pricing conversation starts. The result is cached for one hour.
The spec is pepper-ui/backend/margin-spec.json: default minimum 60%, overridden per Dripos category name. As of this writing: Coffee beans 40%, Packaged beverages 30% (retail resale runs structurally lower; tuned 17 July 2026). Maciej edits that file to change a threshold.
A product is flagged below spec only when every ingredient has a cost; missing costs understate COGS and would flag noise. The suggested price = COGS ÷ (1 − spec) rounded up to the next quarter dollar, since rounding down would land back below spec. Below-spec rows feed the Monday digest. Products that also carry option-scoped (modifier) recipes show base COGS only and are flagged, because their real COGS moves with the modifier choice.
Supplies, Ingredients and Coffee tabs
These read Dripos Supply Chain directly. Stock depletes via recipes at the POS; par levels are set in Dripos, not here. Coffee is the coffee-tagged ingredients in Dripos SC; coffee ordering and receiving runs through the /receive-inventory skill, not through Order up.
Quick reference
| Constant | Value | File |
|---|---|---|
| Burn window | 28 days | supply-ledger.js |
| Safety days on reorder | 4 | supply-ledger.js |
| Lead days default | 3 (per item, lead_days) | supply-ledger.js |
| Burn engine | 03:40 nightly, 3-day catch-up | supply-burn.js |
| Usage capture | 03:50 nightly, 1-day windows, 35-day catch-up | supply-variance.js |
| Variance default window | 28 days | supply-variance.js, InventoryView.svelte |
| Leak flag | ≤ −10% and ≤ −$5, recipe required | InventoryView.svelte |
| Proposal horizon / target cover / event lookahead | 7 / 21 / 14 days | supply-proposal.js |
| Digest | Mondays 06:30, Telegram | supply-proposal.js |
| Margin minimum | 60% default; 40% Coffee beans; 30% Packaged beverages | margin-spec.json |