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¶

In [1]:
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¶

In [2]:
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¶

In [3]:
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="");
No description has been provided for this image
In [ ]: