Skip to content

Policies

Ordering rules and their target providers. Guide: Policies.

BasePolicy

BasePolicy(
    lead_time: int,
    review_period: Optional[int] = None,
    service_level: Optional[float] = None,
    allow_backorders: bool = None,
    *,
    schedule: Optional[DecisionSchedule] = None,
)

Base class for every ordering policy, in the spirit of PyTorch's nn.Module.

A policy is configured with the operation (lead time, decision schedule, shortage rule), fitted on forecast information with fit, and asked for orders with predict. It reads a copy of the state and returns an OrderDecision; it never changes stock. Subclasses implement fit (set self.fitted_ = True and return self) and predict.

Example
class DaysOfCover(BasePolicy):
    def __init__(self, days, **kwargs):
        super().__init__(**kwargs)
        self.days = days

    def fit(self, forecast_df, **kwargs):
        self.daily_mean_ = forecast_df.set_index("unique_id")["daily_mean"]
        self.fitted_ = True
        return self

    def predict(self, inventory_state_df, *, current_period, **kwargs):
        state = inventory_state_df.inventory_position()
        level = state["unique_id"].map(self.daily_mean_) * self.days
        orders = pd.DataFrame({
            "unique_id": state["unique_id"],
            "order_quantity": (level - state["inventory_position"]).clip(lower=0),
            "order_period": current_period,
            "expected_delivery_period": current_period + self.lead_time,
        })
        return OrderDecision(orders, lead_time=self.lead_time)

Parameters:

Name Type Description Default
lead_time int

Periods from order to delivery, an integer >= 0. An order placed in period t arrives before demand in period t + L.

required
review_period Optional[int]

Periods between ordering opportunities, an integer >= 1. Shorthand for schedule=PeriodicSchedule(review_period).

None
service_level Optional[float]

Probability that the policy's targets represent, in (0, 1), or None when targets are not quantiles.

None
allow_backorders bool

True to keep unserved demand as backorders, False for lost sales. Required.

None
schedule Optional[DecisionSchedule]

A DecisionSchedule. Give either this or review_period (or both, if they agree).

None

Raises:

Type Description
ValueError

If a value is out of range or the timing arguments disagree.

fit

fit(forecast_df: DataFrame, **kwargs: object) -> BasePolicy

Bind forecast information to the policy. Subclasses must implement it.

Parameters:

Name Type Description Default
forecast_df DataFrame

Targets or forecasts, one or more rows per SKU.

required
**kwargs object

Policy-specific options.

{}

Returns:

Type Description
BasePolicy

The fitted policy (self), with fitted_ = True.

predict

predict(
    inventory_state_df: Union[
        DataFrame, InventoryStateDataFrame
    ],
    **kwargs: object,
) -> Union[OrderDecision, DataFrame]

Propose orders for the given state. Subclasses must implement it.

Parameters:

Name Type Description Default
inventory_state_df Union[DataFrame, InventoryStateDataFrame]

A copy of the state before demand.

required
**kwargs object

The engine passes current_period (the state period).

{}

Returns:

Type Description
Union[OrderDecision, DataFrame]

An OrderDecision with one row per SKU and the policy's lead_time.

OrderUpToPolicy

OrderUpToPolicy(
    lead_time: int,
    review_period: Optional[int] = None,
    *,
    freq: Optional[str] = None,
    service_level: Optional[float] = None,
    allow_backorders: bool,
    schedule: Optional[DecisionSchedule] = None,
    sku_column: str = "unique_id",
    date_column: str = "date",
)

Bases: BasePolicy

Order-up-to (R, S) policy: restore the inventory position to S.

At every decision period the policy orders max(0, S - IP), where IP is on hand + on order - backorders. S is fitted per SKU as a target for the protection window H = L + R: typically the service_level quantile of total demand over the window, computed outside Stockcast, or from per-period means and standard deviations under an independent-normal assumption.

Example
policy = OrderUpToPolicy(
    lead_time=2, review_period=4, freq="D", service_level=0.95,
    allow_backorders=False,
).fit(
    targets,                          # unique_id, S: the 95% quantile over 6 days
    target_column="S",
    forecast_origin=pd.Timestamp("2026-01-05"),
)
decision = policy.predict(state, current_period=1)

Parameters:

Name Type Description Default
lead_time int

Lead time in periods (L)

required
review_period Optional[int]

Review period in periods (R)

None
freq Optional[str]

Length of one period, a pandas frequency such as "D" or "W-MON"; lead_time and review_period count periods of this length. Optional when fit can read it from per-period forecast dates.

None
service_level Optional[float]

Target probability, or None for external planner targets

None
allow_backorders bool

Explicitly choose backorders or lost sales

required
schedule Optional[DecisionSchedule]

Optional DecisionSchedule; replaces periodic shorthand.

None
sku_column str

SKU column of the tables given to fit and predict (default "unique_id").

'unique_id'
date_column str

Date column of the table given to fit: the date of the last demand period each row covers (default "date"). The default column is read only if it exists; a column named explicitly must exist.

'date'

fit

fit(
    forecast_df: DataFrame,
    *,
    forecast_origin: Optional[Timestamp] = None,
    target_column: Optional[str] = None,
    target_probability: Optional[float] = None,
    protection_horizon: Optional[int] = None,
    mean_column: Optional[str] = None,
    std_column: Optional[str] = None,
) -> OrderUpToPolicy

Calculate target inventory levels from explicit scientific inputs.

Exactly one input mode must be selected:

  1. Supply one externally calculated protection-period target per SKU with target_column.
  2. Supply consecutive marginal means and standard deviations with mean_column and std_column. They are combined under an independence assumption across periods: S = sum(means) + z * sqrt(sum(stds**2)).

Marginal quantiles are not accepted because their sum is not generally the quantile of cumulative protection-period demand.

The target window runs from forecast_origin for protection_horizon periods, so its end date is forecast_origin + protection_horizon periods. Give the origin, a date column, or both (they must then agree): the origin follows from each row's date exactly. The table's column names are the policy's sku_column and date_column.

The period length is the policy's freq. Without it, mean/std mode reads it from the forecast dates when every SKU has at least three evenly spaced dates on a standard calendar (daily, weekly, monthly, quarterly or yearly, with any step such as "2D"; day steps need a Saturday or Sunday among the dates when weekdays alone would fit a business-day step too); a direct target has one date per SKU, so it needs freq.

Parameters:

Name Type Description Default
forecast_df DataFrame

Target or forecast DataFrame.

required
forecast_origin Optional[Timestamp]

Information-set date at which the forecast or direct target was created. Optional when the date column dates the rows.

None
target_column Optional[str]

Direct protection-period target column. The frame must contain exactly one row per SKU in this mode.

None
target_probability Optional[float]

Probability represented by the target. Defaults to the policy's service_level; if given, it must equal it.

None
protection_horizon Optional[int]

Number of periods represented by the target. Defaults to lead_time + review_period for periodic schedules (and must equal it if given). Nonperiodic schedules require an explicit horizon, checked at each decision against its next opportunity or terminal window.

None
mean_column Optional[str]

Marginal forecast mean column for mean/std mode.

None
std_column Optional[str]

Marginal forecast standard deviation column for mean/std mode. It is required; Stockcast never invents it. Each mean/std row has a step fh (1, 2, ...); without an fh column the steps follow from the dates and a given forecast_origin.

None

Returns:

Type Description
OrderUpToPolicy

self (for method chaining)

validate_decision_window

validate_decision_window(period, information_date, offset)

Check, before a run, that the target covers the decision's window.

Called by the engine for every scheduled decision. For irregular schedules the window is (next decision - period) + lead_time; the forecast origin and end date must match the decision's information date.

Parameters:

Name Type Description Default
period

Zero-based demand period of the decision.

required
information_date

Date of the last demand known at the decision.

required
offset

The simulation's period frequency.

required

Raises:

Type Description
ValueError

If the target's window or dates do not match.

predict

predict(
    inventory_state_df: Union[
        DataFrame, InventoryStateDataFrame
    ],
    sku_column: Optional[str] = None,
    *,
    current_period: int,
) -> OrderDecision

Calculate order quantities for each SKU.

Order quantity = max(0, S - IP) where S = target level (from fit) IP = inventory position (from inventory_state_df)

Parameters:

Name Type Description Default
inventory_state_df Union[DataFrame, InventoryStateDataFrame]

InventoryStateDataFrame object or DataFrame with inventory data Must have 'inventory_position' or ['on_hand', 'on_order', 'backorders']

required
sku_column Optional[str]

SKU column of the state; defaults to the policy's sku_column.

None
current_period int

Explicit decision period used for order and delivery timing.

required

Returns:

Type Description
OrderDecision

OrderDecision object (get_dataframe() gives the table) with columns: - unique_id, order_quantity, target_level, inventory_position, - reorder_point (NaN for this policy), order_period, expected_delivery_period

get_target_levels

get_target_levels() -> DataFrame

Return the fitted order-up-to level S per SKU.

Returns:

Type Description
DataFrame

A DataFrame with the SKU column and target_level.

Raises:

Type Description
ValueError

If the policy is not fitted.

get_target_metadata

get_target_metadata() -> dict

Return how the target was made.

Returns:

Type Description
dict

A dict with representation, target_probability,

dict

protection_horizon, target_source, forecast_origin,

dict

forecast_frequency, target_end_date and, for independent-normal

dict

targets, calculation_method.

Raises:

Type Description
ValueError

If the policy is not fitted.

ReorderPointPolicy

ReorderPointPolicy(
    lead_time: int,
    review_period: Optional[int] = None,
    *,
    freq: Optional[str] = None,
    policy_type: Optional[Literal["sQ", "sS"]] = None,
    service_level: Optional[float] = None,
    order_quantity: Optional[float] = None,
    allow_backorders: bool,
    schedule: Optional[DecisionSchedule] = None,
    sku_column: str = "unique_id",
    date_column: str = "date",
)

Bases: BasePolicy

Order when inventory position is at or below a reorder point s.

  • (s,Q): order a fixed, explicit quantity Q.
  • (s,S): order up to S.

policy_type follows from the inputs: giving order_quantity makes an (s,Q) policy, otherwise (s,S). Passing it states the choice and is checked.

Review timing comes from review_period or an explicit schedule; use review_period=1 for every-period review.

fit takes s (and S) from one of three sources:

  • a forecast table (reorder_point_column): s is dated and covers the schedule's protection window, checked at every decision (see the module docstring). With service_level set, s is the demand quantile at that probability; with service_level=None it is a planner value.
  • fixed values (reorder_point=): one number for every SKU, or one per SKU, for example jointly optimized (s,S) pairs.
  • your own rule (target_provider=), a ReorderPointTargetProvider.

S is never interpreted as a quantile: in the literature s and S are generally determined jointly. Stockcast only requires S >= s.

freq is the length of one period (a pandas frequency such as "D"); lead_time and review_period count periods of this length. Forecast targets need it; fixed values and target providers may omit it.

sku_column (default "unique_id") names the SKU column of the tables given to fit and predict; date_column (default "date") the date column of a forecast table: the last period each row's s covers. The default date column is read only if it exists; a column named explicitly must exist.

fit

fit(
    target_df: Optional[DataFrame] = None,
    *,
    reorder_point_column: Optional[str] = None,
    forecast_origin: Optional[Timestamp] = None,
    reorder_horizon: Optional[int] = None,
    target_probability: Optional[float] = None,
    order_up_to_column: Optional[str] = None,
    reorder_point=None,
    order_up_to_level=None,
    target_provider: Optional[
        ReorderPointTargetProvider
    ] = None,
) -> ReorderPointPolicy

Set the reorder point s (and S for (s,S)) per SKU.

Choose exactly one source:

  1. reorder_point_column (and order_up_to_column): targets in target_df, typically from a forecast. They are dated: give forecast_origin, the policy's date column, or both; the window of s ends reorder_horizon periods after the origin and is checked at every decision.
  2. reorder_point (and order_up_to_level): fixed planning values, one number for every SKU, a {sku: value} dict, or a Series indexed by SKU.
  3. target_provider: your own ReorderPointTargetProvider, called with target_df.

Sources 2 and 3 are planning levels: they need service_level=None and claim no window. Their dates are optional: forecast_origin needs the policy's freq, and the run then checks it as for source 1; freq alone records the period length without an origin.

Parameters:

Name Type Description Default
target_df Optional[DataFrame]

One row per SKU (sources 1 and 3; optional for 2).

None
reorder_point_column Optional[str]

Column holding s.

None
forecast_origin Optional[Timestamp]

Last observed demand date used to build s. Optional when the policy's date column dates the rows: the last period of the window of s, forecast_origin + reorder_horizon periods (source 1 only). The origin follows from it, and is checked against it when also given.

None
reorder_horizon Optional[int]

Protection window of s. Defaults to lead_time + review_period for a periodic schedule (and must equal it if given); other schedules require it and check it at each decision against (next opportunity - decision) + lead_time.

None
target_probability Optional[float]

Quantile mode only. Defaults to service_level; if given, it must equal it. Must be omitted in planner mode.

None
order_up_to_column Optional[str]

Column holding S for an (s,S) policy.

