Practical Monitoring and Evaluation Framework for Public Health Supply Chain¶
Some performance metrics in public health supply chains need to be monitored for every lifesaving product at every health facility. For example, the Months of Stock (MOS) may have hundreds or thousands of values across products and facilities. So, traditional dashboards are not an option.
Related metrics often follow a problem with a single metric. To infer the cause and decide corrective actions, the Procurement Manager needs a tool to analyze value ranges and behavior across related metrics. Yet, it would be better to prevent a problem. To decide preventive actions, the Procurement Manager needs statistical process control.
For a Procurement Manager the proposed Monitoring and Evaluation Framework automates monitoring value ranges and historical behavior of related performance metrics for public health LMIS. It helps evaluate the necessity of preventive and corrective actions for public health supply chains in a timely manner.
The framework was inspired by two resources: the KPI Quick Start Guide: Public Health Supply Chains in LMICs by Parambir S Gill, Stew Stremel et al., and the NIST/SEMATECH Engineering Statistics Handbook.
Features complementary to a Public Health LMIS¶
|
Feature |
Public Health LMIS |
Monitoring and Evaluation Framework |
|
Explore performance values |
Pooling by demand |
Pushing by schedule to analyze values. |
|
Explore performance behavior |
No |
Pushing by schedule to analyze historical behavior. |
|
Evaluation of related metric |
Defined in SOP or by experience. Mainly manually. |
Automated by AI and knowledge graph. |
Proof of concept¶
Month of Stock metric¶
Read data from xlsx¶
import pandas as pd
df = pd.read_excel(
"moz2020-2023.xlsx",
sheet_name="Data",
parse_dates=["Month"]
).sort_values("Month")
df=df[0:48]
Calculate trend¶
import numpy as np
from statsmodels.nonparametric.smoothers_lowess import lowess
# define thresholds
low_threshold = 5
upper_threshold = 10
# Prepare the data
plot_df = df[["Month", "Value"]].copy()
plot_df["Month"] = pd.to_datetime(plot_df["Month"])
plot_df = plot_df.sort_values("Month").dropna()
# Calculate the linear trend
x = np.arange(len(plot_df))
# slope, intercept = np.polyfit(x, plot_df["Value"], 1)
# plot_df["Trend"] = slope * x + intercept
# Calculate the nonlinear trend
plot_df["Trend"] = lowess(
endog=plot_df["Value"],
exog=x,
frac=0.35,
return_sorted=False
)
Paint¶
import seaborn as sns
import pandas as pd
plot_df1=pd.melt(plot_df, id_vars=['Month'],value_vars=['Value','Trend'],var_name='category') ## de-pivot for sns
g=sns.relplot(
data=plot_df1, kind="line",
x="Month", y="value",
hue=plot_df1["category"].replace({
"Value": "Data value",
"Trend": "Trend value"
}),
height=10,
aspect=2
)
g.fig.suptitle("Months of stock by months", fontsize=24)
g.fig.subplots_adjust(top=0.9) # Make room for the title
# Horizontal area: sales between 0 and 100
g.ax.axhspan(plot_df1['value'].min(),low_threshold, color="red", alpha=0.15, label="MOS is below minimum");
g.ax.axhspan(upper_threshold,plot_df1['value'].max(), color="orange", alpha=0.15,label="MOS is above maximum");
##axis labels
g.ax.set_xlabel("Reported month", fontsize=20)
g.ax.set_ylabel("Months of stock", fontsize=20)
##legend
g.legend.remove()
g.ax.legend(title="");