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(orsku_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
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Union[DataFrame, List, ndarray]
|
A list or array of SKU ids (then call an |
required |
max_lead_time
|
Optional[int]
|
Pipeline length. |
None
|
sku_column
|
str
|
Name of the SKU column (default |
'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]
|
|
None
|
inventory_position
¶
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 the underlying DataFrame (latest state only).
Returns:
| Type | Description |
|---|---|
DataFrame
|
Copy of the internal DataFrame with current period state |
get_history
¶
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 |
clear_history
¶
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) |
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 |
None
|
on_hand_column
|
str
|
Column holding the counted stock. |
'on_hand'
|
sku_column
|
str
|
SKU column of |
'unique_id'
|
date_column
|
str
|
Column with the opening date (default |
'date'
|
max_lead_time
|
Optional[int]
|
Pipeline length; |
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
|
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'
|
opening_date
|
Optional[Timestamp]
|
The opening date (the day the stock was counted).
Optional when |
None
|
sku_column
|
Optional[str]
|
SKU column of |
None
|
date_column
|
str
|
Column with the opening date (default |
'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 |
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 satisfyperiod < 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
¶
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
¶
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 |
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'
|
date_column
|
str
|
Column with the date; it must equal the state's date. |
'date'
|
sku_column
|
Optional[str]
|
SKU column of |
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 |
required |
demand_column
|
str
|
Column with demand. |
'y'
|
date_column
|
Optional[str]
|
Column with the date. |
'date'
|
sku_column
|
Optional[str]
|
SKU column of |
None
|
Returns:
| Type | Description |
|---|---|
InventoryStateDataFrame
|
A new state. |
OrderDecision
¶
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 the underlying order DataFrame.
Returns:
| Type | Description |
|---|---|
DataFrame
|
Copy of the internal DataFrame |
total_order_quantity
¶
Calculate total order quantity across all SKUs.
Returns:
| Type | Description |
|---|---|
float
|
Sum of all order quantities |
skus_to_order
¶
Count how many SKUs have positive order quantities.
Returns:
| Type | Description |
|---|---|
int
|
Number of SKUs with order_quantity > 0 |
OrderLines
¶
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