This article covers the three radar tabs in the Supply OS module of the Pepper console: Amazon radar, Costco radar and Vendors. They share one view and one model; only the data source and the footnote change. It explains what a radar row means (cadence, due date, lapsed, price alert, the basket chip), where each source file comes from, and how to refresh it. Definitions for the rest of Supply OS (on-hand, burn, variance, Order up, margins) are in Supply OS definitions — on-hand, burn, cover, variance, margins, counts, Order up.
What a radar is
Recurring purchases such as kitchen and bar consumables are not tracked in any stock system, but their order cadence is a burn signal. For every item with at least 3 orders (MIN_ORDERS = 3) the radar takes the distinct order dates, computes the median gap between them as the reorder interval, and sets the due date at last order + median interval. Files: pepper-ui/backend/supply-amazon-radar.js and supply-costco-radar.js (the latter also builds the vendor rows).
| Status | Rule |
|---|---|
| Overdue | Due date is in the past (due-in below 0 days). |
| Due soon | Due within 3 days (due-in 0 to 3). |
| OK | Due later than 3 days out. |
| Lapsed | Days since the last order exceed the larger of 3 × the interval and interval + 45 days. This is read as a discontinued buy (recipe change, supplier switch), not a purchasing miss. Lapsed rows are hidden by the default Active filter and carry no price alert. |
Rows are ranked overdue, due soon, OK, lapsed, then by due-in. The four header chips are the filters: overdue and due soon (Active), all recurring items, price alerts, lapsed. Overdue and due-soon rows are what Order up lists under "due by cadence".
Price signals
Three price signals: the alert rule every radar shares, then a live-price sub-line each for Amazon and Costco.
Price alert
An item gets a price alert when the last paid price is at least 15% above the median of its earlier paid prices (ALERT_UP_PCT = 15), with at least 3 priced orders and the item not lapsed. The median ignores a one-off spike. Maciej's rule from 14 July 2026: pure percentage, no dollars-per-year gate; the annualised impact is shown as information only. The "paid trend" column is the first-to-last change across the item's own paid history.
Amazon "now $X"
The launchd job com.pepper.amazon-price-poll (daily 05:20, scripts/amazon/amazon-price-poll.py, Playwright with the persisted Amazon session) self-polls the current price of every radar-active ASIN into data/amazon-price-watch.json. The radar shows the latest poll against the median paid price; the sub-line turns red at +15% or more and green at −15% or less. The same job sends a Telegram digest at 15% or more over (feeding the sourcing queue) and a stock-up digest at 15% or more under. The file is optional; the radar runs without it.
Costco "deliv $X"
Costco rows show the last warehouse price paid, and where verified, a sub-line with the Business Delivery price to ZIP 60305 and its percentage against warehouse. Delivered prices live in pepper-ui/backend/costco-delivered-prices.json, checked against a signed-in costcobusinessdelivery.com session; each entry records the check date. When an entry has a promo price whose end date has not passed, the promo (Instant Savings) is used and the row says "promo". To refresh one item, search its item number at /CatalogSearch?dept=All&keyword=N and edit the JSON.
The basket chip
"basket 3mo ↑ +4% · 12mo ↓ −3%" answers one question per channel: is everything going up, down or steady, against what we actually paid. For each window — 3 and 12 months; basketTrend() defaults to 90 and 365 days and every caller uses the defaults, so the view's 6mo label is dead code — every non-lapsed item with at least two priced orders is compared: its latest price against the price nearest the window-ago point, provided a history point exists within ±60 days of it. Each item's change is weighted by its estimated annual spend, last price × median order quantity × (365 ÷ interval), so a 25-case oat-milk habit moves the needle more than a once-a-quarter jar of nuts. A weighted change under 2% in either direction reads as steady. Computed in basketTrend() in supply-costco-radar.js, used by all three tabs.
Sources and how to refresh them
| Tab | Data file | Origin | Refresh |
|---|---|---|---|
| Amazon radar | data/amazon-orders-2025-2026.json | Amazon Business Order History Report (the panel note describes it as 20 months of history) | Import button on the tab, see below |
| Costco radar | data/costco-receipts-2024-2026.json | Warehouse receipts (the panel note says since February 2024) [check: earliest receipt in data/costco-receipts-2024-2026.json on the Mini], extracted from costco.com's orders GraphQL | Manual re-extraction, recipe in the vault handoff note |
| Vendors (WebstaurantStore) | data/wss-orders-2017-2026.json | Extracted from the WSS /myaccount order history | Manual, playbook in the vault WSS note |
| Vendors (others) | pepper-ui/backend/vendor-orders.json | Hand-maintained, email-sourced (order emails are the source of truth) | Edit the JSON |
Both radars re-read their file when its modification time changes, so dropping a new file in refreshes the view on the next request; nothing needs restarting.
Amazon: the CSV import
Amazon's Conditions of Use prohibit scraping, so the Amazon radar runs on a human-initiated export rather than the older Playwright scraper (scripts/amazon/amazon-orders-extract.py). In Amazon Business go to Business Analytics → Orders → Download CSV, then drop the file onto the Import control on the Amazon radar tab. pepper-ui/backend/amazon-import-data.js parses the 73-column, one-row-per-line-item report into the order shape the radar consumes and writes data/amazon-orders-2025-2026.json. Extra columns (brand, seller, category, UNSPSC, net totals) are kept on each item for later use. Nothing automated touches the Amazon account.
Costco: receipts and names
Receipts come from the signed-in costco.com session, Orders & Purchases → Warehouse tab, via two GraphQL calls on ecom-api.costco.com/ebusiness/order/v1/orders/graphql (receiptsWithCounts for the list in quarterly windows, then per barcode for line items). The full method, headers and the clipboard-relay export workaround are in the vault at projects/supply-os-HANDOFF.md. Read the radar accordingly: personal and business trips are mixed in the same membership. Gas Station receipts are skipped, and returns (negative extended lines) are not purchase events. Receipt abbreviations such as "FK 12Z PT CP" are unreadable, so pepper-ui/backend/costco-item-names.json maps item numbers to real product-page names (scripts/costco/costco-item-names.py); missing items fall back to receipt text. Each row links to Costco Business Delivery search for its item number. Separately, the every-30-minutes purchase-intake job uses scripts/costco/costco-receipts-pull.py to read new Costco receipts off the Ramp card feed into the ledger; that path feeds on-hand, not this radar.
Vendors: WebstaurantStore and email-sourced vendors
WebstaurantStore orders are reshaped into one radar row per recurring SKU, requiring at least 2 orders; canceled orders are excluded. The extraction playbook is the vault note projects/supply-os-wss-extraction.md. Other vendors (for example Keicha World bar matcha, unit price per kg) are curated by hand in vendor-orders.json from order emails. Rows link to WSS by product link or SKU search; other vendors link to the vendor site.
Vendor due dates are quantity-aware. The interval is scaled by last order quantity ÷ median order quantity, so a 3 kg order against a usual 2 kg stretches the reorder interval by 1.5×. The lapsed threshold scales the same way, and the price alert's annual impact multiplies by the median quantity.
Quick reference
| Constant | Value | File |
|---|---|---|
| Minimum orders for a radar row | 3 (2 for WSS and vendor rows) | supply-amazon-radar.js, supply-costco-radar.js |
| Due soon | within 3 days | both radars |
| Lapsed | gap > max(3 × interval, interval + 45 days) | both radars |
| Price alert | last paid ≥ 15% above median of prior paid | both radars, ALERT_UP_PCT |
| Amazon price poll | daily 05:20, ±15% digests | com.pepper.amazon-price-poll |
| Basket windows / steady band / match tolerance | 3, 12 months (function accepts others; no caller passes 180) / under 2% / ±60 days | basketTrend() |
| Delivery ZIP | 60305 | costco-delivered-prices.json |