LESSON 81 · ADVANCED PROJECT38 min

Project: Inventory Reorder Planner

Turn validated stock records into a stable, explainable reorder plan without changing the source records.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Project: Inventory Reorder Planner

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    A small store wants a list of what to reorder. Each item has current stock, a minimum threshold, and a target level. These numbers have different roles. Minimum decides whether the item enters the order at all. Target decides where stock should be after the order arrives. In our example, Pen is below its minimum of three, so it needs an order of six units to reach eight. Paper is exactly at its minimum of five, so under our chosen rule it should stay off the list. Tape has zero stock and needs four units to reach its target. A rushed implementation can easily use less-than-or-equal at the boundary and subtract from minimum instead of target.

  2. 00:42

    That produces a plausible-looking list with the wrong business decisions. We will run that broken version, diagnose both errors separately, validate the input records, and build a deterministic planner that does not modify its source. The lesson page includes a checked task with empty input, boundary equality, zero stock, duplicate names, and invalid numeric fields. This is intentionally a teaching project. Real purchasing also needs supplier lead times, pack sizes, reservations, and prices; those rules are outside this small contract. The output is wrong in two ways. Paper appears even though stock equals its threshold.

  3. 01:24

    The condition uses less-than-or-equal, while this project's rule says strictly below minimum. Paper also gets an order quantity of zero, which is a warning that trigger and quantity logic have been mixed up. Pen receives one unit because minimum minus current stock is three minus two. That would merely bring it to the trigger level, not to its target of eight. It needs eight minus two, which is six. Tape similarly needs four units, not two. The code is short and syntactically valid, and every row has a number, so a superficial test may miss the problem. The repair is not to tweak the output after the fact. State the business rule first: stock below minimum triggers, and target minus stock

  4. 02:07

    supplies the quantity. Before applying it, we also need to consider what happens if a record is malformed. Negative stock, a target below minimum, or two items with the same normalized name would make the plan unreliable. Validation belongs before output, so a bad last row cannot leave a partial order that somebody might act on. The three rows make the roles clear. At stock two, the strict comparison is True and the amount to reach target is six. At stock three, a hypothetical quantity to target could be five, but the trigger is False, so no order is created. The same is true at stock four.

  5. 02:48

    We calculate the quantity only for triggered rows. The last line is a Python detail that matters for data validation: bool is a subclass of int, so isinstance(True, int) returns True. If an inventory record accidentally contains stock=True, treating that as one item would be misleading. This task requires actual nonnegative integer counts and explicitly rejects booleans. Names must be text, nonblank after trimming, and unique after trimming. Target must be at least minimum, otherwise a triggered item could be sent toward a level below its own trigger. We will check every record before constructing the plan.

  6. 03:30

    A production system could use a typed data model and more nuanced stock states, but the same principle applies: define valid input separately from the operation that consumes it. As you watch the final function, identify where each promised rule is enforced. The first pass validates the whole catalog. It trims names, reads the three numeric fields, rejects missing keys and wrong shapes, rejects booleans and negative counts, checks target against minimum, and records normalized names to catch duplicates. If any record is invalid, the function raises ValueError before returning a plan. The second pass is compact because it can rely on validated data.

  7. 04:14

    It walks the original order, selects only items whose stock is strictly below minimum, and computes target minus stock. There is no sort, so the result is deterministic in the same order as the source list. Paper is absent. Pen and Tape have quantities six and four. The final print shows Pen's original stock is still two; planning an order is not the same as receiving it. Mutating stock during planning would conflate a proposed purchase with a completed delivery. The function returns new tuples, leaving input dictionaries untouched. This two-pass design is easy to explain, though it scans the list twice.

  8. 04:54

    For a small inventory that cost is negligible. If performance mattered, one could validate into normalized records in one pass and build from that list, but the same all-or-nothing contract should remain clear. These assertions cover the branches that a single attractive demo would miss. Empty input returns an empty list. An item exactly at minimum is not reordered, even though its target is higher. Zero stock is valid and produces an order that reaches the target. The source record still says zero afterward, proving that planning did not mutate inventory. The invalid cases then test a Boolean count, a target below minimum, and

  9. 05:36

    duplicate names hidden by surrounding spaces. Each must raise ValueError. If the function silently accepts one, the script raises an assertion instead of printing checks passed. Think about other policies a real store would need: whether reservations count against available stock, whether orders must be in packs of six, and whether two warehouses share stock. Those requirements could change the formula, so they belong in the specification and tests rather than in unspoken assumptions. For this teaching contract, the expected result is deliberately narrow and fully checkable. On the companion page, begin with the broken planner, calculate the three

  10. 06:19

    expected item decisions by hand, then edit the code and run the checker. The quiz asks you to explain why the strict threshold and the target amount are different. Once you can justify both, you can adapt this pattern to a real inventory domain with confidence that every additional rule has a place to be tested.

