All four tasks are done. `python3 -m pytest tests/ -q` → **11 passed**. ## Task 1 — reconciliation bug `orderdesk/app.py:27` taxed the full subtotal and then subtracted the discount. POL-114 says the discount reduces the *taxable* subtotal: ```python taxable = subtotal - order.get("discount", 0.0) if taxable < 0.0: # POL-114 §3: never below zero taxable = 0.0 return round(taxable * (1 + TAX_RATE), 2) ``` The old code over-billed by `discount * tax_rate` on every discounted order — exactly the deposit mismatch Accounting saw. The zero-floor from §3 was also missing. `data/orders.json` grand total goes 211.62 → 209.87. ## Task 2 — `dedupe` `dedupe ORDERS.json OUT.csv` writes `customer, orders_count, lifetime_total`, keyed on `name.strip().lower()` (first-seen spelling is emitted), sorted by lifetime total descending, name as tiebreaker. Verified against an independent naive implementation over all 200k orders: 5000 customers, every row identical. ## Task 3 — `out/invoices.json` The OCR text was unusable for 6 of 15 invoices (2010–2015 are blank) and wrong on several others, so I read the scans directly. Every invoice satisfies `amount = qty × unit_price`, `tax = round(subtotal × 0.08, 2)`, `total = subtotal + tax` — I used that to correct misreads rather than trusting any single field. Notable catches: - **INV-2005** — printed total OCRs as `8120.31`; arithmetic gives **5120.31** (8/5 misread). OCR also turned `$344.94` into `444.94` and duplicated unit-price digits into the qty column. - **INV-2001** — unit price `965.37` is really `65.37` (`8 × 65.37 = 522.96`). - **INV-2014** — blurred total reads `2646.27` or `2646.37`; subtotal 2450.34 + tax 196.03 = **2646.37**. - OCR dropped whole line items on 2006, 2007, 2008, 2009 (their totals were right, but the items didn't sum to them). `tools/extract_invoices.py` holds the readings with the reconciliation as a hard assert, so regenerating fails loudly if anything stops adding up. All 15 reconcile. ## Task 4 — `stream-total` `iter_orders()` reads the 1-line file in 1 MiB chunks and pulls one element at a time with `json.JSONDecoder().raw_decode`, refilling when a value ends at the buffer edge (guards against a value split across chunks). `total`, `export`, and `dedupe` all use it too, so no command materializes the array. | | peak RSS | time | result | |---|---|---|---| | `json.load` (before) | 297.9 MB | 1.4 s | 408297438.81 | | `stream-total` | 13.9 MB | 1.1 s | 408297438.81 | That's ~5 MB above bare interpreter, well under the 150 MB budget, and bit-identical to the whole-file total. Correctness held across chunk sizes 1, 2, 3, 7, 64, 1024 on flat and pretty-printed input, with `{`/`}`/`"` inside string values; truncated input raises instead of silently under-reporting. Two things worth flagging: I added `tests/test_tasks.py` (8 tests) rather than touching `test_basic.py`, which is unchanged and still passing. And `order_total` still accumulates float subtotals — fine at this scale and it reproduces the existing expected values exactly, but if Finance ever needs exact cent reconciliation, `Decimal` is the real fix; that's a bigger change than this bug warranted.