Skip to content

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

fill_rate(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

demand_period_service_level(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

cycle_service_level(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 include_partial_cycles (bool).

None

Returns:

Type Description
float

A share between 0 and 1, or NaN when there is no cycle.

sku_period_stockout_rate

sku_period_stockout_rate(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

stockout_period_rate(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

backorder_period_rate(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

demand_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

fulfilled_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

shortage_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

lost_sales_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

order_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

backlog_unit_periods(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

terminal_backlog_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

terminal_pipeline_units(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

order_event_count(
    event_frame: DataFrame, context: Optional[dict] = None
) -> int

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

sku_order_line_count(
    event_frame: DataFrame, context: Optional[dict] = None
) -> int

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

sku_order_quantity_variance(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

order_variance(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

capacity_violation_count(
    event_frame: DataFrame, context: Optional[dict] = None
) -> int

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

capacity_violation_rate(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

avg_on_hand(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

avg_on_order(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

avg_inventory_position(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

peak_ending_on_hand(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

ending_on_hand_variance(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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

inventory_turns(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 periods_per_year (> 0). Without it, the period length is read from the event table's dates: daily 365, weekly 52, monthly 12, quarterly 4, yearly 1 (divided by the step, so "2D" is 182.5). Other frequencies, irregular dates, or fewer than three dates raise an error asking for it.

None

Returns:

Type Description
float

The number of turns per year, or NaN without stock.

Costs

holding_cost

holding_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 holding_cost_per_unit_period unless the event table has a column of that name (per-row rates). Rates are per unit and per period.

None

Returns:

Type Description
float

The cost.

shortage_cost

shortage_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 shortage_cost_per_unit unless the event table has a column of that name (per-row rates). Rates are per unit short.

None

Returns:

Type Description
float

The cost.

backlog_cost

backlog_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 backlog_cost_per_unit_period unless the event table has a column of that name (per-row rates). Rates are per unit owed and per period.

None

Returns:

Type Description
float

The cost.

ordering_cost

ordering_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 order_cost_per_sku_line and order_cost_per_unit, unless the event table has columns of those names.

None

Returns:

Type Description
float

The cost.

purchase_cost

purchase_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 purchase_cost_per_unit unless the event table has a column of that name (per-row rates). Rates are per unit ordered.

None

Returns:

Type Description
float

The cost.

waste_cost

waste_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 waste_cost_per_unit unless the event table has a column of that name (per-row rates). Rates are per unit expired.

None

Returns:

Type Description
float

The cost.

terminal_backlog_cost

terminal_backlog_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 terminal_backlog_cost_per_unit unless the event table has a column of that name (per-row rates). Rates are per unit owed.

None

Returns:

Type Description
float

The cost.

terminal_pipeline_cost

terminal_pipeline_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 terminal_pipeline_cost_per_unit unless the event table has a column of that name (per-row rates). Rates are per unit on order.

None

Returns:

Type Description
float

The cost.

salvage_credit

salvage_credit(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 on_hand_salvage_per_unit and pipeline_salvage_per_unit, unless the event table has columns of those names.

None

Returns:

Type Description
float

The credit (a positive number).

total_cost

total_cost(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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]

cost_components plus the rates of every listed component.

None

Returns:

Type Description
float

The total cost.

cost_per_demand_unit

cost_per_demand_unit(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 total_cost.

None

Returns:

Type Description
float

The cost per unit demanded, or NaN without demand.

cost_per_fulfilled_unit

cost_per_fulfilled_unit(
    event_frame: DataFrame, context: Optional[dict] = None
) -> float

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 total_cost.

None

Returns:

Type Description
float

The cost per unit served, or NaN if nothing was served.