, it is straightforward to make an impact together with your data science and analytics skills.
Even when data quality stays a problem more often than not, you’ll find opportunities to unravel problems by providing insights to operational teams.
Operations Manager: “What number of temporary employees should I recruit next to fulfill our workforce demand at the bottom cost?
After I was a Supply Chain Solution Manager in a logistics company, I learned data science by applying mathematical principles to unravel operational problems in our warehouses.
It was very archaic.
I used to be running Python scripts or Jupyter notebooks on my machine and sharing the outcomes with my colleagues.
Until I discovered Streamlit, which let me effortlessly package my models into web applications that I could easily deploy.

That is crucial for any supply chain process engineer or data scientist to learn how one can productise analytics tools.
Methods to transform Python code into actionable insights?
In this text, I’ll show you how one can transform a simulation model inbuilt Jupyter Notebook into a totally functioning web application.
This exercise was a part of a tutorial series on my YouTube Channel, through which we use Python to learn Inventory Management in Supply Chain.
You’ll find out how I took a core module written in Python scripts to construct an interactive application with which you’ll be able to:
- Simulate multiple inventory management rules
- Test several scenarios with different delivery lead times (LD in days), demand variability (sigma in pieces) and cycle time (T in days)
When you will not be accustomed to these concepts, you’ll be able to still follow this tutorial, as I’ll briefly introduce them in the primary section.
Or you’ll be able to directly jump into the second section that focuses only on the creation and deployment of the app.
Inventory Management Simulation with Python
What’s inventory management?
Most retailers I’ve worked with in Asia and Europe manage store orders with rule-based methods built into their ERPs.
When do you could replenish your stores to avoid stock-outs?
These rules are often implemented in an Enterprise Resource Planning (ERP) software that sends orders to a Warehouse Management System (WMS).

The goal is to develop a policy that minimises ordering, holding and absence costs.
- Ordering Costs: fixed cost to position an order
- Holding Costs: variable costs required to maintain your inventory (storage and capital costs)
- Shortage Costs: the prices of not having enough inventory to fulfill the shopper demand (Lost Sales, Penalty)
We are going to act as data scientists in a retail company and assess the efficiency of the inventory team’s rules.
Logistics Director: “Samir, we’d like your support to grasp why some stores face stockouts while other have an excessive amount of inventory.”
For that, we’d like a tool to simulate multiple scenarios and visualise the impact of key parameters on costs and stock availability.

Within the chart above, you will have an example of a rule with:
- A uniform demand distribution (i.e σ = 0)
- A periodic review policy with T = 10 days
- A delivery lead time of LD = 1 day.
As you’ll be able to see within the green chart, your inventory on-hand (IOH) is all the time positive (i.e. you don’t experience stock-outs).
- What if you will have a 3-day lead time?
- What can be the impact of variability within the demand (σ > 0)?
- Can we reduce the typical inventory available?
To reply these questions, I developed a simulation model using Jupyter Notebook to generate visuals in an entire tutorial.

In this text, I’ll briefly introduce the core functions I inbuilt this tutorial and show how we’ll reuse them in our Streamlit app.
Your Inventory Simulation Tool in a Jupyter Notebook
The outcomes of this tutorial will serve because the core foundation of our Inventory Simulation Streamlit App.
The project structure is basic with two Python files (.py) and a Jupyter Notebook.
tuto_inventory /
├─ Inventory Management.ipynb
└─ inventory/
├─ init.py
├─ inventory_analysis.py
└─ inventory_models.py
In inventory_models.py, you’ll find a Pydantic class that accommodates all of the input parameters for our simulation.
from typing import Optional, Literal
from pydantic import BaseModel, Field
class InventoryParams(BaseModel):
"""Base economic & demand parameters (deterministic day by day demand)."""
D: float = Field(2000, gt=0, description="Annual demand (units/yr)")
T_total: int = Field(365, ge=1, description="Days in horizon (normally 365)")
LD: int = Field(0, ge=0, description="Lead time (days)")
T: int = Field(10, ge=1, description="Cycle time (days)")
Q: float = Field(0, ge=0, description="Order quantity (units)")
initial_ioh: float = Field(0, description="Initial inventory available")
sigma: float = Field(0, ge=0, description="Standard deviation of day by day demand (units/day)")
These operational parameters cover
- Demand Distribution: the overall demand
D, our simulation horizonT_total) and the variability σ ) - Logistics Operations: with the delivery lead time
LD(in days) - Inventory rule parameters: including the cycle time
T, order quantityQand the initial inventory availableinitial_ioh
Based on these parameters, we wish to simulate the impact on our distribution chain:
- Demand distribution: what number of units did we sell?
- Inventory On-Hand in green: what number of units do we’ve in the shop?
- Replenishment orders in blue: when and the way much have we ordered?

In the instance above, you see a simulation of a periodic review policy with a 10-day cycle time.
How did I generate these visuals?
These functions are simulated using the category InventorySimulation created in inventory_analysis.py.
class InventorySimulation:
def __init__(self,
params: InventoryParams):
self.type = type
self.D = params.D
self.T_total = params.T_total
self.LD = params.LD
self.T = params.T
self.Q = params.Q
self.initial_ioh = params.initial_ioh
self.sigma = params.sigma
# # Demand per day (unit/day)
self.D_day = self.D / self.T_total
# Simulation dataframe
self.sim = pd.DataFrame({'time': np.array(range(1, self.T_total+1))})
We start by initialising the input parameters for the functions:
order()which represents a periodic ordering policy (you order Q units every T days)simulation_1()that calculates the impact of the demand (sales) and the ordering policy on the inventory available every day
class InventorySimulation:
''' [Beginning of the class] '''
def order(self, t, T, Q, start_day=1):
"""Order Q starting at `start_day`, then every T days."""
return Q if (t > start_day and ((t-start_day) % T) == 0) else 0
def simulation_1(self):
"""Fixed-cycle ordering; lead time NOT compensated."""
sim_1 = self.sim.copy()
sim_1['demand'] = np.random.normal(self.D_day, self.sigma, self.T_total)
T = int(self.T)
Q = float(self.Q)
sim_1['order'] = sim_1['time'].apply(lambda t: self.order(t, T, Q))
LD = int(self.LD)
sim_1['receipt'] = sim_1['order'].shift(LD, fill_value=0.0)
# Inventory: iterative update to respect lead time
ioh = [self.initial_ioh]
for t in range(1, len(sim_1)):
new_ioh = ioh[-1] - sim_1.loc[t, 'demand']
new_ioh += sim_1.loc[t, 'receipt']
ioh.append(new_ioh)
sim_1['ioh'] = ioh
for col in ['order', 'ioh', 'receipt']:
sim_1[col] = np.rint(sim_1[col]).astype(int)
return sim_1
The function simulation_1() features a mechanism that updates inventory on-hand based on store demand (sales) and provide (replenishment orders).
My goal for this tutorial was to start out with two basic rules to clarify what happens while you introduce a lead time, as shown below.

Stores experience stock-outs, as shown within the green chart.
Their on-hand inventory becomes negative as a result of late deliveries.
What can we do? Possibly increasing the order quantity Q?
That is what I attempted within the tutorial; we discovered that this solution doesn’t work.

