Metrics¶
Every metric has the signature metric(event_frame, context=None) -> float and is imported from stockcast.evaluation. The metrics guide gives every formula in one table.
Service¶
fill_rate
¶
Share of demand served from stock in the period it occurred.
sum(fulfilled_units) / sum(demand) over period rows, 1.0 when there is no
demand. Backorders served in later periods do not count as filled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The fill rate, between 0 and 1. |
demand_period_service_level
¶
Share of SKU-period rows with demand in which nothing was short.
Rows without demand are excluded; 1.0 when no row has demand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
A share between 0 and 1. |
cycle_service_level
¶
Share of replenishment cycles without any shortage.
A cycle runs from the arrival of one replenishment order to the arrival of
the next, per SKU. An order that arrives in several deliveries (supplier
lines, partial or delayed deliveries) starts one cycle, at its first
delivery; the event table marks these rows in order_arrival_flag. Event tables
without that column use every receipt, which is the same when each order
arrives in one delivery. The cycle before the first arrival and the one
after the last arrival are incomplete; the context says whether they count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Must contain |
None
|
Returns:
| Type | Description |
|---|---|
float
|
A share between 0 and 1, or NaN when there is no cycle. |
sku_period_stockout_rate
¶
Share of SKU-period rows with a shortage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
A share between 0 and 1. |
stockout_period_rate
¶
Share of periods in which any SKU in the slice was short.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
A share between 0 and 1. |
backorder_period_rate
¶
Share of periods in which any SKU in the slice ended with backorders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
A share between 0 and 1. |
Quantities¶
demand_units
¶
Total demand, sum(demand) over period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
fulfilled_units
¶
Total demand served from stock in its own period, sum(fulfilled_units).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
shortage_units
¶
Total demand not served in its own period, sum(shortage_units).
In lost-sales mode this equals lost sales; in backorder mode it equals new backorders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
lost_sales_units
¶
Total lost sales, sum(lost_sales_units); zero in backorder mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
order_units
¶
Total ordered quantity, sum(order_quantity).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
backlog_unit_periods
¶
Backorder exposure, sum(backorders_end) over SKU-period rows.
Measured in unit-periods: 3 units owed for 2 periods count 6.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
terminal_backlog_units
¶
Backorders still open at the end, summed over SKUs.
Uses each SKU's last period row in the slice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
terminal_pipeline_units
¶
Units still on order at the end, summed over SKUs.
Uses each SKU's last period row in the slice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The metric value. |
order_event_count
¶
Number of decisions that placed at least one positive order.
Counted once per decision for the whole portfolio, for a fee per decision:
the event table stores each decision's count on one row, so totals add up
across periods and across runs. It is not a per-SKU count; to count orders
per SKU, use sku_order_line_count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The number of order events. |
sku_order_line_count
¶
Number of positive order lines, sum(sku_order_line_count).
Without a supply model this is one line per SKU and order; with one, each supplier line counts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The number of order lines. |
sku_order_quantity_variance
¶
Population variance of positive order-line sizes.
Computed from order_quantity, order_line_quantity_squared_sum and
sku_order_line_count; NaN when there is no order line. Decisions that
order nothing are not lines, so orders of 30, 0 and 0 give 0.0; for the
variance over decisions with zero orders included, use order_variance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The variance, or NaN. |
order_variance
¶
Variance of order quantities over decisions, zero orders included.
Per SKU: the population variance (ddof=0) of order_quantity over the
SKU's rows with decision_flag True, so a decision that ordered nothing
counts as a zero order. With several SKUs: the mean of the per-SKU
variances; SKUs without a decision row are left out. With
review_period=1 every period is a decision.
This is the order variance of Zhou, Arik and Wang (2023), "Business
Metric-Aware Forecasting for Inventory Management", arXiv:2308.13118,
Sec. 3.2.1: C_v = c_v * Var_t(o_t), taken over all time points and,
as their Appendix A.5 computes it, per series as the mean squared
deviation from the series' mean order. When every SKU has the same number
of decisions, the mean over SKUs equals that mean over all series and time
points. Orders of 30, 0 and 0 give 200.0, while
sku_order_quantity_variance (positive order lines only) gives 0.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The variance, or NaN when the slice has no decision row. |
capacity_violation_count
¶
Number of order lines cut by a capacity constraint.
Counts rows with capacity_violation_flag set (MaximumOrderQuantity,
ShelfSpaceLimit, or a custom capacity rule).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The number of capped order lines. |
capacity_violation_rate
¶
Share of positive requested order lines cut by a capacity constraint.
0.0 when nothing was requested.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
A share between 0 and 1. |
Stock¶
avg_on_hand
¶
Mean ending on-hand stock over SKU-period rows.
A per-SKU-per-period average. For the total stock of a portfolio use
peak_ending_on_hand or sum ending_on_hand by period yourself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The mean, or NaN for an empty slice. |
avg_on_order
¶
Mean ending pipeline (units on order) over SKU-period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The mean, or NaN for an empty slice. |
avg_inventory_position
¶
Mean ending inventory position over SKU-period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The mean, or NaN for an empty slice. |
peak_ending_on_hand
¶
Largest total ending stock in any period.
Stock is first summed over the SKUs of the slice for each period.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The peak, or NaN for an empty slice. |
ending_on_hand_variance
¶
Population variance over periods of total ending stock.
Stock is first summed over the SKUs of the slice for each period.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Not used by this metric; accepted for a uniform signature. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The variance, or NaN for an empty slice. |
inventory_turns
¶
Annualised throughput divided by average total stock.
(sum(fulfilled_units + backorders_fulfilled) / n_periods * periods_per_year)
/ mean_t(total ending stock). Throughput counts every unit that left the
shelf for a customer: demand served in its own period and backorders served
later.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The number of turns per year, or NaN without stock. |
Costs¶
holding_cost
¶
Holding cost, sum(h * ending_on_hand) over period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
shortage_cost
¶
Shortage cost, sum(p * shortage_units) over period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
backlog_cost
¶
Backorder cost, sum(b * backorders_end) over period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
ordering_cost
¶
Ordering cost: a fixed fee per order line plus a cost per unit ordered.
sum(K * sku_order_line_count + c_o * order_quantity). Each SKU order line
(each supplier line with a supply model) pays its own fee, so pooled costs are
sums of SKU costs. For a fee per decision, use order_event_count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options with |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
purchase_cost
¶
Purchase cost, sum(c * order_quantity), charged when orders are placed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
waste_cost
¶
Waste cost, sum(w * expired_units) over period rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
terminal_backlog_cost
¶
Cost of backorders still open at the end of the slice.
Rate times each SKU's final backorders_end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
terminal_pipeline_cost
¶
Cost of units still on order at the end of the slice.
Rate times each SKU's final on_order_end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options. Supplies |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost. |
salvage_credit
¶
Value of the stock and pipeline left at the end of the slice.
on_hand_salvage_per_unit * final ending_on_hand + pipeline_salvage_per_unit *
final on_order_end per SKU. total_cost subtracts it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
Evaluation options with |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The credit (a positive number). |
total_cost
¶
Sum of the cost components you list.
context['cost_components'] names the components: holding, shortage,
backlog, ordering, purchase, waste, terminal_backlog,
terminal_pipeline, salvage. Each listed component needs all of its
rates, zeros included; salvage is subtracted, the others are added.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
|
None
|
Returns:
| Type | Description |
|---|---|
float
|
The total cost. |
cost_per_demand_unit
¶
total_cost divided by total demand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
As for |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost per unit demanded, or NaN without demand. |
cost_per_fulfilled_unit
¶
total_cost divided by units served from stock.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_frame
|
DataFrame
|
Event table, or a slice of it (for example one window or group). |
required |
context
|
Optional[dict]
|
As for |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The cost per unit served, or NaN if nothing was served. |