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

See also

User Contributed Notes
0 notes + add a note