These two scenarios highlighted the necessity for an improved ordering policy that compensates for lead times.
That’s what we inbuilt the second a part of the tutorial with these two additional functions.
class InventorySimulation:
''' [Beginning of the class] '''
def order_leadtime(self, t, T, Q, LD, start_day=1):
return Q if (t > start_day and ((t-start_day + (LD-1)) % T) == 0) else 0
def simulation_2(self, method: Optional[str] = "order_leadtime"):
"""Fixed-cycle ordering; lead time NOT compensated."""
sim_1 = self.sim.copy()
LD = int(self.LD)
sim_1['demand'] = np.maximum(np.random.normal(self.D_day, self.sigma, self.T_total), 0)
T = int(self.T)
Q = float(self.Q)
if method == "order_leadtime":
sim_1['order'] = sim_1['time'].apply(lambda t: self.order_leadtime(t, T, Q, LD))
else:
sim_1['order'] = sim_1['time'].apply(lambda t: self.order(t, T, Q))
sim_1['receipt'] = sim_1['order'].shift(LD, fill_value=0.0)
# Inventory: iterative update to respect lead time
ioh = [self.initial_ioh]
for t in range(1, len(sim_1)):
new_ioh = ioh[-1] - sim_1.loc[t, 'demand']
new_ioh += sim_1.loc[t, 'receipt']
ioh.append(new_ioh)
sim_1['ioh'] = ioh
for col in ['order', 'ioh', 'receipt']:
sim_1[col] = np.rint(sim_1[col]).astype(int)
return sim_1
The concept is sort of easy.
In practice, which means inventory planners must create replenishment orders at day = T – LD to compensate for the lead time.

This ensures stores receive their goods on day = T as shown within the chart above.
At the top of this tutorial, we had an entire simulation tool that allows you to test any scenario to change into accustomed to inventory management rules.
When you need more clarification, you’ll find detailed explanations in this step-by-step YouTube tutorial:
Nevertheless, I used to be not satisfied with the result.
Why do we’d like to package this in an internet application?
Productising This Simulation Tool
As you’ll be able to see within the video, I manually modified the parameters within the Jupyter notebook to check different scenarios.

That is somewhat normal for data scientists like us.
But, would you imagine our Logistics Director opening a Jupyter Notebook in VS Code to check the various scenarios?
We’d like to productise this tool so anyone can access it without requiring programming or data science skills.
Excellent news, 70% of the job is completed as we’ve the core modules.
In the following section, I’ll explain how we will package this in a user-friendly analytics product.
Create your Inventory Simulation App using Streamlit
On this section, we’ll create a single-page Streamlit App based on the core module from the primary tutorial.
You may start by cloning this repository that accommodates the core simulation, including inventory_models.py and inventory_analysis.py.

With this repository in your local machine, you’ll be able to follow the tutorial and find yourself with a deployed app like this one:

Let’s start!
Project Setup
Step one is to create a neighborhood Python environment for the project.
For that, I counsel you to make use of the package manager uv:
# Create a virtual environment and activate it
uv init
uv venv
source .venv/bin/activate
# Install
uv pip install -r requirements.txt
It would install the libraries listed in the necessities file:
streamlit>=1.37
pandas>=2.0
numpy>=1.24
matplotlib>=3.7
pydantic>=2.0
We include Streamlit, Pydantic, and libraries for data manipulation (numpy, pandas) and for generating visuals (matplotlib).
Now you’re able to construct your app.
Create your Streamlit Page
Create a Python file that you just call: app.py
import numpy as np
import matplotlib.pyplot as plt
import streamlit as st
from inventory.inventory_models import InventoryParams
from inventory.inventory_analysis import InventorySimulation
seed = 1991
np.random.seed(seed)
st.set_page_config(page_title="Inventory Simulation – Streamlit", layout="wide")
On this file, we start by:
- Importing the libraries installed and the evaluation module with its Pydantic class for the input parameters
- Defining a seed for random distribution generation that shall be used to generate a variable demand
Then we begin to create the page with st.set_page_config(), through which we include as parameters:
page_title: the title of the net page of your applayout: an choice to set the layout of the page

By setting the parameter layout to “wide”, we make sure the page is wide by default.
You may run your app now,
streamlit run app.py
After running this command, your app will be opened using the local URL shared in your terminal:

After loading, what you will have is that this blank page:

Congratulations, you will have run your app!
When you face any issues at this stage, please check that:
- The local Python environment is about up properly
- You could have installed all of the libraries inside the necessities file
Now we will start constructing our Inventory Management App.
A Sidebar with Inventory Management Parameters
Do you remember the Pydantic class we’ve defined in inventory_models.py?
They are going to function our input parameters for the app, and we’ll display them in a Streamlit sidebar.
with st.sidebar:
st.markdown("**Inventory Parameters**")
D = st.number_input("Annual demand D (units/yr)", min_value=1, value=2000, step=50)
T_total = st.number_input("Horizon T_total (days)", min_value=1, value=365, step=1)
LD = st.number_input("Lead time LD (days)", min_value=0, value=0, step=1)
T = st.number_input("Cycle time T (days)", min_value=1, value=10, step=1)
Q = st.number_input("Order quantity Q (units)", min_value=0.0, value=55.0, step=10.0, format="%.2f")
initial_ioh = st.number_input("Initial inventory available", min_value=0.0, value=55.0, step=1.0, format="%.2f")
sigma = st.number_input("Each day demand std. dev. σ (units/day)", min_value=0.0, value=0.0, step=0.5, format="%.2f")
method = st.radio(
"Ordering method",
options=["Simple Ordering", "Lead-time Ordering"],
index=0
)
method_key = "order_leadtime" if method.startswith("Lead-time") else "order"
run = st.button("Run simulation", type="primary")
if run:
st.session_state.has_run = True
On this sidebar, we include:
- A title using
st.markdown() - Number Input fields for all of the parameters with their minimum, default, and incremental step values
- A radio button to pick out the ordering method (considering or not lead time)
- A button to start out the primary simulation

Before this block, we should always add a session state variable:
if "has_run" not in st.session_state:
st.session_state.has_run = False
This boolean indicates whether the user has already clicked the simulation button.
If that’s the case, the app will routinely rerun all calculations each time the user changes a parameter.
Great, now you app.py should appear like this:
import numpy as np
import matplotlib.pyplot as plt
import streamlit as st
from inventory.inventory_models import InventoryParams
from inventory.inventory_analysis import InventorySimulation
seed = 1991
np.random.seed(seed)
st.set_page_config(page_title="Inventory Simulation – Streamlit", layout="wide")
if "has_run" not in st.session_state:
st.session_state.has_run = False
with st.sidebar:
st.markdown("**Inventory Parameters**")
D = st.number_input("Annual demand D (units/yr)", min_value=1, value=2000, step=50)
T_total = st.number_input("Horizon T_total (days)", min_value=1, value=365, step=1)
LD = st.number_input("Lead time LD (days)", min_value=0, value=0, step=1)
T = st.number_input("Cycle time T (days)", min_value=1, value=10, step=1)
Q = st.number_input("Order quantity Q (units)", min_value=0.0, value=55.0, step=10.0, format="%.2f")
initial_ioh = st.number_input("Initial inventory available", min_value=0.0, value=55.0, step=1.0, format="%.2f")
sigma = st.number_input("Each day demand std. dev. σ (units/day)", min_value=0.0, value=0.0, step=0.5, format="%.2f")
method = st.radio(
"Ordering method",
options=["Simple Ordering", "Lead-time Ordering"],
index=0
)
method_key = "order_leadtime" if method.startswith("Lead-time") else "order"
run = st.button("Run simulation", type="primary")
if run:
st.session_state.has_run = True
You may have a take a look at your app, normally the window should appear like this after a refresh:

On the left, you will have your sidebar that we just created.

Then, for aesthetics and user experience, I would like so as to add a title and a reminder of the parameters used.
Why do I would like to remind the parameters used?
On the top-left side of the window, you will have a button to cover the side panel, as shown below.

