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

Choose the trajectory count

The call above omits Samples and therefore uses the default 2,500 trajectories. This represents about 125 expected paths beyond P95 and 25 beyond P99. These counts describe coverage of the extreme part of the demand and sell-through curves. They do not determine the accuracy of the expected return, action ranking, or replenishment decision.

Keep the default unless repeated comparisons on representative items support another count. If P99 behavior can change the replenishment decision, include Samples: 10000 in the comparison before applying a larger count to the full dataset. See How to choose a Monte Carlo sample count for the selection and convergence procedure.

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