Skip to content

State and orders

The inventory state and the order objects that move through a run. Guide: Inventory state.

InventoryStateDataFrame

InventoryStateDataFrame(
    data: Union[DataFrame, List, ndarray],
    max_lead_time: Optional[int] = None,
    sku_column: str = "unique_id",
    opening_date: Optional[Timestamp] = None,
    allow_backorders: Optional[bool] = None,
)

Inventory state of many SKUs at one moment: one row per SKU.

Holds what is on the shelf (on_hand), on its way (in_transit, an array of length max_lead_time whose slot k arrives k + 1 periods from now), and owed to customers (backorders), plus the shared period and date. Policies decide from the inventory position, on_hand + sum(in_transit) - backorders.

The opening state is always supplied, never guessed: use from_observed (counted stock), or build the state from its SKUs and call initialize_from_observed, initialize_zero (empty), or pass a complete state table. During a run the engine owns the state; the methods below are also public for a caller-owned loop.

Main columns
  • unique_id (or sku_column): SKU identifier.
  • on_hand: units on the shelf.
  • in_transit: pipeline array per SKU.
  • backorders: demand owed to customers.
  • period, date: the state's position in time.
  • latest_*: flows of the most recent period (latest_received, latest_fulfilled, latest_shortage, ...), reset each period.
  • target_level: the policy's latest target, for information.
Example
state = InventoryStateDataFrame.from_observed(
    pd.DataFrame({"unique_id": ["tea_250g", "coffee_1kg"], "on_hand": [30.0, 12.0]}),
    opening_date=pd.Timestamp("2026-01-05"),
)
state.inventory_position()

Parameters:

Name Type Description Default
data Union[DataFrame, List, ndarray]

A list or array of SKU ids (then call an initialize_* method), or a DataFrame with the SKU column and, for a complete state, on_hand, backorders, in_transit (NumPy arrays), period and date.

required
max_lead_time Optional[int]

Pipeline length. None (default) sizes it when needed: a run grows it to cover the policy's lead time and the supply model's longest delivery, and placing an order or declaring open orders grows it to fit. Growing only adds empty slots at the far end. Give a number to fix it, for example to leave room for delayed supplier deliveries.

None
sku_column str

Name of the SKU column (default "unique_id").

'unique_id'
opening_date Optional[Timestamp]

Opening date (the day the stock was counted), if not given later to an initializer.

None
allow_backorders Optional[bool]

True (backorders) or False (lost sales). May stay unset until the engine applies the policy's setting; an explicit value must match the policy's.

None

inventory_position

inventory_position() -> DataFrame

Calculate inventory position (IP) for all SKUs.

Inventory Position = on_hand + total_in_transit - backorders

This represents the total inventory committed to satisfy demand, including both physical stock and outstanding orders in transit.

Returns:

Type Description
DataFrame

DataFrame with all original columns plus 'inventory_position' column

get_dataframe

get_dataframe() -> DataFrame

Get the underlying DataFrame (latest state only).

Returns:

Type Description
DataFrame

Copy of the internal DataFrame with current period state

get_history

get_history() -> DataFrame

Get the complete historical DataFrame with all periods stacked.

Returns all accumulated historical states concatenated into a single DataFrame. Each row represents a SKU at a specific period. History accumulates automatically during process_demand() calls.

Returns:

Type Description
DataFrame

DataFrame with all historical states stacked (unique_id × period combinations)

DataFrame

Returns empty DataFrame if no history has been accumulated

Example
# After running simulation for 3 periods
latest = inventory.get_dataframe()  # 9 rows (current period only)
history = inventory.get_history()   # 27 rows (9 SKUs × 3 periods)

clear_history

clear_history() -> None

Clear the accumulated history.

Useful for resetting history tracking without creating a new instance.

initialize_zero

initialize_zero(
    opening_date: Optional[Timestamp] = None,
) -> InventoryStateDataFrame

Initialize all inventory levels to zero.

Sets all numeric inventory columns (on_hand, backorders, target_level, latest_order) to 0 and period to 0. Useful for starting fresh simulations with empty inventory.

Parameters:

Name Type Description Default
opening_date Optional[Timestamp]

Explicit opening date, or omit only when the constructor retained one complete state date

None

Returns:

Type Description
InventoryStateDataFrame

self (for method chaining)

Example
inventory = InventoryStateDataFrame(
    pd.DataFrame({'unique_id': ['SKU_A', 'SKU_B']}),
    max_lead_time=7,
)
inventory.initialize_zero(opening_date=pd.Timestamp('2025-01-01'))
# → on_hand=0, target_level=0, latest_order=0, backorders=0 for all SKUs

from_observed classmethod

from_observed(
    stock_df: DataFrame,
    *,
    opening_date: Optional[Timestamp] = None,
    on_hand_column: str = "on_hand",
    sku_column: str = "unique_id",
    date_column: str = "date",
    max_lead_time: Optional[int] = None,
    allow_backorders: Optional[bool] = None,
) -> InventoryStateDataFrame

Create a state from counted stock: nothing on order, no backorders.

The SKUs are the rows of stock_df. This is the same as InventoryStateDataFrame(skus, ...).initialize_from_observed(...).

Parameters:

Name Type Description Default
stock_df DataFrame

One row per SKU with the SKU column and the counted stock, and optionally a date column with the opening date.

required
opening_date Optional[Timestamp]

The opening date (the day the stock was counted). Optional when stock_df has the date column; checked against it when both are given.

None
on_hand_column str

Column holding the counted stock.

'on_hand'
sku_column str

SKU column of stock_df; the state uses the same name.

'unique_id'
date_column str

Column with the opening date (default "date"). The default column is read only if it exists; a column named explicitly must exist.

'date'
max_lead_time Optional[int]

Pipeline length; None sizes it when needed.

None
allow_backorders Optional[bool]

Leave unset to take the policy's shortage mode.

None

Returns:

Type Description
InventoryStateDataFrame

A new state.

Raises:

Type Description
ValueError

If the SKU column is missing, or as initialize_from_observed.

Example
state = InventoryStateDataFrame.from_observed(
    pd.DataFrame({"unique_id": ["tea", "coffee"], "on_hand": [30.0, 12.0]}),
    opening_date=pd.Timestamp("2026-01-05"),
)

# Or date the table itself:
counts = pd.DataFrame({
    "unique_id": ["tea", "coffee"],
    "date": pd.Timestamp("2026-01-05"),
    "on_hand": [30.0, 12.0],
})
state = InventoryStateDataFrame.from_observed(counts)

initialize_from_observed

initialize_from_observed(
    stock_df: DataFrame,
    *,
    on_hand_column: str = "on_hand",
    opening_date: Optional[Timestamp] = None,
    sku_column: Optional[str] = None,
    date_column: str = "date",
) -> InventoryStateDataFrame

Set counted on-hand stock; no pipeline and no backorders.

The opening date is opening_date, the date column of stock_df, or both (they must then agree). Every row must hold the same date.

Parameters:

Name Type Description Default
stock_df DataFrame

One row per SKU with the SKU column and the stock, and optionally a date column with the opening date.

required
on_hand_column str

Column holding the counted stock (default "on_hand").

'on_hand'
opening_date Optional[Timestamp]

The opening date (the day the stock was counted). Optional when stock_df has the date column.

None
sku_column Optional[str]

SKU column of stock_df; defaults to the state's.

None
date_column str

Column with the opening date (default "date"). The default column is read only if it exists; a column named explicitly must exist.

'date'

Returns:

Type Description
InventoryStateDataFrame

The state itself, for chaining.

Raises:

Type Description
ValueError

If SKUs are missing or extra, stock is negative or not finite, or the opening date is missing, invalid, not the same for every row, or differs from opening_date.

with_open_orders

with_open_orders(
    open_orders: DataFrame,
    *,
    sku_column: Optional[str] = None,
) -> InventoryStateDataFrame

Declare the opening pipeline as explicit open orders.

in_transit stores only the quantity due at each future period. This method records the orders behind it: one row per scheduled delivery, with optional supplier, order period and order grouping. Call it after initialize_zero or initialize_from_observed.

Required columns
  • SKU column (sku_column, default the state's SKU column)
  • due_period: integer state period at which the delivery is received, before that period's demand. It must satisfy period < due_period <= period + max_lead_time.
  • quantity: finite quantity > 0 still to be delivered.
Optional columns
  • supplier_id: hashable supplier identifier (missing = unknown)
  • order_period: integer period the order was placed (<= period; missing = unknown, never inferred)
  • order_id: groups rows that are scheduled deliveries of one order line (partial deliveries). Stockcast assigns its own integer ids in order of first appearance.
  • ordered_quantity: original ordered quantity of the order line, >= its remaining deliveries (default: their sum)

If in_transit is still empty it is set from these rows. If it already holds quantities, the rows must reproduce it exactly: the orders then only attribute the existing pipeline. A disagreement is rejected rather than silently resolved.

Returns:

Type Description
InventoryStateDataFrame

self (for method chaining, like the initializers)

open_orders

open_orders() -> DataFrame

Return one row per open order line.

Columns: order_id, the SKU column, supplier_id, source, order_period, ordered_quantity, remaining_quantity, due_period (next delivery) and final_due_period. A pipeline given only as in_transit appears as opening orders with unknown supplier.

scheduled_receipts

scheduled_receipts() -> DataFrame

Return one row per open delivery: order_id, SKU, supplier_id, due_period, quantity.

advance_period

advance_period(
    *, freq: str, is_review_period: bool
) -> InventoryStateDataFrame

Move to the next period and receive what is due.

Resets the latest_* flows, advances period and date, receives the first pipeline slot, and serves old backorders from it first. Demand is not touched: call fulfill_demand after any decision.

Parameters:

Name Type Description Default
freq str

Length of one period, a pandas frequency such as "D".

required
is_review_period bool

Whether a decision may be made in the new period.

required

Returns:

Type Description
InventoryStateDataFrame

A new state.

fulfill_demand

fulfill_demand(
    demand_df: DataFrame,
    *,
    demand_column: str = "y",
    date_column: str = "date",
    sku_column: Optional[str] = None,
) -> InventoryStateDataFrame

Serve the current period's demand from stock.

Unserved demand becomes backorders or lost sales, per allow_backorders. The period and date do not change.

Parameters:

Name Type Description Default
demand_df DataFrame

One row per SKU for the current date.

required
demand_column str

Column with demand (default "y").

'y'
date_column str

Column with the date; it must equal the state's date.

'date'
sku_column Optional[str]

SKU column of demand_df; defaults to the state's.

None

Returns:

Type Description
InventoryStateDataFrame

A new state.

process_demand

process_demand(
    demand_df: DataFrame,
    *,
    freq: str,
    demand_column: str = "y",
    date_column: Optional[str] = "date",
    sku_column: Optional[str] = None,
) -> InventoryStateDataFrame

Advance, receive and serve demand in one step, without an order.

A shortcut for periods without a decision, so the new period has is_review_period=False. For a decision before demand, use advance_period, then place the order, then fulfill_demand.

Parameters:

Name Type Description Default
demand_df DataFrame

One row per SKU for the next date.

required
freq str

Length of one period, a pandas frequency such as "D".

required
demand_column str

Column with demand.

'y'
date_column Optional[str]

Column with the date.

'date'
sku_column Optional[str]

SKU column of demand_df.

None

Returns:

Type Description
InventoryStateDataFrame

A new state.

OrderDecision

OrderDecision(
    data: DataFrame,
    sku_column: str = "unique_id",
    lead_time: Optional[int] = None,
)

Multi-SKU order decision represented as a DataFrame.

This class represents ordering decisions for multiple SKUs, typically generated by inventory policies (e.g., OrderUpToPolicy.predict()).

Columns
  • unique_id: SKU identifier
  • order_quantity: Amount to order for each SKU
  • target_level: Target inventory level (S in Order-Up-To policy)
  • inventory_position: Current inventory position when order was calculated
  • reorder_point: Reorder point (s in continuous review policies)
  • order_period: Period when order was placed
  • expected_delivery_period: Period when order is expected to arrive

order_quantity is mandatory. Optional diagnostic columns are added with NaN when they are not applicable.

Attributes:

Name Type Description
- lead_time

Lead time from the policy (L)

Example
# Typically created from policy output
policy = OrderUpToPolicy(
    lead_time=7,
    review_period=7,
    service_level=0.95,
    allow_backorders=False,
)
policy.fit(forecast_df)

# predict() automatically creates OrderDecision with policy parameters
orders = policy.predict(inventory_state_df, current_period=decision_period)
# orders.lead_time = 7

# Access order quantities
print(orders.get_dataframe()[['unique_id', 'order_quantity']])

Parameters:

Name Type Description Default
data DataFrame

DataFrame with order data (must include unique_id column)

required
sku_column str

Name of the SKU identifier column (default: 'unique_id')

'unique_id'
lead_time Optional[int]

Lead time from the policy (optional, for parameter propagation)

None

get_dataframe

get_dataframe() -> DataFrame

Get the underlying order DataFrame.

Returns:

Type Description
DataFrame

Copy of the internal DataFrame

total_order_quantity

total_order_quantity() -> float

Calculate total order quantity across all SKUs.

Returns:

Type Description
float

Sum of all order quantities

skus_to_order

skus_to_order() -> int

Count how many SKUs have positive order quantities.

Returns:

Type Description
int

Number of SKUs with order_quantity > 0

OrderLines

OrderLines(data: DataFrame, sku_column: str = 'unique_id')

Supplier order lines with explicit delivery periods (order-level API).

OrderDecision holds one order quantity per SKU and a single lead time. OrderLines is the order-level form used by place_order_lines and by SimulationEngine(..., supply=SupplyModel(...)): one row per scheduled delivery, so one SKU can be ordered from several suppliers, with different lead times, and one order line can be delivered in parts.

Columns
  • unique_id (or sku_column): SKU identifier (may repeat)
  • order_quantity: finite quantity >= 0 of this delivery
  • order_period: state period in which the order is placed
  • due_period: state period in which the delivery is received, before that period's demand (due_period >= order_period; equal means an immediate receipt, as with zero lead time)
  • supplier_id (optional): hashable supplier identifier
  • order_line (optional): rows with the same value are deliveries of one order line (partial deliveries); default one line per row
Example
lines = OrderLines(pd.DataFrame({
    'unique_id':      ['beans', 'beans', 'beans'],
    'supplier_id':    ['local', 'import', 'import'],
    'order_quantity': [20.0,     60.0,     40.0],
    'order_period':   [5,        5,        5],
    'due_period':     [6,        12,       15],
    'order_line':     [0,        1,        1],
}))

get_dataframe

get_dataframe() -> DataFrame

Return a copy of the validated order lines.

total_order_quantity

total_order_quantity() -> float

Sum of all delivery quantities.