This helps users to have more room to point out the visual.
Nevertheless, the input parameters shall be hidden.
Subsequently, we’d like so as to add a reminder at the highest.
st.title("Inventory Simulation Web Application")
# Chosen Input Parameters
D_day = D / T_total
st.markdown("""
""", unsafe_allow_html=True)
def quick_card(label, value, unit=""):
unit_html = f'{unit}
' if unit else ""
st.markdown(f'{label}
{value}
{unit_html}', unsafe_allow_html=True)
Within the piece of code above, I actually have introduced :
- CSS styling embedded in Streamlit to create cards
- The function quick_card will create a card for every parameter using its
label,valueandunits

Then we will generate these cards in the identical row using the streamlit object st.columns().
c1, c2, c3, c4, c5, c6 = st.columns(6)
with c1:
quick_card("Average day by day demand", f"{D_day:,.2f}", "units/day")
with c2:
quick_card("Lead time", f"{LD}", "days")
with c3:
quick_card("Cycle time", f"{T}", "days")
with c4:
quick_card("Order quantity Q", f"{Q:,.0f}", "units")
with c5:
quick_card("Initial IOH", f"{initial_ioh:,.0f}", "units")
with c6:
quick_card("Demand σ", f"{sigma:.2f}", "units/day")
The primary line defines the six columns through which we place the cards using the quick_card function.

We will move on to the interesting part: integrating the simulation tool into the app.
Any query or blocking at this step? You should utilize the comment section of the video, I’ll try my best to reply promptly.
Integrating the inventory simulation module within the Streamlit App
We will now start working on the important page.
Allow us to imagine that our Logistics Director arrives on the page, selects the various parameters and clicks on “Run Simulation”.
This could trigger the simulation module:
if st.session_state.has_run:
params = InventoryParams(
D=float(D),
T_total=int(T_total),
LD=int(LD),
T=int(T),
Q=float(Q),
initial_ioh=float(initial_ioh),
sigma=float(sigma)
)
sim_engine = InventorySimulation(params)
On this short piece of code, we do many things:
- We construct the input parameter object using the Pydantic class from
inventory_models.py - We create the simulation engine
InventorySimulationclass frominventory_analysis.py
Nothing will change on the user interface.
We will now begin to define the computation part.
if st.session_state.has_run:
''' [Previous block introduced above]'''
if method_key == "order_leadtime":
df = sim_engine.simulation_2(method="order_leadtime")
elif method_key == "order":
df = sim_engine.simulation_2(method="order")
else:
df = sim_engine.simulation_1()
# Calculate key parameters that shall be shown below the visual
stockouts = (df["ioh"] < 0).sum()
min_ioh = df["ioh"].min()
avg_ioh = df["ioh"].mean()
First, we select the right ordering method.
Depending on the ordering rule chosen within the radio button of the sidebar, we call the suitable simulation method:
- method=”order”: is the initial method that I showed you, which did not keep a stable inventory once I added a lead time
- method=”order_leadtime”: is the improved method that orders at day = T – LD

The outcomes, stored within the pandas dataframe, df are used to compute key indicators reminiscent of the variety of stockouts and the minimum and maximum inventory levels.
Now that we've simulation results available, let’s create our visuals.
Create Inventory Simulation Visuals on Streamlit
When you followed the video version of this tutorial, you most likely noticed that I copied and pasted the code from the notebook created in the primary tutorial.
if st.session_state.has_run:
'''[Previous Blocks introduced above]'''
# Plot
fig, axes = plt.subplots(3, 1, figsize=(9, 4), sharex=True) # ↓ from (12, 8) to (9, 5)
# Demand
df.plot(x='time', y='demand', ax=axes[0], color='r', legend=False, grid=True)
axes[0].set_ylabel("Demand", fontsize=8)
# Orders
df.plot.scatter(x='time', y='order', ax=axes[1], color='b')
axes[1].set_ylabel("Orders", fontsize=8); axes[1].grid(True)
# IOH
df.plot(x='time', y='ioh', ax=axes[2], color='g', legend=False, grid=True)
axes[2].set_ylabel("IOH", fontsize=8); axes[2].set_xlabel("Time (day)", fontsize=8)
# Common x formatting
axes[2].set_xlim(0, int(df["time"].max()))
for ax in axes:
ax.tick_params(axis='x', rotation=90, labelsize=6)
ax.tick_params(axis='y', labelsize=6)
plt.tight_layout()
Indeed, the identical visual code that you just use to prototype in your Notebooks will work in your app.
The one difference is that as an alternative of using plt.show(), you conclude the section with:
st.pyplot(fig, clear_figure=True, use_container_width=True)
Streamlit must explicitly control of the rendering using
figwhich is the Matplotlib figure you created earlier.clear_figure=Truewhich is a parameter to clear the figure from memory after rendering to avoid duplicated plots when parameters are updated.use_container_width=Trueto make the chart routinely resize to the width of the Streamlit page
At this stage, normally you will have this in your app:

When you try to alter any parameter, for instance, change the ordering method, you will note the visual routinely updated.
What's remaining?
As a bonus, I added a bit to assist you discover additional functionalities of streamlit.
if st.session_state.has_run:
'''[Previous Blocks introduced above]'''
# Key parameters presented below the visual
kpi_cols = st.columns(3)
kpi_cols[0].metric("Stockout days", f"{stockouts}")
kpi_cols[1].metric("Min IOH (units)", f"{min_ioh:,.0f}")
kpi_cols[2].metric("Avg IOH (units)", f"{avg_ioh:,.0f}")
# Message of knowledge
st.success("Simulation accomplished.")
Along with the columns that we already defined within the previous section, we've:
metric()that generates a clean and built-in Streamlit cardst.success()to display a green success banner to notify the user that the outcomes have been updated.

That is the cherry on the cake that we would have liked to conclude this app.
You now have an entire app that routinely generates visuals and simply tests scenarios, working in your machine.
What about our Logistics Director? How he can use it?
Let me show you how one can deploy it without spending a dime on Streamlit Community Cloud easily.
Deploy your App on Streamlit Community
You first must push your code to your GitHub, as I did here: GitHub Repository.
You then go to the highest right of your app, and also you click on Deploy.

Then click Deploy now, and follow the steps to offer access to your GitHub account (Streamlit will routinely detect that you will have pushed your code to GitHub).

You may create a custom URL: mine is supplyscience.
After clicking on Deploy, Streamlit will redirect you to your deployed app!
Congratulations, you will have deployed your first Supply Chain Analytics Streamlit App!
Any query? Be at liberty to check the whole tutorial.
Be at liberty to make use of the comment to ask your questions or share what you deployed on Streamlit.
Conclusion
I designed this tutorial for the version of myself from 2018.
Back then, I knew enough Python and analytics to construct optimisation and simulation tools for my colleagues, but I didn't yet know how one can turn them into real products.
This tutorial is step one in your journey toward industrialising analytics solutions.
What you built here might be not production-ready, nevertheless it is a functional app that you would be able to share with our Logistics Director.
Do you would like inspiration for other applications?
Methods to Complete this App?
Now that you will have the playbook for deploying Supply Chain Analytics apps, you most likely wish to improve the app by adding more models.
I actually have a suggestion for you.
You may follow this step-by-step tutorial to implement Product Segmentation with Pareto Evaluation and ABC Chart.
You'll learn how one can deploy strategic visuals just like the ABC XYZ that help retail firms manage their inventory.

This will be easily implemented on the second page of the app that you just just deployed.
If needed, I can work on one other article specializing in this solution!
Uninterested in inventory management?
You could find 80+ case studies of AI & analytics products to support Supply Chain Optimisation, Business Profitability and Process Automation on this cheat sheet.

For many of the case studies, published in Towards Data Science, you'll find the source code that will be implemented in a Streamlit app.
About Me
Let’s connect on Linkedin and Twitter. I'm a Supply Chain Engineer who uses data analytics to enhance logistics operations and reduce costs.
For consulting or advice on analytics and sustainable supply chain transformation, be happy to contact me via Logigreen Consulting.
When you are considering Data Analytics and Supply Chain, take a look at my website.
