Skip to main content

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

  1. Create a PyPI project named replenishment (or let the first publish create it).
  2. In PyPI, configure a trusted publisher for this repository and workflow:
    • Owner: janrth
    • Repository: replenishment
    • Workflow: release.yml
    • Environment: pypi
  3. In GitHub, create an environment named pypi in 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)

  1. Push a version tag:

    git tag v0.1.0
    git push origin v0.1.0
    
  2. In GitHub, create/publish a release for that tag.

  3. The Release workflow will:

    • build sdist and wheel artifacts,
    • attach them to the GitHub release,
    • publish them to PyPI automatically.

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",
)

Mean forecast + safety stock example

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",
)

Percentile target optimization example

Notebooks

For full runnable walkthroughs (including additional variants):

  • notebooks/mean_forecast_safety_stock_example.ipynb
  • notebooks/percentile_optimization_example.ipynb
  • notebooks/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_csv
  • standard_simulation_rows_from_dataframe
  • build_point_forecast_article_configs_from_standard_rows

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

replenishment-0.1.0.tar.gz (5.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

replenishment-0.1.0-py3-none-any.whl (38.7 kB view details)

Uploaded Python 3

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

Hashes for replenishment-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3dcbb9ff63fa5c46ca95834d39627a983fb77a229eff75e18310d664b78eb646
MD5 7085855d58b7596fec5bc5d22ee5a7f6
BLAKE2b-256 2b2f532ca99bed398a82c885037da0c569c0aa3b10712c8d911404bc0abbd4f2

See more details on using hashes here.

Provenance

The following attestation bundles were made for replenishment-0.1.0.tar.gz:

Publisher: release.yml on janrth/replenishment

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for replenishment-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fd73bdf2a9112cd925cefb82203fd4e90c68211a5d0f02f1d9e3b588fee24e2b
MD5 51dca36bc785e0a5b2b9a1ab8463a286
BLAKE2b-256 0dd52afda087ecbd2d3f5edfea0b5075c9308d36e33a2eed2ff655c0a8dc110c

See more details on using hashes here.

Provenance

The following attestation bundles were made for replenishment-0.1.0-py3-none-any.whl:

Publisher: release.yml on janrth/replenishment

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page