{
  "id": "P123",
  "slug": "inventory-diff",
  "key": "P123-inventory-diff",
  "title": "Inventory diff",
  "summary": "Two stock counts compared in both directions: added, gone, changed and renamed items, an item very likely moved to a new code, a units balance, and what a one-way check never sees.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P123 - Inventory diff\n\nTwo stock counts, last month's and this month's, each item a code, a name\nand a quantity, compared in both directions.\n\n- `main.eml` - the menu, both counts side by side, the differences, the\n  one-way checks, and changes to the new count\n- `stock.eml` - the two counts, codes in order, the differences, items\n  very likely moved to a new code, the units balance, and the changes\n\nHow each part works:\n\n- As in the corpus case `inventory-diff-report`, a difference has two\n  directions: what is new now, and what is gone since. Checking each item\n  now against before finds the added and the changed, and never the gone;\n  checking before against now never finds the added.\n- Items on both counts are changed (another quantity), renamed (another\n  name under the same code), or the same. A quantity of 0 is still on the\n  list: counted and empty, not gone.\n- The same item counted under a new code shows up as one gone and one\n  added. When a gone item and an added one have the same name and\n  quantity, the program says it is very likely one item.\n- Units now - units before = added - gone + the change on the items in\n  both: 537 - 561 = 42 - 17 - 49.\n- As in the corpus case `dict-inventory-report`, the counts are dicts\n  keyed by code, always reported in sorted order.\n\nWhat is checked: menu choices 1 to 5; a code of a capital letter and 3\ndigits; a quantity from 0 to 99999, or remove for an item on the new\ncount; a name of up to 20 lowercase letters and spaces for a new one. An\nempty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- both counts, the differences and the one-way checks;\n- B205 taken off and B201 added back, after which brackets are the same\n  item again;\n- glue from 0 to 4, the differences and the one-way checks.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- codes: a cancel, d400, D40 and DD400;\n- quantities for D400: a cancel, x, 100000 and -3;\n- a new code Z999: a cancel, Tape, a name too long, then tape with a\n  cancel and remove, tape 2, and masking tape 7;\n- the differences.\n\nBuilt on the verified corpus cases `inventory-diff-report` (a difference\ntaken in both directions) and `dict-inventory-report` (counts in a dict,\nreported in a fixed order).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P123 inventory diff: two stock counts side by side, the differences in\n# both directions, what a one-way check misses, and changes to the new\n# count.\nimport stock\n\ndef plain(s):\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 pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef names_text(ns):\n    \"\" => out\n    for i in [0:len(ns) - 1]:\n        if i > 0 and i == len(ns) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + ns[i] => out\n    return out\n\ndef signed(n):\n    if n > 0:\n        return \"+\" + str(n)\n    return str(n)\n\ndef item_text(count, code):\n    return code + \" \" + count[code][0] + \" \" + str(count[code][1])\n\ndef show(before, now):\n    \"  code  before                now\" ^0\n    for code in all_codes(before, now):\n        \"-\" => a\n        \"-\" => b\n        if code in before:\n            before[code][0] + \" \" + str(before[code][1]) => a\n        if code in now:\n            now[code][0] + \" \" + str(now[code][1]) => b\n        (\"  \" + code + \"  \" + pad(a, 22) + b) ^0\n    (\"  \" + str(len(before)) + \" items before, \" + str(len(now)) + \" now.\") ^0\n\ndef all_codes(before, now):\n    {} => both\n    for c in before:\n        1 => both[c]\n    for c in now:\n        1 => both[c]\n    return stock.sorted_codes(both)\n\ndef differences(before, now):\n    stock.diff(before, now) => d\n    \"Differences, before to now:\" ^0\n    if len(d[\"added\"]) == 0:\n        \"  added:   none\" ^0\n    for code in d[\"added\"]:\n        (\"  added:   \" + item_text(now, code)) ^0\n    if len(d[\"gone\"]) == 0:\n        \"  gone:    none\" ^0\n    for code in d[\"gone\"]:\n        (\"  gone:    \" + item_text(before, code)) ^0\n    for code in d[\"changed\"]:\n        now[code][1] - before[code][1] => delta\n        (\"  changed: \" + code + \" \" + now[code][0] + \" \" + str(before[code][1]) + \" -> \" + str(now[code][1]) + \" (\" + signed(delta) + \")\") ^0\n        if now[code][1] == 0:\n            (\"           \" + code + \" is still on the list, at 0: counted and empty, not gone.\") ^0\n    for code in d[\"renamed\"]:\n        (\"  renamed: \" + code + \" \" + before[code][0] + \" -> \" + now[code][0]) ^0\n    (\"  the same: \" + str(len(d[\"same\"])) + \" items\") ^0\n    for p in stock.moved(before, now, d):\n        (\"Very likely one item under a new code: \" + p[0] + \" \" + before[p[0]][0] + \" is gone and \" + p[1] + \" \" + now[p[1]][0] + \" added, with the same name and quantity (\" + str(now[p[1]][1]) + \").\") ^0\n    stock.balance(before, now, d) => b\n    (\"Units: \" + str(b[0]) + \" before, \" + str(b[1]) + \" now: \" + signed(b[2]) + \" added, \" + signed(0 - b[3]) + \" gone, \" + signed(b[4]) + \" on the items in both.\") ^0\n\ndef one_way(before, now):\n    stock.diff(before, now) => d\n    [] => found\n    for code in d[\"added\"]:\n        found + [code] => found\n    for code in d[\"changed\"]:\n        found + [code] => found\n    (\"Checking each item now against before finds \" + list_or_none(found) + \" - and never what is gone: \" + list_or_none(d[\"gone\"]) + \".\") ^0\n    [] => found2\n    for code in d[\"gone\"]:\n        found2 + [code] => found2\n    for code in d[\"changed\"]:\n        found2 + [code] => found2\n    (\"Checking each item before against now finds \" + list_or_none(found2) + \" - and never what is new: \" + list_or_none(d[\"added\"]) + \".\") ^0\n\ndef list_or_none(xs):\n    if len(xs) == 0:\n        return \"nothing\"\n    return names_text(xs)\n\ndef good_code(c):\n    if len(c) != 4 or c[0] < \"A\" or c[0] > \"Z\":\n        return False\n    for k in [1:3]:\n        if c[k] < \"0\" or c[k] > \"9\":\n            return False\n    return True\n\ndef ask_qty():\n    # the quantity, -1 to cancel or refuse, -2 to remove\n    plain(input(\"quantity (0 to 99999, or remove)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return 0 - 1\n    if t == \"remove\":\n        return 0 - 2\n    if len(t) == 0 or len(t) > 5:\n        \"Type a whole number from 0 to 99999, or remove.\" ^0\n        return 0 - 1\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            \"Type a whole number from 0 to 99999, or remove.\" ^0\n            return 0 - 1\n    return int(t)\n\ndef change(now):\n    plain(input(\"code (a capital letter and 3 digits, such as D400)> \")) => code\n    if code == \"\":\n        \"Cancelled.\" ^0\n        return now\n    if not good_code(code):\n        \"Type a capital letter and 3 digits, such as D400.\" ^0\n        return now\n    if code in now:\n        ask_qty() => q\n        if q == 0 - 1:\n            return now\n        if q == 0 - 2:\n            (code + \" \" + now[code][0] + \" taken off the new count.\") ^0\n            return stock.remove_item(now, code)\n        (code + \" \" + now[code][0] + \": \" + str(now[code][1]) + \" -> \" + str(q) + \".\") ^0\n        return stock.set_item(now, code, now[code][0], q)\n    plain(input(\"name (up to 20 lowercase letters and spaces)> \")) => name\n    if name == \"\":\n        \"Cancelled.\" ^0\n        return now\n    if len(name) > 20:\n        \"Type up to 20 lowercase letters and spaces.\" ^0\n        return now\n    for c in name:\n        if not ((c >= \"a\" and c <= \"z\") or c == \" \"):\n            \"Type up to 20 lowercase letters and spaces.\" ^0\n            return now\n    ask_qty() => q\n    if q == 0 - 1:\n        return now\n    if q == 0 - 2:\n        (code + \" is not on the new count.\") ^0\n        return now\n    (code + \" \" + name + \" added to the new count, \" + str(q) + \".\") ^0\n    return stock.set_item(now, code, name, q)\n\nstock.before_count() => before\nstock.now_count() => now\n\n\"== Inventory diff ==\" ^0\n\"Two stock counts, last month's and this month's, compared in both directions.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(before, now)\n    elif choice == \"2\":\n        differences(before, now)\n    elif choice == \"3\":\n        one_way(before, now)\n    elif choice == \"4\":\n        change(now) => now\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import stock\n\ndef plain(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 pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef names_text(ns):\n    out = \"\"\n    for i in range(0, len(ns)):\n        if i > 0 and i == len(ns) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + ns[i]\n    return out\n\ndef signed(n):\n    if n > 0:\n        return \"+\" + str(n)\n    return str(n)\n\ndef item_text(count, code):\n    return code + \" \" + count[code][0] + \" \" + str(count[code][1])\n\ndef show(before, now):\n    print(\"  code  before                now\")\n    for code in all_codes(before, now):\n        a = \"-\"\n        b = \"-\"\n        if code in before:\n            a = before[code][0] + \" \" + str(before[code][1])\n        if code in now:\n            b = now[code][0] + \" \" + str(now[code][1])\n        print(\"  \" + code + \"  \" + pad(a, 22) + b)\n    print(\"  \" + str(len(before)) + \" items before, \" + str(len(now)) + \" now.\")\n\ndef all_codes(before, now):\n    both = {}\n    for c in before:\n        both[c] = 1\n    for c in now:\n        both[c] = 1\n    return stock.sorted_codes(both)\n\ndef differences(before, now):\n    d = stock.diff(before, now)\n    print(\"Differences, before to now:\")\n    if len(d[\"added\"]) == 0:\n        print(\"  added:   none\")\n    for code in d[\"added\"]:\n        print(\"  added:   \" + item_text(now, code))\n    if len(d[\"gone\"]) == 0:\n        print(\"  gone:    none\")\n    for code in d[\"gone\"]:\n        print(\"  gone:    \" + item_text(before, code))\n    for code in d[\"changed\"]:\n        delta = now[code][1] - before[code][1]\n        print(\"  changed: \" + code + \" \" + now[code][0] + \" \" + str(before[code][1]) + \" -> \" + str(now[code][1]) + \" (\" + signed(delta) + \")\")\n        if now[code][1] == 0:\n            print(\"           \" + code + \" is still on the list, at 0: counted and empty, not gone.\")\n    for code in d[\"renamed\"]:\n        print(\"  renamed: \" + code + \" \" + before[code][0] + \" -> \" + now[code][0])\n    print(\"  the same: \" + str(len(d[\"same\"])) + \" items\")\n    for p in stock.moved(before, now, d):\n        print(\"Very likely one item under a new code: \" + p[0] + \" \" + before[p[0]][0] + \" is gone and \" + p[1] + \" \" + now[p[1]][0] + \" added, with the same name and quantity (\" + str(now[p[1]][1]) + \").\")\n    b = stock.balance(before, now, d)\n    print(\"Units: \" + str(b[0]) + \" before, \" + str(b[1]) + \" now: \" + signed(b[2]) + \" added, \" + signed(0 - b[3]) + \" gone, \" + signed(b[4]) + \" on the items in both.\")\n\ndef one_way(before, now):\n    d = stock.diff(before, now)\n    found = []\n    for code in d[\"added\"]:\n        found = found + [code]\n    for code in d[\"changed\"]:\n        found = found + [code]\n    print(\"Checking each item now against before finds \" + list_or_none(found) + \" - and never what is gone: \" + list_or_none(d[\"gone\"]) + \".\")\n    found2 = []\n    for code in d[\"gone\"]:\n        found2 = found2 + [code]\n    for code in d[\"changed\"]:\n        found2 = found2 + [code]\n    print(\"Checking each item before against now finds \" + list_or_none(found2) + \" - and never what is new: \" + list_or_none(d[\"added\"]) + \".\")\n\ndef list_or_none(xs):\n    if len(xs) == 0:\n        return \"nothing\"\n    return names_text(xs)\n\ndef good_code(c):\n    if len(c) != 4 or c[0] < \"A\" or c[0] > \"Z\":\n        return False\n    for k in range(1, 4):\n        if c[k] < \"0\" or c[k] > \"9\":\n            return False\n    return True\n\ndef ask_qty():\n    t = plain(input(\"quantity (0 to 99999, or remove)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return 0 - 1\n    if t == \"remove\":\n        return 0 - 2\n    if len(t) == 0 or len(t) > 5:\n        print(\"Type a whole number from 0 to 99999, or remove.\")\n        return 0 - 1\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            print(\"Type a whole number from 0 to 99999, or remove.\")\n            return 0 - 1\n    return int(t)\n\ndef change(now):\n    code = plain(input(\"code (a capital letter and 3 digits, such as D400)> \"))\n    if code == \"\":\n        print(\"Cancelled.\")\n        return now\n    if not good_code(code):\n        print(\"Type a capital letter and 3 digits, such as D400.\")\n        return now\n    if code in now:\n        q = ask_qty()\n        if q == 0 - 1:\n            return now\n        if q == 0 - 2:\n            print(code + \" \" + now[code][0] + \" taken off the new count.\")\n            return stock.remove_item(now, code)\n        print(code + \" \" + now[code][0] + \": \" + str(now[code][1]) + \" -> \" + str(q) + \".\")\n        return stock.set_item(now, code, now[code][0], q)\n    name = plain(input(\"name (up to 20 lowercase letters and spaces)> \"))\n    if name == \"\":\n        print(\"Cancelled.\")\n        return now\n    if len(name) > 20:\n        print(\"Type up to 20 lowercase letters and spaces.\")\n        return now\n    for c in name:\n        if not (c >= \"a\" and c <= \"z\" or c == \" \"):\n            print(\"Type up to 20 lowercase letters and spaces.\")\n            return now\n    q = ask_qty()\n    if q == 0 - 1:\n        return now\n    if q == 0 - 2:\n        print(code + \" is not on the new count.\")\n        return now\n    print(code + \" \" + name + \" added to the new count, \" + str(q) + \".\")\n    return stock.set_item(now, code, name, q)\n\nbefore = stock.before_count()\nnow = stock.now_count()\nprint(\"== Inventory diff ==\")\nprint(\"Two stock counts, last month's and this month's, compared in both directions.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(before, now)\n    elif choice == \"2\":\n        differences(before, now)\n    elif choice == \"3\":\n        one_way(before, now)\n    elif choice == \"4\":\n        now = change(now)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "stock.eml",
      "eml": "# P123 inventory diff - two stock counts, each item a code, a name and a\n# quantity. As in the corpus case inventory-diff-report, the difference\n# has two directions: what is new now and what is gone since, and a report\n# that asks only one of them misses the other. As in dict-inventory-report,\n# the counts are dicts keyed by code, reported in sorted order. A quantity\n# of 0 is still on the list; an item off the list is gone.\n#\n# A count is {code: [name, quantity]}.\n\ndef before_count():\n    return {\"A100\": [\"bolts\", 120], \"A101\": [\"nuts\", 300], \"A102\": [\"washers\", 75], \"B200\": [\"hinges\", 40], \"B201\": [\"brackets\", 12], \"C300\": [\"glue\", 9], \"C301\": [\"sealant\", 5]}\n\ndef now_count():\n    return {\"A100\": [\"bolts\", 120], \"A101\": [\"nuts\", 260], \"A102\": [\"washers\", 75], \"B200\": [\"door hinges\", 40], \"B205\": [\"brackets\", 12], \"C300\": [\"glue\", 0], \"D400\": [\"tape\", 30]}\n\ndef sorted_codes(count):\n    # the codes in order, by insertion into a growing sorted list\n    [] => out\n    for code in count:\n        [] => nxt\n        False => placed\n        for c in out:\n            if not placed and code < c:\n                nxt + [code] => nxt\n                True => placed\n            nxt + [c] => nxt\n        if not placed:\n            nxt + [code] => nxt\n        nxt => out\n    return out\n\ndef only_in(a, b):\n    # the codes in a and not in b, in order\n    [] => out\n    for code in sorted_codes(a):\n        if not (code in b):\n            out + [code] => out\n    return out\n\ndef diff(before, now):\n    # {\"added\", \"gone\", \"changed\", \"renamed\", \"same\"}: lists of codes\n    [] => changed\n    [] => renamed\n    [] => same\n    for code in sorted_codes(now):\n        if code in before:\n            if before[code][1] != now[code][1]:\n                changed + [code] => changed\n            if before[code][0] != now[code][0]:\n                renamed + [code] => renamed\n            if before[code][1] == now[code][1] and before[code][0] == now[code][0]:\n                same + [code] => same\n    return {\"added\": only_in(now, before), \"gone\": only_in(before, now), \"changed\": changed, \"renamed\": renamed, \"same\": same}\n\ndef moved(before, now, d):\n    # pairs [gone code, added code] with the same name and quantity: very\n    # likely one item counted under a new code\n    [] => out\n    for g in d[\"gone\"]:\n        for a in d[\"added\"]:\n            if before[g][0] == now[a][0] and before[g][1] == now[a][1]:\n                out + [[g, a]] => out\n    return out\n\ndef units(count):\n    0 => n\n    for code in count:\n        n + count[code][1] => n\n    return n\n\ndef balance(before, now, d):\n    # [units before, now, added, gone, change in the items on both]\n    0 => added\n    for code in d[\"added\"]:\n        added + now[code][1] => added\n    0 => gone\n    for code in d[\"gone\"]:\n        gone + before[code][1] => gone\n    0 => change\n    for code in d[\"changed\"]:\n        change + now[code][1] - before[code][1] => change\n    return [units(before), units(now), added, gone, change]\n\ndef copy_count(count):\n    {} => out\n    for code in count:\n        [count[code][0], count[code][1]] => out[code]\n    return out\n\ndef set_item(count, code, name, qty):\n    copy_count(count) => out\n    [name, qty] => out[code]\n    return out\n\ndef remove_item(count, code):\n    {} => out\n    for c in count:\n        if c != code:\n            [count[c][0], count[c][1]] => out[c]\n    return out\n",
      "python": "def before_count():\n    return {\"A100\": [\"bolts\", 120], \"A101\": [\"nuts\", 300], \"A102\": [\"washers\", 75], \"B200\": [\"hinges\", 40], \"B201\": [\"brackets\", 12], \"C300\": [\"glue\", 9], \"C301\": [\"sealant\", 5]}\n\ndef now_count():\n    return {\"A100\": [\"bolts\", 120], \"A101\": [\"nuts\", 260], \"A102\": [\"washers\", 75], \"B200\": [\"door hinges\", 40], \"B205\": [\"brackets\", 12], \"C300\": [\"glue\", 0], \"D400\": [\"tape\", 30]}\n\ndef sorted_codes(count):\n    out = []\n    for code in count:\n        nxt = []\n        placed = False\n        for c in out:\n            if not placed and code < c:\n                nxt = nxt + [code]\n                placed = True\n            nxt = nxt + [c]\n        if not placed:\n            nxt = nxt + [code]\n        out = nxt\n    return out\n\ndef only_in(a, b):\n    out = []\n    for code in sorted_codes(a):\n        if not code in b:\n            out = out + [code]\n    return out\n\ndef diff(before, now):\n    changed = []\n    renamed = []\n    same = []\n    for code in sorted_codes(now):\n        if code in before:\n            if before[code][1] != now[code][1]:\n                changed = changed + [code]\n            if before[code][0] != now[code][0]:\n                renamed = renamed + [code]\n            if before[code][1] == now[code][1] and before[code][0] == now[code][0]:\n                same = same + [code]\n    return {\"added\": only_in(now, before), \"gone\": only_in(before, now), \"changed\": changed, \"renamed\": renamed, \"same\": same}\n\ndef moved(before, now, d):\n    out = []\n    for g in d[\"gone\"]:\n        for a in d[\"added\"]:\n            if before[g][0] == now[a][0] and before[g][1] == now[a][1]:\n                out = out + [[g, a]]\n    return out\n\ndef units(count):\n    n = 0\n    for code in count:\n        n = n + count[code][1]\n    return n\n\ndef balance(before, now, d):\n    added = 0\n    for code in d[\"added\"]:\n        added = added + now[code][1]\n    gone = 0\n    for code in d[\"gone\"]:\n        gone = gone + before[code][1]\n    change = 0\n    for code in d[\"changed\"]:\n        change = change + now[code][1] - before[code][1]\n    return [units(before), units(now), added, gone, change]\n\ndef copy_count(count):\n    out = {}\n    for code in count:\n        out[code] = [count[code][0], count[code][1]]\n    return out\n\ndef set_item(count, code, name, qty):\n    out = copy_count(count)\n    out[code] = [name, qty]\n    return out\n\ndef remove_item(count, code):\n    out = {}\n    for c in count:\n        if c != code:\n            out[c] = [count[c][0], count[c][1]]\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\n\n4\nd400\n4\nD40\n4\nDD400\n4\nD400\n\n4\nD400\nx\n4\nD400\n100000\n4\nD400\n-3\n4\nZ999\n\n4\nZ999\nTape\n4\nZ999\na very long name indeed yes\n4\nZ999\ntape\n\n4\nZ999\ntape\nremove\n4\nZ999\ntape 2\n4\nZ999\nmasking tape\n7\n2\n5\n",
      "screen": "== Inventory diff ==\nTwo stock counts, last month's and this month's, compared in both directions.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> \nCancelled.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> d400\nType a capital letter and 3 digits, such as D400.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> D40\nType a capital letter and 3 digits, such as D400.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> DD400\nType a capital letter and 3 digits, such as D400.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> D400\nquantity (0 to 99999, or remove)> \nCancelled.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> D400\nquantity (0 to 99999, or remove)> x\nType a whole number from 0 to 99999, or remove.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> D400\nquantity (0 to 99999, or remove)> 100000\nType a whole number from 0 to 99999, or remove.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> D400\nquantity (0 to 99999, or remove)> -3\nType a whole number from 0 to 99999, or remove.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> \nCancelled.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> Tape\nType up to 20 lowercase letters and spaces.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> a very long name indeed yes\nType up to 20 lowercase letters and spaces.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> tape\nquantity (0 to 99999, or remove)> \nCancelled.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> tape\nquantity (0 to 99999, or remove)> remove\nZ999 is not on the new count.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> tape 2\nType up to 20 lowercase letters and spaces.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> Z999\nname (up to 20 lowercase letters and spaces)> masking tape\nquantity (0 to 99999, or remove)> 7\nZ999 masking tape added to the new count, 7.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 2\nDifferences, before to now:\n  added:   B205 brackets 12\n  added:   D400 tape 30\n  added:   Z999 masking tape 7\n  gone:    B201 brackets 12\n  gone:    C301 sealant 5\n  changed: A101 nuts 300 -> 260 (-40)\n  changed: C300 glue 9 -> 0 (-9)\n           C300 is still on the list, at 0: counted and empty, not gone.\n  renamed: B200 hinges -> door hinges\n  the same: 2 items\nVery likely one item under a new code: B201 brackets is gone and B205 brackets added, with the same name and quantity (12).\nUnits: 561 before, 544 now: +49 added, -17 gone, -49 on the items in both.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\nB205\nremove\n4\nB201\nbrackets\n12\n2\n4\nC300\n4\n2\n3\n5\n",
      "screen": "== Inventory diff ==\nTwo stock counts, last month's and this month's, compared in both directions.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 1\n  code  before                now\n  A100  bolts 120             bolts 120\n  A101  nuts 300              nuts 260\n  A102  washers 75            washers 75\n  B200  hinges 40             door hinges 40\n  B201  brackets 12           -\n  B205  -                     brackets 12\n  C300  glue 9                glue 0\n  C301  sealant 5             -\n  D400  -                     tape 30\n  7 items before, 7 now.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 2\nDifferences, before to now:\n  added:   B205 brackets 12\n  added:   D400 tape 30\n  gone:    B201 brackets 12\n  gone:    C301 sealant 5\n  changed: A101 nuts 300 -> 260 (-40)\n  changed: C300 glue 9 -> 0 (-9)\n           C300 is still on the list, at 0: counted and empty, not gone.\n  renamed: B200 hinges -> door hinges\n  the same: 2 items\nVery likely one item under a new code: B201 brackets is gone and B205 brackets added, with the same name and quantity (12).\nUnits: 561 before, 537 now: +42 added, -17 gone, -49 on the items in both.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 3\nChecking each item now against before finds B205, D400, A101 and C300 - and never what is gone: B201 and C301.\nChecking each item before against now finds B201, C301, A101 and C300 - and never what is new: B205 and D400.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> B205\nquantity (0 to 99999, or remove)> remove\nB205 brackets taken off the new count.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> B201\nname (up to 20 lowercase letters and spaces)> brackets\nquantity (0 to 99999, or remove)> 12\nB201 brackets added to the new count, 12.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 2\nDifferences, before to now:\n  added:   D400 tape 30\n  gone:    C301 sealant 5\n  changed: A101 nuts 300 -> 260 (-40)\n  changed: C300 glue 9 -> 0 (-9)\n           C300 is still on the list, at 0: counted and empty, not gone.\n  renamed: B200 hinges -> door hinges\n  the same: 3 items\nUnits: 561 before, 537 now: +30 added, -5 gone, -49 on the items in both.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 4\ncode (a capital letter and 3 digits, such as D400)> C300\nquantity (0 to 99999, or remove)> 4\nC300 glue: 0 -> 4.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 2\nDifferences, before to now:\n  added:   D400 tape 30\n  gone:    C301 sealant 5\n  changed: A101 nuts 300 -> 260 (-40)\n  changed: C300 glue 9 -> 4 (-5)\n  renamed: B200 hinges -> door hinges\n  the same: 3 items\nUnits: 561 before, 541 now: +30 added, -5 gone, -45 on the items in both.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 3\nChecking each item now against before finds D400, A101 and C300 - and never what is gone: C301.\nChecking each item before against now finds C301, A101 and C300 - and never what is new: D400.\n\n1) both counts  2) differences  3) one-way checks  4) change the new count  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "inventory-diff-report",
      "caseId": "174-inventory-diff-report",
      "title": "Difference is directional"
    },
    {
      "slug": "dict-inventory-report",
      "caseId": "142-dict-inventory-report",
      "title": "Dictionaries as the primary structure"
    }
  ],
  "updated": "2026-10-17"
}
