The Type Library

Inventory Stockout Risk

Compute velocity-based days of cover per SKU from a sales history and an on-hand snapshot, rank imminent stockouts by the revenue they put at risk, recommend reorder quantities against supplier lead times, MOQ and case pack, and surface…

Type
Warns you before a bestseller sells out
Browse the technical files
---
name: inventory-stockout-risk
description: Compute velocity-based days of cover per SKU from a sales history and an on-hand snapshot, rank imminent stockouts by the revenue they put at risk, recommend reorder quantities against supplier lead times, MOQ and case pack, and surface the working capital sitting in overstock and dead stock.
---

# Inventory Stockout Risk

Turns three exports into one purchasing decision: what runs out before it can
be replaced, how much revenue that costs, how much to order from whom, and
what is already over-bought.

## Before you run

This skill ships scripts and sample data alongside this SKILL.md. Before running any command:

1. **Get the files.** Make sure the skill's other files (`scripts/`, `examples/` and anything else listed with this skill) are in your working folder at the same relative paths. Some environments load only SKILL.md; if yours did, fetch each file from this skill's published files and write it to the matching path. In Type, read them with the skill-file tools. Anywhere else, the Type Skills Library API lists every file with its path, content and `sha256`: GET `https://api.type.com/api/public/library/skills` and take the entry with slug `inventory-stockout-risk`.
2. **Check the copies are exact.** Compare each file's size in bytes, not characters (and its hash, where your tools report one), with the published version before running. A copy written out from the published file is fine once its byte size and hash match; never run a script you summarised or reconstructed from memory.
3. **Run from the skill's folder**, calling interpreters explicitly: `python3 scripts/…` and `bash examples/run.sh`.
4. **Try the sample first.** If the skill ships `examples/run.sh` and `examples/expected_output.txt`, run `bash examples/run.sh`; its output should match the expected file exactly. If it doesn't, stop and report the first differing line rather than running on real data.

## When to use this

- Weekly or fortnightly purchasing review, ahead of cutting POs.
- Before a demand spike (a sale, a launch, a seasonal peak) to find the SKUs
  that cannot survive it.
- When the storefront is oversetting expectations: negative on-hand, "sold
  out" bestsellers, back-in-stock emails piling up.
- Cash planning: how much working capital is frozen in slow stock that could
  fund the reorder of fast stock.
- After a supplier lead time changes, to re-cut every reorder point at once.

## Gathering the input

Three local files; the script never touches the network.

**1. Sales history** (`--sales`). One row per SKU per day, or per SKU per
order line — the script sums by SKU and date either way.
- **Shopify**: the Sales by product report, or an orders export exploded to
  line-item level. Exclude cancelled and test orders.
- **A warehouse or ERP**: a shipped or invoiced line-item table.
- Use at least the `--base-window-days` span (default 84 days). Shorter
  histories still run but the baseline velocity and the demand sigma behind
  safety stock get noisy; say so if you only have a few weeks.

**2. On-hand inventory snapshot** (`--inventory`). One row per SKU, taken the
same day as `--as-of`.
- **Shopify**: Inventory export (available quantity), joined to cost per item
  and price.
- **A 3PL or ERP** (ShipBob, ShipHero, Cin7, NetSuite): on-hand by SKU, plus
  open purchase orders and their ETAs.
- `unit_cost` must be *landed* cost, not invoice cost, or the working-capital
  figure understates.

**3. Supplier table** (`--suppliers`, optional but it changes the answers).
Lead time, MOQ, and case pack per supplier. This normally lives in a
purchasing spreadsheet, not in any system — ask for it. Without it every SKU
falls back to `--default-lead-time-days`.

## Running it

```bash
python3 scripts/stockout_risk.py \
  --sales examples/sales_history.csv \
  --inventory examples/inventory_snapshot.csv \
  --suppliers examples/suppliers.csv \
  --as-of 2026-09-15
```

Tuning that matters:

- `--as-of YYYY-MM-DD` (required) is the snapshot date. Every window ends
  here, and a sales row dated after it is an error, not a silent drop.
- `--window-days` (28) and `--base-window-days` (84) with
  `--recent-weight` (70) set the velocity blend. A seasonal brand should raise
  the recent weight; a steady replenishment brand should lower it.
- `--service-level` (95) drives safety stock through the normal inverse CDF.
  90 for low-margin commodity SKUs, 97–99 for hero SKUs you cannot be out of.
- `--review-days` (14) is how often you actually cut POs. Ordering monthly
  with a 14-day review assumption under-orders.
- `--overstock-days` (120) and `--dead-stock-value` (500) classify slow stock.
- `--json` for machine-readable output.

Bad input exits 2 naming the file, row, and column.

## Reading and presenting the output

- **Stockout risk table**, ranked by revenue at risk. `short` is the number of
  days the shelf is empty before a new order placed on the as-of date can
  land. An existing inbound PO counts by quantity and ETA: it extends cover
  by `on_order / velocity` days (or refills an already-empty shelf on its
  ETA), so a small PO that lands early can still leave a gap later. `stockout`
  is the first empty day after that simulation, which is why it can sit later
  than `cover` (on hand only) implies. `rev at risk` is short days times
  velocity times selling price. `trend` compares recent velocity
  with the baseline window: a `+40%` SKU with 7 days of cover is the most
  urgent line in any report.
- **Recommended replenishment**, grouped by supplier with quantities rounded
  up to case pack and raised to MOQ, and a purchase order value per supplier.
  This is the section a buyer acts on directly.
- **Overstock and dead stock**, with excess units and capital tied up at
  landed cost.
- **Summary** with status counts and the two headline numbers: revenue at risk
  and capital frozen.

When reporting to a human:

1. Lead with the two headline numbers and the top three at-risk SKUs by name.
2. Separate *cannot be fixed* from *can be fixed*. A SKU with 7 days of cover
   and a 60-day lead time will stock out no matter what is ordered today —
   the decision there is air freight, a partial PO, a substitution, or pulling
   paid spend off that product until it is back. Say that explicitly rather
   than just printing an order quantity.
3. Point out where the PO value exceeds the capital sitting in overstock:
   that is the liquidation conversation.
4. Flag any status of `OVERSOLD` as an operational incident, not a purchasing
   one — the storefront is selling units that do not exist.

## Limits

- **Velocity is history, not a forecast.** No seasonality model, no promotion
  calendar, no launch curve, no trend extrapolation beyond the recent-window
  weighting. A SKU about to appear in a sale will run out faster than this
  says. Override it for known events.
- **Safety stock assumes normally distributed daily demand and a fixed lead
  time.** Real DTC demand is lumpy and lead times vary; the sigma is measured
  from the baseline window only. Treat the recommended quantity as a starting
  point a buyer adjusts, not a number to send to a supplier unreviewed.
- **Past stockouts bias velocity downward.** If a SKU was out of stock for
  three weeks inside the window, its measured velocity understates true
  demand, so it will be under-ordered. The script cannot see historical
  availability. Check the at-risk list against known stockouts.
- **No multi-location or channel splits.** One pooled on-hand figure per SKU.
  It does not model bin, warehouse, retail, marketplace, or reserved-for-
  order inventory.
- **No bundles or kits.** A bundle that consumes component SKUs is only
  counted as whatever the sales history says it is.
- **No supplier price breaks, container fill, or freight optimisation.**
  Quantities respect MOQ and case pack only.
- SKUs in the sales history but absent from the inventory snapshot are
  skipped and listed under DATA QUALITY rather than silently ignored.