None
reorder_point

Fixed s: a number, a dict, or a Series by SKU.

None
order_up_to_level

Fixed S for an (s,S) policy, in the same forms.

None
target_provider Optional[ReorderPointTargetProvider]

A ReorderPointTargetProvider.

None

Returns:

Type Description
ReorderPointPolicy

The fitted policy (self).

validate_decision_window

validate_decision_window(period, information_date, offset)

Check, before a run, that s covers the decision's window.

Parameters:

Name Type Description Default
period

Zero-based demand period of the decision.

required
information_date

Date of the last demand known at the decision.

required
offset

The simulation's period frequency.

required

Raises:

Type Description
ValueError

If the reorder horizon or dates do not match.

predict

predict(
    inventory_state_df: Union[
        DataFrame, InventoryStateDataFrame
    ],
    sku_column: Optional[str] = None,
    *,
    current_period: int,
) -> OrderDecision

Order when inventory_position <= s.

(s,Q) orders Q; (s,S) orders max(0, S - inventory_position).

get_reorder_points

get_reorder_points() -> DataFrame

Return the fitted reorder point s per SKU.

Returns:

Type Description
DataFrame

A DataFrame with the SKU column and reorder_point.

Raises:

Type Description
ValueError

If the policy is not fitted.

get_parameters

get_parameters() -> DataFrame

Return s with Q (for (s,Q)) or S (for (s,S)) per SKU.

get_target_metadata

get_target_metadata() -> dict

Return a copy of target, window, and quantity provenance.

SingleOrderPolicy

SingleOrderPolicy(
    lead_time: int,
    *,
    freq=None,
    selling_horizon: int,
    decision_period: int = 0,
    service_level=None,
    allow_backorders: bool,
    sku_column: str = "unique_id",
    date_column: str = "date",
)

Bases: OrderUpToPolicy

Buy once for a selling season (newsvendor).

The policy decides once, at decision_period. The order arrives after lead_time periods, which is when the season starts; the season lasts selling_horizon periods. The target is a quantile of total season demand (for example at newsvendor_critical_fractile), and the order is the target minus the stock already available.

Demand outside the season must be zero, the run must cover the whole season, and any opening pipeline must arrive by the season's start.

Parameters:

Name Type Description Default
lead_time int

Periods from the order to the start of the season, >= 0.

required
freq

Length of one period, a pandas frequency such as "D"; lead_time and selling_horizon count periods of this length.

None
selling_horizon int

Length of the season in periods, >= 1.

required
decision_period int

Demand period of the single decision (default 0).

0
service_level

Probability the target represents, or None.

None
allow_backorders bool

True or False.

required
sku_column str

SKU column of the tables given to fit and predict (default "unique_id").

'unique_id'
date_column str

Date column of the target table: the season's last period (default "date"). Read only if the column exists; a column named explicitly must exist.

'date'

fit

fit(
    target_df,
    *,
    target_column,
    forecast_origin=None,
    target_probability=None,
)

Bind the season target.

Give forecast_origin, the policy's date column, or both: the season ends lead_time + selling_horizon periods after the origin, on the date column's date.

Parameters:

Name Type Description Default
target_df

One row per SKU.

required
target_column

Column with the season target.

required
forecast_origin

