Stock replenishment simulation library.
Project description
replenishment
Stock replenishment simulation utilities.
Environment (uv + Python 3.12)
This repo is set up to use Python 3.12 via uv.
uv python install 3.12
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[dev]"
Run checks with:
uv run pytest
Release and PyPI publishing
This repository is configured so that publishing on PyPI is done from GitHub
Actions (.github/workflows/release.yml), not from local twine upload.
One-time setup
- Create a PyPI project named
replenishment(or let the first publish create it). - In PyPI, configure a trusted publisher for this repository and workflow:
- Owner:
janrth - Repository:
replenishment - Workflow:
release.yml - Environment:
pypi
- Owner:
- In GitHub, create an environment named
pypiin repo settings.
Versioning
Package versions are derived from git tags via setuptools-scm. Use semantic
tags such as v0.1.0, v0.2.0, etc.
Release flow (GitHub-first)
-
Push a version tag:
git tag v0.1.0 git push origin v0.1.0
-
In GitHub, create/publish a release for that tag.
-
The
Releaseworkflow will:- build
sdistand wheel artifacts, - attach them to the GitHub release,
- publish them to PyPI automatically.
- build
You can also run the workflow manually from the Actions tab using workflow_dispatch
and pass git_ref (for example v0.1.0).
Usage
Plot-first usage
This README focuses on two visual workflows:
- Mean forecast + safety stock optimization.
- Percentile target optimization.
Example 1: mean forecast + safety stock
This example optimizes the safety stock factor on a backtest window, then applies the learned policy to the evaluation horizon.
from replenishment import (
generate_standard_simulation_rows,
optimize_point_forecast_policy_and_simulate_actuals,
plot_replenishment_decisions,
replenishment_decision_rows_to_dataframe,
split_standard_simulation_rows,
standard_simulation_rows_to_dataframe,
)
rows = generate_standard_simulation_rows(
n_unique_ids=1,
periods=18,
start_date="2031-01-01",
frequency_days=30,
forecast_start_period=10,
history_mean=52,
history_std=6,
forecast_mean=48,
forecast_std=5,
lead_time=2,
initial_on_hand=30,
current_stock=30,
seed=7,
)
backtest_rows, eval_rows = split_standard_simulation_rows(rows)
optimized, _, decision_rows = optimize_point_forecast_policy_and_simulate_actuals(
backtest_rows,
eval_rows,
candidate_factors=[0.8, 0.9, 1.0],
)
rows_df = standard_simulation_rows_to_dataframe(rows, library="pandas")
decision_df = replenishment_decision_rows_to_dataframe(decision_rows, library="pandas")
example_id = decision_df["unique_id"].iloc[0]
plot_replenishment_decisions(
rows_df,
decision_df,
unique_id=example_id,
title="Mean forecast + safety stock (optimized)",
decision_style="line",
)
Example 2: optimize percentile targets
This example learns the best percentile target on backtest rows and then plots replenishment decisions on forecast rows.
from replenishment import (
PercentileForecastOptimizationPolicy,
build_percentile_forecast_candidates_from_standard_rows,
build_replenishment_decisions_from_simulations,
generate_standard_simulation_rows,
optimize_forecast_targets,
plot_replenishment_decisions,
replenishment_decision_rows_to_dataframe,
simulate_replenishment_with_aggregation,
split_standard_simulation_rows,
standard_simulation_rows_to_dataframe,
)
rows = generate_standard_simulation_rows(
n_unique_ids=1,
periods=24,
start_date="2031-01-01",
frequency_days=30,
forecast_start_period=14,
history_mean=42,
history_std=7,
forecast_mean=40,
forecast_std=5,
lead_time=2,
initial_on_hand=25,
current_stock=25,
seed=11,
percentile_multipliers={"p50": 1.0, "p80": 1.2, "p95": 1.35},
)
backtest_rows, forecast_rows = split_standard_simulation_rows(rows)
percentile_configs = build_percentile_forecast_candidates_from_standard_rows(
backtest_rows,
include_mean=True,
review_period=1,
forecast_horizon=1,
)
optimized = optimize_forecast_targets(percentile_configs)
forecast_configs = build_percentile_forecast_candidates_from_standard_rows(
forecast_rows,
include_mean=True,
review_period=1,
forecast_horizon=1,
)
simulations = {}
for unique_id, config in forecast_configs.items():
target = optimized[unique_id].target
policy = PercentileForecastOptimizationPolicy(
forecast=config.forecast_candidates[target],
lead_time=config.lead_time,
)
simulations[unique_id] = simulate_replenishment_with_aggregation(
periods=config.periods,
demand=config.demand,
initial_on_hand=config.initial_on_hand,
lead_time=config.lead_time,
policy=policy,
aggregation_window=1,
holding_cost_per_unit=config.holding_cost_per_unit,
stockout_cost_per_unit=config.stockout_cost_per_unit,
order_cost_per_order=config.order_cost_per_order,
order_cost_per_unit=config.order_cost_per_unit,
)
decision_rows = build_replenishment_decisions_from_simulations(
forecast_rows,
simulations,
percentile_target={uid: optimized[uid].target for uid in simulations},
review_period=1,
forecast_horizon=1,
rmse_window=1,
)
rows_df = standard_simulation_rows_to_dataframe(rows, library="pandas")
decision_df = replenishment_decision_rows_to_dataframe(decision_rows, library="pandas")
example_id = decision_df["unique_id"].iloc[0]
plot_replenishment_decisions(
rows_df,
decision_df,
unique_id=example_id,
title="Percentile forecast target (optimized)",
decision_style="line",
)
Notebooks
For full runnable walkthroughs (including additional variants):
notebooks/mean_forecast_safety_stock_example.ipynbnotebooks/percentile_optimization_example.ipynbnotebooks/generated_data_example.ipynb
For data-loading and table-oriented flows (without plots), see:
notebooks/stock_replenishment_example.ipynb
Standard schema helpers
If you need CSV/DataFrame conversion helpers:
iter_standard_simulation_rows_from_csvstandard_simulation_rows_from_dataframebuild_point_forecast_article_configs_from_standard_rows
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file replenishment-0.1.0.tar.gz.
File metadata
- Download URL: replenishment-0.1.0.tar.gz
- Upload date:
- Size: 5.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3dcbb9ff63fa5c46ca95834d39627a983fb77a229eff75e18310d664b78eb646
|
|
| MD5 |
7085855d58b7596fec5bc5d22ee5a7f6
|
|
| BLAKE2b-256 |
2b2f532ca99bed398a82c885037da0c569c0aa3b10712c8d911404bc0abbd4f2
|
Provenance
The following attestation bundles were made for replenishment-0.1.0.tar.gz:
Publisher:
release.yml on janrth/replenishment
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
replenishment-0.1.0.tar.gz -
Subject digest:
3dcbb9ff63fa5c46ca95834d39627a983fb77a229eff75e18310d664b78eb646 - Sigstore transparency entry: 928837527
- Sigstore integration time:
-
Permalink:
janrth/replenishment@fdd0a6d3f412afcc48d8bbf41c857d7c6e7a0b93 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/janrth
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fdd0a6d3f412afcc48d8bbf41c857d7c6e7a0b93 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file replenishment-0.1.0-py3-none-any.whl.
File metadata
- Download URL: replenishment-0.1.0-py3-none-any.whl
- Upload date:
- Size: 38.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd73bdf2a9112cd925cefb82203fd4e90c68211a5d0f02f1d9e3b588fee24e2b
|
|
| MD5 |
51dca36bc785e0a5b2b9a1ab8463a286
|
|
| BLAKE2b-256 |
0dd52afda087ecbd2d3f5edfea0b5075c9308d36e33a2eed2ff655c0a8dc110c
|
Provenance
The following attestation bundles were made for replenishment-0.1.0-py3-none-any.whl:
Publisher:
release.yml on janrth/replenishment
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
replenishment-0.1.0-py3-none-any.whl -
Subject digest:
fd73bdf2a9112cd925cefb82203fd4e90c68211a5d0f02f1d9e3b588fee24e2b - Sigstore transparency entry: 928837558
- Sigstore integration time:
-
Permalink:
janrth/replenishment@fdd0a6d3f412afcc48d8bbf41c857d7c6e7a0b93 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/janrth
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fdd0a6d3f412afcc48d8bbf41c857d7c6e7a0b93 -
Trigger Event:
workflow_dispatch
-
Statement type: