<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P177-stock-reorder/ | updated: 2026-10-19 -->

# P177 Stock reorder

One item's reorder point set for a 5-day lead time when the supplier takes 10, a stock screen that counts units on a ship and units already promised, and a shortage in which every order doubles.

EML-P project `projects/stock-reorder` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/stock-reorder` runs it and `eml project verify projects/stock-reorder` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: the-lead-time-doubled-and-the-reorder-point-did-not (https://efficientnewlanguage.org/cases/906-the-lead-time-doubled-and-the-reorder-point-did-not/), the-stock-was-counted-and-the-promises-were-not (https://efficientnewlanguage.org/cases/910-the-stock-was-counted-and-the-promises-were-not/), the-orders-doubled-because-half-would-be-filled (https://efficientnewlanguage.org/cases/907-the-orders-doubled-because-half-would-be-filled/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
5

5
0
5
31
5
x
5
10

5
10
2001
5
10
x
5
30
2000
5
3
50
6
```

Screen:

```text
== Stock reorder ==
A reorder point, a stock screen and a shortage, each right by its own count.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 0
Pick a number from 1 to 6.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> x
Pick a number from 1 to 6.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 
Cancelled.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 0
Type a whole number from 1 to 30.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 31
Type a whole number from 1 to 30.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> x
Type a whole number from 1 to 30.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 10
reorder point (0 to 2000)> 
Cancelled.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 10
reorder point (0 to 2000)> 2001
Type a whole number from 0 to 2000.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 10
reorder point (0 to 2000)> x
Type a whole number from 0 to 2000.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 30
reorder point (0 to 2000)> 2000
A 30-day lead time and a reorder point of 2,000: out of stock on 58 days of 120, 2,195 units short, 4 orders.
A point of 40 a day times the lead time is 1,200.
One order of 600 at a time cannot cover a wait that sells 1,200: the shelf runs dry, whatever the point.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 3
reorder point (0 to 2000)> 50
A 3-day lead time and a reorder point of 50: out of stock on 14 days of 120, 435 units short, 7 orders.
A point of 40 a day times the lead time is 120.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 6
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
2
3
4
5
10
300
5
10
400
5
1
0
6
```

Screen:

```text
== Stock reorder ==
A reorder point, a stock screen and a shortage, each right by its own count.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 1
A garden hose in a hardware shop's warehouse. It sells 40 a day on average: 35 on Monday, 40 on Tuesday and Wednesday, 45 on Thursday, 50 on Friday, 60 on Saturday and 10 on Sunday.
An order of 600 is placed when stock falls to the reorder point. The point, 200, was set for the supplier's 5-day lead time: 5 days of demand. The supplier now takes 10.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 2
A season of 120 days from 800 in stock:
  lead time  reorder point          days out  units short  orders
  5 days     200                           8          155       7
  10 days    200, unchanged               35        1,210       5
  10 days    400, recomputed               5          120       8
Every order fired on time at its point. With the point left at 5 days of demand, the shelf is empty on 35 days of the 120; recomputed for 10 days, on 5.
Even the recomputed point runs out in a busy week: it covers the average wait, and Saturdays sell 60. Stock kept above the point for that swing is what covers it.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 3
The sales screen shows 1,400: 1,000 on hand and 400 on a ship. Of those on hand, 300 are already promised to earlier orders.
Available to promise today: 1,000 - 300 = 700, half of what the screen shows.
A day of new orders for 900, accepted against the screen, promises 200 units that are not there to deliver.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 4
In a shortage the supplier fills 50% of every order. 1,000 shops each need 100: 100,000 in all.
Knowing half will be filled, each orders 200: an order book of 200,000, and read as demand, capacity built for 200,000.
When supply catches up and every order is filled in full, 100,000 of the book is cancelled: half of it was never demand.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 10
reorder point (0 to 2000)> 300
A 10-day lead time and a reorder point of 300: out of stock on 22 days of 120, 755 units short, 6 orders.
A point of 40 a day times the lead time is 400.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 10
reorder point (0 to 2000)> 400
A 10-day lead time and a reorder point of 400: out of stock on 5 days of 120, 120 units short, 8 orders.
A point of 40 a day times the lead time is 400.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 5
lead time in days (1 to 30)> 1
reorder point (0 to 2000)> 0
A 1-day lead time and a reorder point of 0: out of stock on 14 days of 120, 360 units short, 7 orders.
A point of 40 a day times the lead time is 40.

1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit
choice> 6
Bye.
```

## Modules

### main.eml

```eml
# P177 stock reorder: one item's reorder point, its stock screen and a
# shortage - the item, a season at the new lead time, what can be promised,
# the shortage's order book, and a lead time and point of your own.
import stock

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def pad(s, n):
    s => out
    while len(out) < n:
        out + " " => out
    return out

def lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def digits(t):
    for c in t:
        if c < "0" or c > "9":
            return False
    return len(t) > 0

def commas(n):
    str(n) => s
    "" => out
    0 => k
    len(s) => i
    while i > 0:
        if k == 3:
            "," + out => out
            0 => k
        s[i - 1] + out => out
        k + 1 => k
        i - 1 => i
    return out

def show_item():
    "A garden hose in a hardware shop's warehouse. It sells 40 a day on average: 35 on Monday, 40 on Tuesday and Wednesday, 45 on Thursday, 50 on Friday, 60 on Saturday and 10 on Sunday." ^0
    ("An order of " + commas(stock.order_size()) + " is placed when stock falls to the reorder point. The point, " + str(stock.point_for(stock.old_lead())) + ", was set for the supplier's " + str(stock.old_lead()) + "-day lead time: " + str(stock.old_lead()) + " days of demand. The supplier now takes " + str(stock.new_lead()) + ".") ^0

def row(lead, point, note):
    stock.season(lead, point) => s
    ("  " + pad(str(lead) + " days", 11) + pad(str(point) + note, 22) + lpad(str(s[0]), 9) + lpad(commas(s[1]), 13) + lpad(str(s[2]), 8)) ^0

def show_season():
    "A season of 120 days from 800 in stock:" ^0
    ("  " + pad("lead time", 11) + pad("reorder point", 22) + lpad("days out", 9) + lpad("units short", 13) + lpad("orders", 8)) ^0
    row(stock.old_lead(), stock.point_for(stock.old_lead()), "")
    row(stock.new_lead(), stock.point_for(stock.old_lead()), ", unchanged")
    row(stock.new_lead(), stock.point_for(stock.new_lead()), ", recomputed")
    stock.season(stock.new_lead(), stock.point_for(stock.old_lead())) => a
    stock.season(stock.new_lead(), stock.point_for(stock.new_lead())) => b
    ("Every order fired on time at its point. With the point left at 5 days of demand, the shelf is empty on " + str(a[0]) + " days of the 120; recomputed for 10 days, on " + str(b[0]) + ".") ^0
    "Even the recomputed point runs out in a busy week: it covers the average wait, and Saturdays sell 60. Stock kept above the point for that swing is what covers it." ^0

def show_promise():
    stock.screen() => s
    s[0] + s[1] => shown
    s[0] - s[2] => atp
    ("The sales screen shows " + commas(shown) + ": " + commas(s[0]) + " on hand and " + commas(s[1]) + " on a ship. Of those on hand, " + commas(s[2]) + " are already promised to earlier orders.") ^0
    ("Available to promise today: " + commas(s[0]) + " - " + commas(s[2]) + " = " + commas(atp) + ", half of what the screen shows.") ^0
    ("A day of new orders for 900, accepted against the screen, promises " + commas(900 - atp) + " units that are not there to deliver.") ^0

def show_shortage():
    stock.shortage() => s
    s[0] * s[1] => need
    int(s[1] * 100 / s[2]) => each
    s[0] * each => book
    ("In a shortage the supplier fills " + str(s[2]) + "% of every order. " + commas(s[0]) + " shops each need " + str(s[1]) + ": " + commas(need) + " in all.") ^0
    ("Knowing half will be filled, each orders " + str(each) + ": an order book of " + commas(book) + ", and read as demand, capacity built for " + commas(book) + ".") ^0
    ("When supply catches up and every order is filled in full, " + commas(book - need) + " of the book is cancelled: half of it was never demand.") ^0

def number(prompt, lo, hi):
    plain(input(prompt + " (" + str(lo) + " to " + str(hi) + ")> ")) => t
    if t == "":
        "Cancelled." ^0
        return 0 - 1
    if len(t) > len(str(hi)) or not digits(t) or int(t) < lo or int(t) > hi:
        ("Type a whole number from " + str(lo) + " to " + str(hi) + ".") ^0
        return 0 - 1
    return int(t)

def own():
    number("lead time in days", 1, 30) => lead
    if lead < 0:
        return
    number("reorder point", 0, 2000) => point
    if point < 0:
        return
    stock.season(lead, point) => s
    ("A " + str(lead) + "-day lead time and a reorder point of " + commas(point) + ": out of stock on " + str(s[0]) + " days of 120, " + commas(s[1]) + " units short, " + str(s[2]) + " orders.") ^0
    ("A point of 40 a day times the lead time is " + commas(stock.point_for(lead)) + ".") ^0
    if stock.point_for(lead) > stock.order_size():
        ("One order of " + commas(stock.order_size()) + " at a time cannot cover a wait that sells " + commas(stock.point_for(lead)) + ": the shelf runs dry, whatever the point.") ^0

"== Stock reorder ==" ^0
"A reorder point, a stock screen and a shortage, each right by its own count." ^0
True => running
while running:
    "" ^0
    "1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_item()
    elif choice == "2":
        show_season()
    elif choice == "3":
        show_promise()
    elif choice == "4":
        show_shortage()
    elif choice == "5":
        own()
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import stock

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def pad(s, n):
    out = s
    while len(out) < n:
        out = out + " "
    return out

def lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def digits(t):
    for c in t:
        if c < "0" or c > "9":
            return False
    return len(t) > 0

def commas(n):
    s = str(n)
    out = ""
    k = 0
    i = len(s)
    while i > 0:
        if k == 3:
            out = "," + out
            k = 0
        out = s[i - 1] + out
        k = k + 1
        i = i - 1
    return out

def show_item():
    print("A garden hose in a hardware shop's warehouse. It sells 40 a day on average: 35 on Monday, 40 on Tuesday and Wednesday, 45 on Thursday, 50 on Friday, 60 on Saturday and 10 on Sunday.")
    print("An order of " + commas(stock.order_size()) + " is placed when stock falls to the reorder point. The point, " + str(stock.point_for(stock.old_lead())) + ", was set for the supplier's " + str(stock.old_lead()) + "-day lead time: " + str(stock.old_lead()) + " days of demand. The supplier now takes " + str(stock.new_lead()) + ".")

def row(lead, point, note):
    s = stock.season(lead, point)
    print("  " + pad(str(lead) + " days", 11) + pad(str(point) + note, 22) + lpad(str(s[0]), 9) + lpad(commas(s[1]), 13) + lpad(str(s[2]), 8))

def show_season():
    print("A season of 120 days from 800 in stock:")
    print("  " + pad("lead time", 11) + pad("reorder point", 22) + lpad("days out", 9) + lpad("units short", 13) + lpad("orders", 8))
    row(stock.old_lead(), stock.point_for(stock.old_lead()), "")
    row(stock.new_lead(), stock.point_for(stock.old_lead()), ", unchanged")
    row(stock.new_lead(), stock.point_for(stock.new_lead()), ", recomputed")
    a = stock.season(stock.new_lead(), stock.point_for(stock.old_lead()))
    b = stock.season(stock.new_lead(), stock.point_for(stock.new_lead()))
    print("Every order fired on time at its point. With the point left at 5 days of demand, the shelf is empty on " + str(a[0]) + " days of the 120; recomputed for 10 days, on " + str(b[0]) + ".")
    print("Even the recomputed point runs out in a busy week: it covers the average wait, and Saturdays sell 60. Stock kept above the point for that swing is what covers it.")

def show_promise():
    s = stock.screen()
    shown = s[0] + s[1]
    atp = s[0] - s[2]
    print("The sales screen shows " + commas(shown) + ": " + commas(s[0]) + " on hand and " + commas(s[1]) + " on a ship. Of those on hand, " + commas(s[2]) + " are already promised to earlier orders.")
    print("Available to promise today: " + commas(s[0]) + " - " + commas(s[2]) + " = " + commas(atp) + ", half of what the screen shows.")
    print("A day of new orders for 900, accepted against the screen, promises " + commas(900 - atp) + " units that are not there to deliver.")

def show_shortage():
    s = stock.shortage()
    need = s[0] * s[1]
    each = int(s[1] * 100 / s[2])
    book = s[0] * each
    print("In a shortage the supplier fills " + str(s[2]) + "% of every order. " + commas(s[0]) + " shops each need " + str(s[1]) + ": " + commas(need) + " in all.")
    print("Knowing half will be filled, each orders " + str(each) + ": an order book of " + commas(book) + ", and read as demand, capacity built for " + commas(book) + ".")
    print("When supply catches up and every order is filled in full, " + commas(book - need) + " of the book is cancelled: half of it was never demand.")

def number(prompt, lo, hi):
    t = plain(input(prompt + " (" + str(lo) + " to " + str(hi) + ")> "))
    if t == "":
        print("Cancelled.")
        return 0 - 1
    if len(t) > len(str(hi)) or not digits(t) or int(t) < lo or int(t) > hi:
        print("Type a whole number from " + str(lo) + " to " + str(hi) + ".")
        return 0 - 1
    return int(t)

def own():
    lead = number("lead time in days", 1, 30)
    if lead < 0:
        return
    point = number("reorder point", 0, 2000)
    if point < 0:
        return
    s = stock.season(lead, point)
    print("A " + str(lead) + "-day lead time and a reorder point of " + commas(point) + ": out of stock on " + str(s[0]) + " days of 120, " + commas(s[1]) + " units short, " + str(s[2]) + " orders.")
    print("A point of 40 a day times the lead time is " + commas(stock.point_for(lead)) + ".")
    if stock.point_for(lead) > stock.order_size():
        print("One order of " + commas(stock.order_size()) + " at a time cannot cover a wait that sells " + commas(stock.point_for(lead)) + ": the shelf runs dry, whatever the point.")

print("== Stock reorder ==")
print("A reorder point, a stock screen and a shortage, each right by its own count.")
running = True
while running:
    print("")
    print("1) the item  2) a season  3) what can be promised  4) the shortage  5) a lead time of your own  6) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_item()
    elif choice == "2":
        show_season()
    elif choice == "3":
        show_promise()
    elif choice == "4":
        show_shortage()
    elif choice == "5":
        own()
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")
```

### stock.eml

```eml
# P177 stock reorder - one item in a hardware shop's warehouse. As in the
# corpus case the-lead-time-doubled-and-the-reorder-point-did-not, the
# reorder point was set to cover demand during a 5-day lead time, and the
# supplier now takes 10. As in the-stock-was-counted-and-the-promises-were-
# not, the stock on the sales screen counts units on a ship and units
# already promised. And as in the-orders-doubled-because-half-would-be-
# filled, a supplier that fills half of every order gets orders for twice
# what is needed.

def demand(d):
    # units sold on day d (day 1 is a Monday) when there is stock
    return [35, 40, 40, 45, 50, 60, 10][(d - 1) % 7]

def order_size():
    return 600

def old_lead():
    return 5

def new_lead():
    return 10

def point_for(lead):
    # the reorder point that covers average demand, 40 a day, over a lead time
    return 40 * lead

def season(lead, point):
    # 120 days from 800 in stock: [days out of stock, units short, orders].
    # An order is placed after a day's sales once stock is at the point, and
    # arrives at the end of the day `lead` days later, after its sales.
    800 => stock
    0 => arrives
    0 => out_days
    0 => short
    0 => orders
    1 => d
    while d <= 120:
        demand(d) => want
        if want > stock:
            out_days + 1 => out_days
            short + want - stock => short
            0 => stock
        else:
            stock - want => stock
        if arrives == d:
            stock + order_size() => stock
            0 => arrives
        if stock <= point and arrives == 0:
            d + lead => arrives
            orders + 1 => orders
        d + 1 => d
    return [out_days, short, orders]

def screen():
    # [on hand, on the ship, already promised]
    return [1000, 400, 300]

def shortage():
    # [shops, what each needs, the share of every order the supplier fills, in %]
    return [1000, 100, 50]
```

Python projection of stock.eml:

```python
def demand(d):
    return [35, 40, 40, 45, 50, 60, 10][(d - 1) % 7]

def order_size():
    return 600

def old_lead():
    return 5

def new_lead():
    return 10

def point_for(lead):
    return 40 * lead

def season(lead, point):
    stock = 800
    arrives = 0
    out_days = 0
    short = 0
    orders = 0
    d = 1
    while d <= 120:
        want = demand(d)
        if want > stock:
            out_days = out_days + 1
            short = short + want - stock
            stock = 0
        else:
            stock = stock - want
        if arrives == d:
            stock = stock + order_size()
            arrives = 0
        if stock <= point and arrives == 0:
            arrives = d + lead
            orders = orders + 1
        d = d + 1
    return [out_days, short, orders]

def screen():
    return [1000, 400, 300]

def shortage():
    return [1000, 100, 50]
```

## README

# P177 - Stock reorder

One item in a hardware shop's warehouse: its reorder point, its stock
screen, and a shortage.

- `main.eml` - the menu, the item, a season at the new lead time, what can
  be promised, the shortage's order book, and a lead time and point of
  your own
- `stock.eml` - demand by weekday, the order size, the lead times, the
  point for a lead time, a season day by day, the screen and the shortage

How each part works:

- A garden hose sells 40 a day on average, 60 on Saturdays and 10 on
  Sundays. An order of 600 goes in when stock falls to the reorder point,
  and arrives at the end of the day the lead time later.
- As in the corpus case `the-lead-time-doubled-and-the-reorder-point-did-not`,
  the point of 200 was 5 days of demand. The supplier now takes 10: over a
  season of 120 days the shelf is empty on 35 days with the point
  unchanged, and on 5 with it recomputed to 400 - every order fired on
  time at its point. Even 400 covers only the average wait; points from
  445 up cover the swing. With a wait longer than 15 days, one order of 600
  at a time cannot keep up, whatever the point.
- As in `the-stock-was-counted-and-the-promises-were-not`, the sales screen
  shows 1,400: 1,000 on hand and 400 on a ship, with 300 of those on hand
  already promised. 700 can be promised, and a day of new orders for 900
  taken against the screen promises 200 that are not there.
- As in `the-orders-doubled-because-half-would-be-filled`, a supplier that
  fills 50% of every order gets 200 from each of 1,000 shops that need
  100: a book of 200,000, half of it cancelled when supply catches up.

What is checked: menu choices 1 to 6; a lead time from 1 to 30 days and a
reorder point from 0 to 2000. An empty answer cancels.

Sessions: `sessions/basic.in` gives:
- the item, the season, what can be promised and the shortage;
- your own: points of 300 and 400 at 10 days, and 0 at 1 day.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- lead times: a cancel, 0, 31 and x; points: a cancel, 2001 and x;
- 30 days with a point of 2,000 (no point is enough), and 3 days with 50.

Built on the verified corpus cases `the-lead-time-doubled-and-the-reorder-point-did-not`
(a reorder point set for a 5-day lead time when the supplier takes 10),
`the-stock-was-counted-and-the-promises-were-not` (a sales screen of stock
on hand and in transit) and `the-orders-doubled-because-half-would-be-filled`
(a shortage allocated as half of every order).
