Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

replenishment-decision-kit

A decision-support system for replenishment under sparse, censored demand and asymmetric costs — not a generic sales forecasting exercise.

The current implementation ships with a synthetic print-on-demand publishing dataset as a demo/case study. The underlying problem, and the code, are not specific to publishing; they apply to any retail or consumer-goods setting where reorder decisions are made under similar constraints.

Note: All data in this repository (data/) is synthetically generated (dgp/). No real business, sales, or inventory data is included.

Problem

Replenishment decisions in these settings share four properties that make them harder than a standard regression or forecasting problem:

  • Sparse demand — many SKUs sell in small, lumpy quantities, especially in the long tail.
  • Censored observations — when a SKU stocks out, observed sales stop reflecting true demand; the gap is invisible unless explicitly modeled.
  • Asymmetric costs — overstocking (idle capital, storage, markdown risk) and understocking (lost sales, unmet demand) are rarely equally costly, and the ratio between them is a business input, not something a model can infer on its own.
  • MOQ constraints — reorder quantities are often bound by a minimum order quantity, so most decision points should correctly resolve to "do nothing" rather than trigger a reorder.

The system is built to make these constraints explicit and inspectable, rather than absorbing them into a single opaque prediction.

Repository structure

The codebase is organized around the data's actual lifecycle, not by file type:

dgp/          → generates synthetic sales, stockout, and catalog data
features/     → builds features and labels from raw sales history, enforcing
                strict no-lookahead (features/engineering.py, features/labels.py)
models/       → interchangeable demand predictors behind one shared interface
                (heuristic, linear, gbdt — see models/base.py)
decision/     → converts a predicted demand distribution into a reorder
                decision (whether to print, how many) under cost and MOQ
                constraints (decision/policy.py)
evaluation/   → backtesting, censoring-bias diagnostics, and the multi-layer
                evaluation report
config/       → business_spec.yaml — the single entry point for business
                parameters (see "Configuration" below)
scripts/      → entry points: generate data, build the training table, tune
                on dev, evaluate on a frozen test set

Data flows in one direction: dgp → features → models → decision → evaluation. Each stage only depends on the stages before it, which is what makes it possible to swap out a model or a business spec without touching the rest of the pipeline.

Two SQLite databases separate what a model is allowed to see from what it's being graded against:

  • data/tables.db — observed data (what a real system would have access to at decision time)
  • data/truth.db — ground-truth demand, including what was lost to stockouts; used only for evaluation, never for training

Models

Three predictors share a common interface (fit, predict_rate, predict_quantiles):

  • Heuristic — rolling-mean baseline, no training.
  • Linear — Ridge regression.
  • GBDT — gradient-boosted trees with a Poisson objective.

This project is an ongoing effort to systematically examine how these commonly-used model classes perform under different data conditions and business settings, rather than assuming a more complex model is automatically the better choice. That analysis is still in progress.

Configuration

config/business_spec.yaml is the single place where business-specific parameters live: cost structure, MOQ, target quantiles, and related settings. Adapting the system to a different business environment is intended to be a matter of editing this spec and pointing dgp/features at a new data source, without changes to the core modeling or decision code.

Roadmap

  • Resolve open questions in the linear model's feature handling
  • Add a unified scalar metric (pinball loss) across all three predictors
  • Extend evaluation to real business data

Quickstart

pip install -r requirements.txt

# 1. Generate synthetic data
python -m scripts.gen

# 2. Build the feature/label training table
python -m scripts.build_training_table

# 3. Tune on the dev split only
python -m scripts.test_model

# 4. Final, frozen evaluation — never used for tuning
python -m scripts.eval
python -m scripts.final_report

test_model.py and eval.py/final_report.py are kept separate deliberately: parameters are only ever tuned against the dev split, and the test split is evaluated once, at the end, to avoid contaminating the final numbers.

Contributors

Languages