How to compute the return of the next replenishment unit
This guide shows how to compute the economic return of replenishing the next unit for a simple single-SKU inventory situation.
Provide the selling price, buy price, lead time, reorder cadence, and the demand
forecast parameters used by actionrwd.reward. Then call
actionrwd.reward to get the demand
distribution that is still uncovered and the expected holding time per ordered unit.
Set a Seed so the example output is reproducible. Finally, combine sell-through
probability, margin, stockout penalty, and carrying cost, then extract the return
for the first unit with valueAt.
table Items = with
[| as Id, as SellPrice, as BuyPrice, as LeadTime, as StepOfReorder,
as MeanDemand, as Dispersion, as Alpha, as StockOnHand |]
[| "cap", 20, 10, 5, 7, 6.0, 2.0, 0.3, 3 |]
table Periods = extend.range(30 into Items)
Periods.Baseline = Items.MeanDemand
Items.Demand, Items.HoldingTime = actionrwd.reward(
TimeIndex: Periods.N,
Baseline: Periods.Baseline,
Dispersion: Items.Dispersion,
Alpha: Items.Alpha,
StockOnHand: Items.StockOnHand,
LeadTime: dirac(Items.LeadTime),
StepOfReorder: Items.StepOfReorder,
Seed: 42)
Items.M = Items.SellPrice - Items.BuyPrice
oosPenalty = 0.5
carryingCost = 0.01
Items.S = oosPenalty * Items.M
Items.C = carryingCost * Items.BuyPrice
Items.SellThrough = (1 - cdf(Items.Demand + 1)) * uniform.right(1)
Items.Return = Items.SellThrough * (Items.M + Items.S) - Items.HoldingTime * Items.C
Items.Return1 = valueAt(Items.Return, 1)
Items.SellThrough1 = valueAt(Items.SellThrough, 1)
Items.Holding1 = valueAt(Items.HoldingTime, 1)
show table "Next unit return" with
Items.Id
Items.Return1
Items.SellThrough1
Items.Holding1
Example output:
| Id | Return1 | SellThrough1 | Holding1 |
|---|---|---|---|
| cap | 14.98868 | 0.9995999 | 0.05319994 |
Use actionrwd.dampen for two-echelon stock
When the warehouse serves stores that hold stock, the uncovered demand at the
warehouse level depends on downstream stock-outs. Use
actionrwd.dampen to convert downstream
stock into a StockOffHand zedfunc, then pass it to actionrwd.reward so the
return reflects the two-echelon network. If demand exists at the warehouse too,
model the warehouse as an extra downstream location with zero stock for
actionrwd.dampen, then use the warehouse stock as StockOnHand in
actionrwd.reward.
table Skus[sku] = with
[| as sku, as SellPrice, as BuyPrice, as Dispersion, as Alpha, as StockOnHand |]
[| "cap", 20, 10, 2.0, 0.2, 4 |]
table Loc[loc] = with
[| as loc |]
[| "north" |]
[| "south" |]
table DownSkus = cross(Skus, Loc)
DownSkus.StockOnHand = if Loc.loc == "north" then 3 else 1
table DownPeriods = extend.range(20 into DownSkus)
DownPeriods.Baseline = if Loc.loc == "north" then 4 else 6
Skus.StockOffHand = actionrwd.dampen(
TimeIndex: DownPeriods.N,
Baseline: DownPeriods.Baseline,
Dispersion: Skus.Dispersion,
Alpha: Skus.Alpha,
StockOnHand: DownSkus.StockOnHand,
Seed: 42)
table NetPeriods = by [Skus.sku, DownPeriods.N]
NetPeriods.Baseline = sum(DownPeriods.Baseline)
NetPeriods.N = same(DownPeriods.N)
Skus.Demand, Skus.HoldingTime = actionrwd.reward(
TimeIndex: NetPeriods.N,
Baseline: NetPeriods.Baseline,
Dispersion: Skus.Dispersion,
Alpha: Skus.Alpha,
StockOnHand: Skus.StockOnHand,
LeadTime: dirac(2),
StepOfReorder: 5,
StockOffHand: Skus.StockOffHand,
Seed: 42)
Skus.M = Skus.SellPrice - Skus.BuyPrice
oosPenalty = 0.5
carryingCost = 0.01
Skus.S = oosPenalty * Skus.M
Skus.C = carryingCost * Skus.BuyPrice
Skus.SellThrough = (1 - cdf(Skus.Demand + 1)) * uniform.right(1)
Skus.Return = Skus.SellThrough * (Skus.M + Skus.S) - Skus.HoldingTime * Skus.C
Skus.StockOffHand1 = valueAt(Skus.StockOffHand, 1)
Skus.Return1 = valueAt(Skus.Return, 1)
show table "Two-echelon return" with
Skus.sku
Skus.StockOffHand1
Skus.Return1
Example output:
| Sku | StockOffHand1 | Return1 |
|---|---|---|
| cap | 0.3977137 | 14.99988 |