1. Offcut reuse was lost on optimize: Find better layout / Best of 100 / Try
alternative now pass available=self._available(); reoptimize seeds preserve
owned/source so a locked offcut stays owned (not silently bought).
2. Inventory is now species-aware end to end: purchase/consume/adjust/on_hand/
available_stock and record_build key by (stock, material); plan_consumption
and Mark-purchased group by species; PurchaseDialog shows species and prices
at the species rate; price-book save backs out the multiplier to the SPF base.
A spruce on-hand no longer satisfies an oak cut.
3. Cross-species placement is now invalid: validate_cut_plan and the GUI drop
path reject an oak cut on a spruce piece.
4. Yield is bought-only and consistent: _score divides bought-used by bought-area
(owned offcuts excluded); the Shopping tab's yield matches.
tests: locked-reopt keeps owned offcut, species-aware on-hand, cross-species
validate, yield excludes owned, optimize preserves the offcut toggle. 203 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires the ledger into the BOM window via three workflows (Codex's
workflow-first UX), plus an offcut-consuming planner.
- StockPiece gains owned/source; build_cut_plan(available=) seeds the packer
from owned offcuts (reusing the seeded-packing) so they're consumed before
buying. Score reports stock_count = pieces to BUY (offcuts free) + owned_count;
prices.estimate and the Buy list exclude owned pieces.
- BOM header: "Build units", an opt-in "Use shop offcuts" toggle, and
"Mark purchased…" / "Record build…" buttons.
- PurchaseDialog: confirm qty + price each, opt-in "save prices to price book".
- RecordBuildDialog: shows consumed stock + each offcut with Keep/Burn/Trash/
Ignore before committing (the moment to correct reality).
- Ledger.record_build takes per-offcut dispositions; used offcuts are consumed
from the bin; build cost snapshot logged.
- tests: offcut toggle drops buy-count to 0, record-build writes the ledger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>