Case study

ReStock: an order list a shop owner can argue with

I built an explainable replenishment engine that turns four CSV exports into a daily order list with a reason on every line. On a simulated 120-SKU store it matches the best spreadsheet's 98.1% fill rate with 66% less inventory, cuts perishable waste from 45% to 0.4%, and protects 22% more gross margin under a cash cap.

ReStock: an order list a shop owner can argue with

At a glance

Role
Solo: problem framing, decision logic, simulator, evals, UI
Company
Independent project
Timeline
Oct 2026 · one week
Team
Solo (AI-assisted coding)
98.1%
fill rate, equal to min–max, with 66% less inventory
45% → 0.4%
perishable waste as a share of purchases
+22%
gross margin under an 80% cash cap vs best baseline
23.7%
7-day forecast WAPE on holdout (vs 24.8% baseline)

Summary

A kirana or small supermarket reorders several times a week, mostly from memory, and loses money in two directions at once: empty shelves on fast sellers and expired stock behind the counter. I built ReStock, a replenishment engine that takes four CSV exports from billing software and produces a daily order list where every line carries a reason the owner can check with a calculator. Before touching a real store, I built a simulator so every policy could be compared on identical demand. Status: benchmark complete, pilot pending. Source on GitHub.

The problem

A shop with 300–1,500 SKUs reorders 3–7 times a week. Two failures happen together and the owner sees neither as a number: stockouts on fast sellers and waste on perishables.

Spreadsheet min–max rules make it worse in a hidden way. A product that keeps selling out looks like it has low demand, because an empty shelf records zero sales. The rule then orders even less.

User

The owner-operator. They have cash pressure, no analyst, and no ERP. They have exports from billing software and a phone to call suppliers. They will not trust a number they can't check.

Reframing the goal

Every line on the order list carries a short paragraph: recent daily demand, how many days of cover are on hand, the supplier lead time, the case pack, and why the quantity landed where it did.

Options considered

  • Machine-learned forecasting (gradient boosting, neural nets)

    The segment has weeks of noisy data, not years. The models can't be explained to a shop owner, and they overfit the one store you train on. Rejected for a first product.

  • Auto-ordering: send purchase orders without approval

    Removes the most work, but trust has to be earned first. One bad automatic order and the product is uninstalled. Rejected until a pilot proves line-acceptance rates.

  • POS and ERP integrations

    Right eventually, but every integration is weeks of setup per billing vendor. Zero setup is what makes a first pilot possible. Rejected for now.

  • Explainable rules + a simulator to prove them

    Chosen

    Censoring-aware smoothing, day-of-week factors, intermittent-demand handling, FEFO for perishables, and greedy margin-ranked cash allocation. Every step is auditable. Validated on a simulator before any pilot.

What I built

1. Demand estimation that handles the messy parts. Censored days (stockouts) are treated as "at least this much", not zero. Weekend peaks and intermittent sellers are handled separately.

2. Order logic the owner can audit. Reorder point and order-up-to level from demand, lead time and a service target, rounded to case packs and supplier minimums. Perishables use first-expiry-first-out so the engine never recommends stock that will expire before it sells.

3. Cash allocation that is greedy on purpose. When the weekly budget can't cover every line, lines are ranked by gross margin protected per rupee spent and filled in order. An optimal solver would do slightly better and be impossible to explain.

4. A store simulator built first. It includes the things that break spreadsheets: empty-shelf days, weekend peaks, promos, perishables with expiry, case packs, supplier minimums and unreliable lead times. Every policy sees identical demand, so the comparison is fair, and nothing is tuned on one shop.

Trade-offs

Results

Simulated 120-SKU store, 5 seeds × 365 days. Three policies on identical demand.

Gut feelMin–max spreadsheetReStock
Fill rate90.7%98.1%98.1%
Average inventory₹19.3L₹39.9L₹13.6L
Perishable waste24.2%44.7%0.4%
Gross margin, cash capped at 80%₹76.5L₹77.3L₹94.6L

Average inventory to hold the same 98.1% fill rate

Before₹39.9L
After₹13.6L

Perishable waste as a share of purchases

Before44.7%
After0.4%

How I measured quality

  • 19 hand-checkable unit tests. Every worked example in the logic document is a test with the same numbers, so the documentation can't drift from the code.
  • Forecast eval on held-out seeds. 7-day WAPE of 23.7% against 24.8% for a 28-day moving average.
  • No tuning on holdout. The smoothing constant was chosen by a sweep on training seeds only.

What I'd do next

  1. Next

    Pilot with 3 stores for 4 weeks

    Primary metric: % of order lines sent unchanged. Guardrails: stockout days and waste.

  2. Then

    WhatsApp delivery

    Owners already order from suppliers on WhatsApp. The order list should arrive where the work happens.

  3. Later

    Auto-ordering, earned per SKU

    Only for SKUs where owners accepted at least 95% of lines for 4 weeks.

What this shows

Problem reframing, decision-making under data scarcity, eval design before launch, and being honest about what a benchmark proves.