Understand the concept

An inventory system should distinguish the trigger for a reorder from the amount to order. In this project each record has a name, stock on hand, a minimum threshold, and a target level. Reorder only when stock is strictly below minimum. If stock equals minimum, the item stays off the plan. When a reorder is needed, the quantity is target minus current stock, not minimum minus current stock. Keeping these two roles separate makes the business rule testable.

The plan follows the input order. That gives a deterministic report without introducing a priority rule the data did not specify. A later application could sort by urgency or supplier, but then it should document the sort key and tie behavior. The function returns new tuples and does not mutate the input dictionaries. It also returns an empty list for an empty catalog or a catalog with no items below threshold.

Validation happens before any output is returned. Names must be nonblank and unique after trimming. Stock, minimum, and target must be nonnegative integers rather than booleans, and target must not be below minimum. A malformed record raises ValueError instead of producing a partial order list. This small schema avoids silent negative order quantities and ambiguous duplicate names.

The checked task includes stock exactly at the threshold, zero stock, a multi-item plan, empty input, and invalid records. The solved examples let learners calculate expected order quantities by hand before running Python. This is a teaching project rather than a full purchasing system: it does not model lead time, supplier pack sizes, reservations, or prices. Those would require explicit fields and tests, not hidden assumptions.

  • Trigger threshold and target level are different
  • Input order makes output deterministic
  • Boundary equality must be specified
  • Validate all records before producing a plan

See it step by step

Read the code, predict the output, then compare it with the result.

01. Separate threshold from target

PYTHON
stock, minimum, target = 2, 3, 8
print(stock < minimum)
print(target - stock)
EXPECTED OUTPUT
True
6

The threshold triggers the order; the target determines a six-unit quantity.

02. Keep an item at the boundary off the plan

PYTHON
for stock in (1, 2, 3):
    print(stock, stock < 2)
EXPECTED OUTPUT
1 True
2 False
3 False

Strictly below is the agreed trigger, so stock equal to minimum does not reorder.

03. Build a stable list without mutation

PYTHON
items = [{'name':'Pen','stock':2,'minimum':3,'target':8},{'name':'Tape','stock':0,'minimum':2,'target':4}]
plan = [(x['name'], x['target'] - x['stock']) for x in items if x['stock'] < x['minimum']]
print(plan)
print(items[0]['stock'])
EXPECTED OUTPUT
[('Pen', 6), ('Tape', 4)]
2

The comprehension preserves input order and reads stock without changing the record.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Reproduce a threshold and quantity error

The first implementation uses the minimum twice: it orders when stock is less than or equal to minimum and requests only enough to reach minimum. Neither rule matches this project's contract. The minimum is a strict trigger; the target is the desired post-order level. Two fields exist because they do different jobs.

Run the example with an item exactly at minimum and another below it. The first should not be ordered, but the broken condition includes it. The second needs six units to reach eight, yet minimum minus stock asks for only one. Write the expected result by hand before changing any Python syntax.

PYTHON
items=[{'name':'Pen','stock':2,'minimum':3,'target':8},{'name':'Paper','stock':5,'minimum':5,'target':9}]
broken=[(x['name'],x['minimum']-x['stock']) for x in items if x['stock']<=x['minimum']]
print(broken)
EXPECTED OUTPUT
[('Pen', 1), ('Paper', 0)]
Follow the reasoning
  1. Paper is exactly at the boundary and should be absent.
  2. Pen needs target minus stock, not minimum minus stock.
  3. The correct plan is [('Pen', 6)].
02 / 03

Validate the full input before planning

A reorder report can be misleading if malformed records slip through. A name containing only spaces is empty after trimming. A Boolean passes a plain isinstance(value, int) check because bool is a subclass of int, so the project explicitly excludes it. Negative stock and a target below minimum can produce nonsensical order quantities.

Validate all records before constructing the output list. Keep a set of trimmed names to detect duplicates, including A and a space-padded A. Because validation is separate, a bad final row cannot leave the caller with a partially produced plan. The function will return only after the entire catalog satisfies the input contract.

PYTHON
def valid_item(item):
    name=item['name']
    numbers=[item[k] for k in ('stock','minimum','target')]
    return isinstance(name,str) and bool(name.strip()) and all(isinstance(v,int) and not isinstance(v,bool) and v>=0 for v in numbers) and item['target']>=item['minimum']
print(valid_item({'name':'Pen','stock':2,'minimum':3,'target':8}))
print(valid_item({'name':'Pen','stock':True,'minimum':3,'target':8}))
EXPECTED OUTPUT
True
False
Follow the reasoning
  1. Text and numeric fields have separate rules.
  2. Boolean stock is rejected despite int subclassing.
  3. A full function must also detect repeated names.
03 / 03

Build a deterministic, non-mutating plan

The final function checks the schema and all values, then walks the original list again to build the plan. It returns a tuple for every record strictly below its minimum. The quantity reaches target from current stock. Items exactly at minimum do not enter the result; an empty catalog yields an empty list.

The order follows the input order, making the result stable without inventing supplier priority. No dictionary field is changed. The checked task adds zero stock, duplicate names, a target below minimum, and a Boolean numeric value. Those cases help separate a reliable little planner from a list comprehension that only works for three valid samples.

PYTHON
def plan_reorders(items):
    seen=set()
    for item in items:
        try:
            name=item['name'].strip()
            stock,minimum,target=(item[k] for k in ('stock','minimum','target'))
        except (KeyError,AttributeError,TypeError):
            raise ValueError('invalid item')
        if not name or name.strip() in seen or any(not isinstance(v,int) or isinstance(v,bool) or v<0 for v in (stock,minimum,target)) or target<minimum:
            raise ValueError('invalid item')
        seen.add(name.strip())
    return [(item['name'].strip(),item['target']-item['stock']) for item in items if item['stock']<item['minimum']]
items=[{'name':'Pen','stock':2,'minimum':3,'target':8},{'name':'Paper','stock':5,'minimum':5,'target':9},{'name':'Tape','stock':0,'minimum':2,'target':4}]
print(plan_reorders(items))
EXPECTED OUTPUT
[('Pen', 6), ('Tape', 4)]
Follow the reasoning
  1. Validate every record and duplicate name first.
  2. Use strict stock < minimum to trigger.
  3. Order target minus stock while preserving source order.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete plan_reorders(items). Validate each record; require a unique nonblank name, nonnegative integer stock/minimum/target (bool is invalid), and target >= minimum. Return (name, target-stock) for records whose stock < minimum, preserving input order. Do not mutate items. Prepared output must be [('Pen', 6), ('Tape', 4)].
Need a hint?

Validate into a separate list before building the plan. Use stock < minimum for the trigger and target - stock for the quantity. Keep a set of trimmed names for duplicate detection.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Stock is exactly equal to minimum. Should this project's rule reorder?

    02.Stock is 2, minimum 3, target 8. What quantity is ordered?

    03.Why preserve input order here?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Ordering at the threshold

    COMMON MISTAKE
    if stock <= minimum:
    FIX
    if stock < minimum:

    What happens: An item with stock exactly equal to minimum appears unexpectedly.
    Follow the stated strict boundary.

    Ordering only up to the minimum

    COMMON MISTAKE
    quantity = minimum - stock
    FIX
    quantity = target - stock

    What happens: The order leaves inventory at the trigger threshold instead of the desired target.
    The target determines the replenishment amount.

    Browse this lesson’s problem fixes

    Explore the runnable code

    The GitHub project includes setup instructions and source code you can run on your computer. Use this lesson for the guided examples, browser task, and quiz.

    View this project on GitHub

    Sources and further reading