Date of the last information used, normally the date before the decision. Optional when the date column dates the rows (the season's last period, forecast_origin + (lead_time + selling_horizon) periods); checked against it when both are given.

None
target_probability

Defaults to service_level; if given, it must equal it.

None

Returns:

Type Description

The fitted policy (self).

validate_decision_window

validate_decision_window(period, information_date, offset)

Check, before a run, that the target covers the selling season.

Parameters:

Name Type Description Default
period

Zero-based demand period of the decision.

required
information_date

Date of the last demand known at the decision.

required
offset

The simulation's period frequency.

required

Raises:

Type Description
ValueError

If the forecast origin is not the decision's information date, or the target does not end at the season's last date.

validate_demand_window

validate_demand_window(demand, n_periods)

Check the demand table: zero outside the season, and the whole season simulated.

predict

predict(inventory_state_df, **kwargs)

Order the season target minus available stock.

Raises:

Type Description
ValueError

If opening pipeline would arrive after the season starts.

newsvendor_critical_fractile

newsvendor_critical_fractile(
    *, selling_price, purchase_cost, salvage_value
)

The newsvendor critical fractile (p - c) / (p - v).

For one purchase with selling price p, purchase cost c and salvage value v, the profit-maximising quantity is the demand quantile at this probability. It assumes linear costs, lost sales, and no second purchase.

Parameters:

Name Type Description Default
selling_price

p.

required
purchase_cost

c.

required
salvage_value

v for each unsold unit. Negative for a disposal cost, for example -1 when each leftover unit costs 1 to dispose of.

required

Returns:

Type Description

The probability, between 0 and 1.

Raises:

Type Description
ValueError

Unless p > c > v.

Example
newsvendor_critical_fractile(selling_price=10, purchase_cost=4, salvage_value=2)
# 0.75

rolling_schedule

rolling_schedule(
    policy: BasePolicy,
    targets: DataFrame,
    inventory: InventoryStateDataFrame,
    *,
    origin_column: str = "cutoff",
    missing_targets: str = "raise",
    **fit_kwargs,
) -> Dict[int, BasePolicy]

Fit one policy per forecast origin of a table of targets.

policy gives the configuration: a copy of it is fitted at every origin with that origin's rows and forecast_origin=origin, so every check of fit applies to every snapshot. Origin o belongs to decision period t when o == opening date + t periods of the policy's freq; t must be a decision period of the policy's schedule.

Example
schedule = rolling_schedule(
    OrderUpToPolicy(lead_time=2, review_period=1, freq="W-MON",
                    service_level=0.9, allow_backorders=False),
    targets,                  # unique_id, cutoff, date, q_90
    inventory,
    target_column="q_90",
)
first = min(schedule)
result = SimulationEngine().run(
    policy=schedule[first], policy_schedule=schedule,
    demand_source=demand, inventory=inventory, allow_target_reuse=False,
)

Parameters:

Name Type Description Default
policy BasePolicy

An OrderUpToPolicy or a ReorderPointPolicy (fitted from columns) with freq. It is not changed.

required
targets DataFrame

One row per SKU and origin (per SKU, origin and step fh for mean/std forecasts), with the policy's SKU column, the origin column and the target columns. Rows of SKUs that are not in the inventory are ignored.

required
inventory InventoryStateDataFrame

The opening InventoryStateDataFrame of the run: it gives the opening date and the SKUs every origin must cover.

required
origin_column str

Column holding each row's forecast origin, the last date its forecast saw (default "cutoff").

'cutoff'
missing_targets str

What a row whose target columns are NaN means: the forecast could not produce that target. "raise" (default) stops; "no_order" leaves the SKU out of that origin's policy, so a run with missing_targets="no_order" places no order for it there. A missing row always raises.

'raise'
**fit_kwargs

Arguments of the policy's fit, such as target_column="q_90"; not forecast_origin, which comes from origin_column.

{}

Returns:

Type Description
Dict[int, BasePolicy]

{decision_period: fitted_policy}, sorted by period. Pass the first

Dict[int, BasePolicy]

one as policy and the whole dict as policy_schedule.

Raises:

Type Description
ValueError

If an origin is off the calendar, before the opening date or not a decision period, a decision between the first and the last origin has no origin, an inventory SKU has no row at an origin, a target is missing under missing_targets="raise", or fit rejects an origin's rows (the message names the origin).

TypeError

If the policy or an input has the wrong type.

ReorderPointTargetProvider

Bases: ABC

Base class for your own rule that sets s (and S) per SKU.

Pass an instance to ReorderPointPolicy.fit(data, target_provider=...). Implement provide. Set the class attribute target_source to "external_direct" when the provider only passes along values computed elsewhere (default "custom_provider"), and extend to_manifest with your settings.

provide abstractmethod

provide(
    target_data: DataFrame, *, sku_column: str
) -> ReorderPointTargets

Return one reorder point (and order-up-to level) per SKU.

Parameters:

Name Type Description Default
target_data DataFrame

The table passed to ReorderPointPolicy.fit.

required
sku_column str

SKU column name.

required

Returns:

Type Description
ReorderPointTargets

A ReorderPointTargets. Stockcast then checks that every value is

ReorderPointTargets

finite, s >= 0 and, for (s,S), S >= s.

to_manifest

to_manifest() -> dict

Describe the provider for the run manifest.

ReorderPointTargets dataclass

ReorderPointTargets(
    frame: DataFrame, metadata: dict = dict()
)

The result of a target provider.

Attributes:

Name Type Description
frame DataFrame

One row per SKU with the SKU column and reorder_point, plus order_up_to_level for an (s,S) policy.

metadata dict

JSON-serialisable description, stored in the run manifest.