{
  "id": "P006",
  "slug": "inventory",
  "key": "P006-inventory",
  "title": "Inventory",
  "summary": "Items with a unit cost and a safety level, stock received and shipped with every movement logged, a warning for anything below its safety level, and the value of the whole stock - from a text menu.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P006 - Inventory\n\nKeeps items, each with a code, a name, a unit cost and a safety level;\nreceives and ships stock, logging every movement; warns when an item falls\nbelow its safety level; and shows the value of the whole stock on every menu.\nA text menu; the stock lives while the program runs.\n\n- `main.eml` - the menu loop, the questions asked with their checks, and the\n  item list, the low-stock report and the movement log as they appear on screen\n- `stock.eml` - the stock, a class: items in a dict keyed by code, which\n  iterates in the order the items were added, so every report keeps that\n  order; receiving, shipping (more than is on hand is refused with a\n  ValueError and changes nothing), the log, values and shortfalls\n- `fields.eml` - trimming, upper case, item codes, whole numbers, and money\n  read digit by digit into whole cents and written as 1,234.50\n\nMoney is whole cents throughout: a unit cost is read without passing through a\nfloat, and a value is quantity times cents.\n\nWhat is checked: an item code has 2 to 12 letters, digits and dashes, is read\nin upper case, and is new when adding and known when receiving or shipping; a\nname has 1 to 18 characters; a unit cost is written like 4 or 4.50, from 0.01\nto 1,000,000.00; a safety level is a whole number from 0 to 100,000, and a\nquantity from 1 to 100,000. An item below its safety level is marked LOW in\nthe list, named after every shipment that leaves it there, and listed with how\nmany to order in the low-stock report.\n\nSessions: `sessions/basic.in` adds three items (codes typed in lower case\ntoo), receives stock, ships two items below their safety levels, shows the\nlist, the low-stock report and the log, then restocks one item and shows the\nreport again; `sessions/bad-input.in` shows every report while empty, types\ncodes that are empty, contain a space or are too long, an empty name, unit\ncosts that are words, have three decimals or are zero, safety levels that are\nwords or negative, a code that is already there in lower case, a code that is\nnot there, quantities of zero, words and too many, and shipments of more than\nis on hand, which are refused and never reach the log.\n\nBuilt on the verified corpus cases `inventory-tracker` (stock as a class that\nrefuses to remove more than it has) and `inventory-ledger` (a dict of stock\nlevels reported in the order the items were added, with what to reorder).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P006 inventory: items with a unit cost and a safety level; stock received\n# and shipped, every movement logged; a warning for anything below its\n# safety level; the value of the whole stock. The stock lives while the\n# program runs; the first twenty projects do not save to files.\nimport stock\nimport fields\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n\ndef ask_code(inv, must_exist):\n    # An item code, upper-cased: one in the stock when must_exist, a new one\n    # otherwise. None once the problem has been shown.\n    fields.upper(fields.trim(input(\"item code> \"))) => code\n    fields.code_problem(code) => problem\n    if problem != \"\":\n        problem ^0\n        return None\n    if must_exist and not (code in inv.items):\n        (\"No item \" + code + \".\") ^0\n        return None\n    if not must_exist and code in inv.items:\n        (\"There is already an item \" + code + \".\") ^0\n        return None\n    return code\n\ndef ask_quantity():\n    # A whole number from 1 to 100,000, or -1 once the problem has been shown.\n    fields.number(fields.trim(input(\"quantity> \"))) => q\n    if q < 1 or q > 100000:\n        \"A quantity is a whole number from 1 to 100,000.\" ^0\n        return -1\n    return q\n\ndef show_items(inv):\n    \"\" ^0\n    (\"-- items: \" + str(len(inv.items)) + \" --\") ^0\n    if len(inv.items) == 0:\n        \"  (nothing here)\" ^0\n    for code in inv.items:\n        inv.items[code] => item\n        (\"  \" + (\"%-10s\" % code) + (\"%-18s\" % item[0]) + (\"%7d\" % item[1]) + \" x \" + (\"%9s\" % fields.money(item[2])) + \" = \" + (\"%11s\" % fields.money(inv.value(code)))) => line\n        if inv.short(code) > 0:\n            line + \"  LOW\" => line\n        line ^0\n    (\"  \" + (\"%-50s\" % \"total value\") + (\"%11s\" % fields.money(inv.total()))) ^0\n\ndef show_low(inv):\n    inv.low() => codes\n    \"\" ^0\n    (\"-- below safety level: \" + str(len(codes)) + \" --\") ^0\n    if len(codes) == 0:\n        \"  Nothing is below its safety level.\" ^0\n    for code in codes:\n        inv.items[code] => item\n        (\"  \" + (\"%-10s\" % code) + str(item[1]) + \" on hand, safety \" + str(item[3]) + \": order at least \" + str(inv.short(code))) ^0\n\ndef show_log(inv):\n    \"\" ^0\n    (\"-- movements: \" + str(len(inv.log)) + \" --\") ^0\n    if len(inv.log) == 0:\n        \"  (nothing here)\" ^0\n    for m in inv.log:\n        (\"  \" + (\"%3d\" % m[0]) + \"  \" + (\"%-4s\" % m[1]) + (\"%-10s\" % m[2]) + (\"%7d\" % m[3]) + \"   on hand \" + str(m[4])) ^0\n\nstock.Stock() => inv\nTrue => running\nwhile running:\n    \"\" ^0\n    (\"== Inventory: \" + plural(len(inv.items), \"item\") + \", value \" + fields.money(inv.total()) + \" ==\") ^0\n    \"1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\" ^0\n    fields.trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        ask_code(inv, False) => code\n        if code != None:\n            fields.trim(input(\"name> \")) => name\n            if name == \"\" or len(name) > 18:\n                \"A name has 1 to 18 characters.\" ^0\n            else:\n                fields.parse_cents(fields.trim(input(\"unit cost> \"))) => cost\n                if cost < 1 or cost > 100000000:\n                    \"A unit cost looks like 4 or 4.50, from 0.01 to 1,000,000.00.\" ^0\n                else:\n                    fields.number(fields.trim(input(\"safety level> \"))) => safety\n                    if safety < 0 or safety > 100000:\n                        \"A safety level is a whole number from 0 to 100,000.\" ^0\n                    else:\n                        inv.add(code, name, cost, safety)\n                        (\"Added \" + code + \": \" + name + \", \" + fields.money(cost) + \" each, safety \" + str(safety) + \".\") ^0\n    elif choice == \"2\":\n        ask_code(inv, True) => code\n        if code != None:\n            ask_quantity() => q\n            if q > 0:\n                inv.receive(code, q)\n                (\"Received \" + str(q) + \" \" + code + \"; on hand \" + str(inv.items[code][1]) + \".\") ^0\n    elif choice == \"3\":\n        ask_code(inv, True) => code\n        if code != None:\n            ask_quantity() => q\n            if q > 0:\n                try:\n                    inv.ship(code, q)\n                    (\"Shipped \" + str(q) + \" \" + code + \"; on hand \" + str(inv.items[code][1]) + \".\") ^0\n                    if inv.short(code) > 0:\n                        (\"Warning: \" + code + \" is below its safety level of \" + str(inv.items[code][3]) + \".\") ^0\n                except ValueError as e:\n                    (\"Cannot ship \" + str(q) + \" \" + code + \": \" + str(e)) ^0\n    elif choice == \"4\":\n        show_items(inv)\n    elif choice == \"5\":\n        show_low(inv)\n    elif choice == \"6\":\n        show_log(inv)\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import stock\nimport fields\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n\ndef ask_code(inv, must_exist):\n    code = fields.upper(fields.trim(input(\"item code> \")))\n    problem = fields.code_problem(code)\n    if problem != \"\":\n        print(problem)\n        return None\n    if must_exist and not code in inv.items:\n        print(\"No item \" + code + \".\")\n        return None\n    if not must_exist and code in inv.items:\n        print(\"There is already an item \" + code + \".\")\n        return None\n    return code\n\ndef ask_quantity():\n    q = fields.number(fields.trim(input(\"quantity> \")))\n    if q < 1 or q > 100000:\n        print(\"A quantity is a whole number from 1 to 100,000.\")\n        return -1\n    return q\n\ndef show_items(inv):\n    print(\"\")\n    print(\"-- items: \" + str(len(inv.items)) + \" --\")\n    if len(inv.items) == 0:\n        print(\"  (nothing here)\")\n    for code in inv.items:\n        item = inv.items[code]\n        line = \"  \" + \"%-10s\" % code + \"%-18s\" % item[0] + \"%7d\" % item[1] + \" x \" + \"%9s\" % fields.money(item[2]) + \" = \" + \"%11s\" % fields.money(inv.value(code))\n        if inv.short(code) > 0:\n            line = line + \"  LOW\"\n        print(line)\n    print(\"  \" + \"%-50s\" % \"total value\" + \"%11s\" % fields.money(inv.total()))\n\ndef show_low(inv):\n    codes = inv.low()\n    print(\"\")\n    print(\"-- below safety level: \" + str(len(codes)) + \" --\")\n    if len(codes) == 0:\n        print(\"  Nothing is below its safety level.\")\n    for code in codes:\n        item = inv.items[code]\n        print(\"  \" + \"%-10s\" % code + str(item[1]) + \" on hand, safety \" + str(item[3]) + \": order at least \" + str(inv.short(code)))\n\ndef show_log(inv):\n    print(\"\")\n    print(\"-- movements: \" + str(len(inv.log)) + \" --\")\n    if len(inv.log) == 0:\n        print(\"  (nothing here)\")\n    for m in inv.log:\n        print(\"  \" + \"%3d\" % m[0] + \"  \" + \"%-4s\" % m[1] + \"%-10s\" % m[2] + \"%7d\" % m[3] + \"   on hand \" + str(m[4]))\n\ninv = stock.Stock()\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"== Inventory: \" + plural(len(inv.items), \"item\") + \", value \" + fields.money(inv.total()) + \" ==\")\n    print(\"1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\")\n    choice = fields.trim(input(\"choice> \"))\n    if choice == \"1\":\n        code = ask_code(inv, False)\n        if code != None:\n            name = fields.trim(input(\"name> \"))\n            if name == \"\" or len(name) > 18:\n                print(\"A name has 1 to 18 characters.\")\n            else:\n                cost = fields.parse_cents(fields.trim(input(\"unit cost> \")))\n                if cost < 1 or cost > 100000000:\n                    print(\"A unit cost looks like 4 or 4.50, from 0.01 to 1,000,000.00.\")\n                else:\n                    safety = fields.number(fields.trim(input(\"safety level> \")))\n                    if safety < 0 or safety > 100000:\n                        print(\"A safety level is a whole number from 0 to 100,000.\")\n                    else:\n                        inv.add(code, name, cost, safety)\n                        print(\"Added \" + code + \": \" + name + \", \" + fields.money(cost) + \" each, safety \" + str(safety) + \".\")\n    elif choice == \"2\":\n        code = ask_code(inv, True)\n        if code != None:\n            q = ask_quantity()\n            if q > 0:\n                inv.receive(code, q)\n                print(\"Received \" + str(q) + \" \" + code + \"; on hand \" + str(inv.items[code][1]) + \".\")\n    elif choice == \"3\":\n        code = ask_code(inv, True)\n        if code != None:\n            q = ask_quantity()\n            if q > 0:\n                try:\n                    inv.ship(code, q)\n                    print(\"Shipped \" + str(q) + \" \" + code + \"; on hand \" + str(inv.items[code][1]) + \".\")\n                    if inv.short(code) > 0:\n                        print(\"Warning: \" + code + \" is below its safety level of \" + str(inv.items[code][3]) + \".\")\n                except ValueError as e:\n                    print(\"Cannot ship \" + str(q) + \" \" + code + \": \" + str(e))\n    elif choice == \"4\":\n        show_items(inv)\n    elif choice == \"5\":\n        show_low(inv)\n    elif choice == \"6\":\n        show_log(inv)\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "stock.eml",
      "eml": "# P006 inventory - the stock. Items live in a dict keyed by item code, which\n# iterates in the order the items were added, so every report comes out in\n# that order. An item is a list [name, on_hand, unit_cost_cents, safety].\n# Every receipt and shipment is logged as [n, kind, code, quantity,\n# on_hand_after]; a shipment larger than what is on hand is refused with a\n# ValueError and changes nothing.\n\nclass Stock:\n    def __init__(self):\n        {} => self.items\n        [] => self.log\n\n    def add(self, code, name, cost, safety):\n        [name, 0, cost, safety] => self.items[code]\n\n    def receive(self, code, quantity):\n        self.items[code] => item\n        item[1] + quantity => item[1]\n        self.record(\"in\", code, quantity)\n\n    def ship(self, code, quantity):\n        self.items[code] => item\n        if quantity > item[1]:\n            raise ValueError(\"Only \" + str(item[1]) + \" on hand.\")\n        item[1] - quantity => item[1]\n        self.record(\"out\", code, quantity)\n\n    def record(self, kind, code, quantity):\n        self.log + [[len(self.log) + 1, kind, code, quantity, self.items[code][1]]] => self.log\n\n    def value(self, code):\n        # What the item's stock cost, in cents.\n        self.items[code] => item\n        return item[1] * item[2]\n\n    def total(self):\n        0 => cents\n        for code in self.items:\n            cents + self.value(code) => cents\n        return cents\n\n    def short(self, code):\n        # How many the item is below its safety level; 0 if it is not.\n        self.items[code] => item\n        if item[1] < item[3]:\n            return item[3] - item[1]\n        return 0\n\n    def low(self):\n        # The codes of the items below their safety level, in the order added.\n        return [code for code in self.items if self.short(code) > 0]\n",
      "python": "class Stock:\n    def __init__(self):\n        self.items = {}\n        self.log = []\n    def add(self, code, name, cost, safety):\n        self.items[code] = [name, 0, cost, safety]\n    def receive(self, code, quantity):\n        item = self.items[code]\n        item[1] = item[1] + quantity\n        self.record(\"in\", code, quantity)\n    def ship(self, code, quantity):\n        item = self.items[code]\n        if quantity > item[1]:\n            raise ValueError(\"Only \" + str(item[1]) + \" on hand.\")\n        item[1] = item[1] - quantity\n        self.record(\"out\", code, quantity)\n    def record(self, kind, code, quantity):\n        self.log = self.log + [[len(self.log) + 1, kind, code, quantity, self.items[code][1]]]\n    def value(self, code):\n        item = self.items[code]\n        return item[1] * item[2]\n    def total(self):\n        cents = 0\n        for code in self.items:\n            cents = cents + self.value(code)\n        return cents\n    def short(self, code):\n        item = self.items[code]\n        if item[1] < item[3]:\n            return item[3] - item[1]\n        return 0\n    def low(self):\n        return [code for code in self.items if self.short(code) > 0]\n"
    },
    {
      "name": "fields.eml",
      "eml": "# P006 inventory - reading and writing what is typed: item codes, whole\n# numbers and money. Money is whole cents from the moment it is typed: \"4.50\"\n# is read digit by digit into 450 and never passes through a float. The\n# interpreter that checks every session does not run string methods yet, so\n# the text handling is written out here.\n\ndef trim(s):\n    # s without the spaces at either end.\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef upper(s):\n    # s with a-z turned into A-Z; everything else as it was.\n    \"abcdefghijklmnopqrstuvwxyz\" => lower_letters\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => upper_letters\n    \"\" => out\n    for c in s:\n        c => d\n        for i in [0:25]:\n            if lower_letters[i] == c:\n                upper_letters[i] => d\n        out + d => out\n    return out\n\ndef code_problem(code):\n    # What is wrong with code as an item code, or \"\" if nothing is: 2 to 12\n    # letters, digits and dashes.\n    if len(code) < 2 or len(code) > 12:\n        return \"An item code has 2 to 12 characters.\"\n    for c in code:\n        if not (c in \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-\"):\n            return \"An item code has only letters, digits and dashes.\"\n    return \"\"\n\ndef number(s):\n    # The value of s if it is digits only (at least one), otherwise -1.\n    if s == \"\":\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef parse_cents(s):\n    # The amount in s in whole cents, or -1 if s is not written like 4, 4.5\n    # or 4.50: digits, then a point and one or two digits if any.\n    0 => whole\n    0 => whole_digits\n    0 => frac\n    0 => frac_digits\n    False => point\n    for c in s:\n        if c == \".\":\n            if point:\n                return -1\n            True => point\n        elif c in \"0123456789\":\n            if point:\n                frac * 10 + int(c) => frac\n                frac_digits + 1 => frac_digits\n            else:\n                whole * 10 + int(c) => whole\n                whole_digits + 1 => whole_digits\n        else:\n            return -1\n    if whole_digits == 0:\n        return -1\n    if point and (frac_digits == 0 or frac_digits > 2):\n        return -1\n    if frac_digits == 1:\n        frac * 10 => frac\n    return whole * 100 + frac\n\ndef money(c):\n    # Whole cents as 1,234.50, built from the digits of the integer.\n    str(c) => s\n    while len(s) < 3:\n        \"0\" + s => s\n    s[0:len(s) - 2] => whole\n    s[len(s) - 2:len(s)] => cents\n    \"\" => grouped\n    len(whole) => i\n    while i > 3:\n        \",\" + whole[i - 3:i] + grouped => grouped\n        i - 3 => i\n    return whole[0:i] + grouped + \".\" + cents\n",
      "python": "def trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef upper(s):\n    lower_letters = \"abcdefghijklmnopqrstuvwxyz\"\n    upper_letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    out = \"\"\n    for c in s:\n        d = c\n        for i in range(0, 26):\n            if lower_letters[i] == c:\n                d = upper_letters[i]\n        out = out + d\n    return out\n\ndef code_problem(code):\n    if len(code) < 2 or len(code) > 12:\n        return \"An item code has 2 to 12 characters.\"\n    for c in code:\n        if not c in \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-\":\n            return \"An item code has only letters, digits and dashes.\"\n    return \"\"\n\ndef number(s):\n    if s == \"\":\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef parse_cents(s):\n    whole = 0\n    whole_digits = 0\n    frac = 0\n    frac_digits = 0\n    point = False\n    for c in s:\n        if c == \".\":\n            if point:\n                return -1\n            point = True\n        elif c in \"0123456789\":\n            if point:\n                frac = frac * 10 + int(c)\n                frac_digits = frac_digits + 1\n            else:\n                whole = whole * 10 + int(c)\n                whole_digits = whole_digits + 1\n        else:\n            return -1\n    if whole_digits == 0:\n        return -1\n    if point and (frac_digits == 0 or frac_digits > 2):\n        return -1\n    if frac_digits == 1:\n        frac = frac * 10\n    return whole * 100 + frac\n\ndef money(c):\n    s = str(c)\n    while len(s) < 3:\n        s = \"0\" + s\n    whole = s[0:len(s) - 2]\n    cents = s[len(s) - 2:len(s)]\n    grouped = \"\"\n    i = len(whole)\n    while i > 3:\n        grouped = \",\" + whole[i - 3:i] + grouped\n        i = i - 3\n    return whole[0:i] + grouped + \".\" + cents\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "9\n4\n5\n6\n1\n\n1\nbolt m6\n1\nVERY-LONG-CODE-1\n1\nB1\n\n1\nB1\nBolt\nabc\n1\nB1\nBolt\n1.234\n1\nB1\nBolt\n0\n1\nB1\nBolt\n0.10\nx\n1\nB1\nBolt\n0.10\n-1\n1\nB1\nBolt\n0.10\n10\n1\nb1\n2\nZZ\n2\nB1\n0\n2\nB1\nabc\n2\nB1\n100001\n3\nB1\n1\n2\nB1\n10\n3\nB1\n11\n3\nB1\n10\n6\n7\n",
      "screen": "\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 9\nPick a number from 1 to 7.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 4\n\n-- items: 0 --\n  (nothing here)\n  total value                                              0.00\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 5\n\n-- below safety level: 0 --\n  Nothing is below its safety level.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 6\n\n-- movements: 0 --\n  (nothing here)\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> \nAn item code has 2 to 12 characters.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> bolt m6\nAn item code has only letters, digits and dashes.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> VERY-LONG-CODE-1\nAn item code has 2 to 12 characters.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> \nA name has 1 to 18 characters.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> Bolt\nunit cost> abc\nA unit cost looks like 4 or 4.50, from 0.01 to 1,000,000.00.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> Bolt\nunit cost> 1.234\nA unit cost looks like 4 or 4.50, from 0.01 to 1,000,000.00.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> Bolt\nunit cost> 0\nA unit cost looks like 4 or 4.50, from 0.01 to 1,000,000.00.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> Bolt\nunit cost> 0.10\nsafety level> x\nA safety level is a whole number from 0 to 100,000.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> Bolt\nunit cost> 0.10\nsafety level> -1\nA safety level is a whole number from 0 to 100,000.\n\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> B1\nname> Bolt\nunit cost> 0.10\nsafety level> 10\nAdded B1: Bolt, 0.10 each, safety 10.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> b1\nThere is already an item B1.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> ZZ\nNo item ZZ.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> B1\nquantity> 0\nA quantity is a whole number from 1 to 100,000.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> B1\nquantity> abc\nA quantity is a whole number from 1 to 100,000.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> B1\nquantity> 100001\nA quantity is a whole number from 1 to 100,000.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 3\nitem code> B1\nquantity> 1\nCannot ship 1 B1: Only 0 on hand.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> B1\nquantity> 10\nReceived 10 B1; on hand 10.\n\n== Inventory: 1 item, value 1.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 3\nitem code> B1\nquantity> 11\nCannot ship 11 B1: Only 10 on hand.\n\n== Inventory: 1 item, value 1.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 3\nitem code> B1\nquantity> 10\nShipped 10 B1; on hand 0.\nWarning: B1 is below its safety level of 10.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 6\n\n-- movements: 2 --\n    1  in  B1             10   on hand 10\n    2  out B1             10   on hand 0\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nbolt-m6\nBolt M6\n0.12\n500\n1\nNUT-M6\nNut M6\n0.05\n500\n1\nDRILL-10\nDrill bit 10 mm\n45\n5\n2\nBOLT-M6\n1200\n2\nnut-m6\n800\n2\nDRILL-10\n30\n3\nBOLT-M6\n750\n3\nDRILL-10\n27\n4\n5\n6\n2\nBOLT-M6\n100\n5\n7\n",
      "screen": "\n== Inventory: 0 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> bolt-m6\nname> Bolt M6\nunit cost> 0.12\nsafety level> 500\nAdded BOLT-M6: Bolt M6, 0.12 each, safety 500.\n\n== Inventory: 1 item, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> NUT-M6\nname> Nut M6\nunit cost> 0.05\nsafety level> 500\nAdded NUT-M6: Nut M6, 0.05 each, safety 500.\n\n== Inventory: 2 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 1\nitem code> DRILL-10\nname> Drill bit 10 mm\nunit cost> 45\nsafety level> 5\nAdded DRILL-10: Drill bit 10 mm, 45.00 each, safety 5.\n\n== Inventory: 3 items, value 0.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> BOLT-M6\nquantity> 1200\nReceived 1200 BOLT-M6; on hand 1200.\n\n== Inventory: 3 items, value 144.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> nut-m6\nquantity> 800\nReceived 800 NUT-M6; on hand 800.\n\n== Inventory: 3 items, value 184.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> DRILL-10\nquantity> 30\nReceived 30 DRILL-10; on hand 30.\n\n== Inventory: 3 items, value 1,534.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 3\nitem code> BOLT-M6\nquantity> 750\nShipped 750 BOLT-M6; on hand 450.\nWarning: BOLT-M6 is below its safety level of 500.\n\n== Inventory: 3 items, value 1,444.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 3\nitem code> DRILL-10\nquantity> 27\nShipped 27 DRILL-10; on hand 3.\nWarning: DRILL-10 is below its safety level of 5.\n\n== Inventory: 3 items, value 229.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 4\n\n-- items: 3 --\n  BOLT-M6   Bolt M6               450 x      0.12 =       54.00  LOW\n  NUT-M6    Nut M6                800 x      0.05 =       40.00\n  DRILL-10  Drill bit 10 mm         3 x     45.00 =      135.00  LOW\n  total value                                            229.00\n\n== Inventory: 3 items, value 229.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 5\n\n-- below safety level: 2 --\n  BOLT-M6   450 on hand, safety 500: order at least 50\n  DRILL-10  3 on hand, safety 5: order at least 2\n\n== Inventory: 3 items, value 229.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 6\n\n-- movements: 5 --\n    1  in  BOLT-M6      1200   on hand 1200\n    2  in  NUT-M6        800   on hand 800\n    3  in  DRILL-10       30   on hand 30\n    4  out BOLT-M6       750   on hand 450\n    5  out DRILL-10       27   on hand 3\n\n== Inventory: 3 items, value 229.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 2\nitem code> BOLT-M6\nquantity> 100\nReceived 100 BOLT-M6; on hand 550.\n\n== Inventory: 3 items, value 241.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 5\n\n-- below safety level: 1 --\n  DRILL-10  3 on hand, safety 5: order at least 2\n\n== Inventory: 3 items, value 241.00 ==\n1) add item  2) receive  3) ship  4) list  5) low stock  6) movements  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "inventory-tracker",
      "caseId": "025-inventory-tracker",
      "title": "Case corpus: a self-authored stock inventory tracker"
    },
    {
      "slug": "inventory-ledger",
      "caseId": "161-inventory-ledger",
      "title": "A dict iterates in insertion order"
    }
  ],
  "updated": "2026-09-28"
}
