Commit Graph

2 Commits

Author SHA1 Message Date
rob 882b0ec959 Phase 2: finish costs by kind in the estimate
- EstimateRates.finish_cost_per_sqft and min_per_finish are now dicts keyed by
  finish kind (sanded/clear/stain/paint) — paint costs more and takes longer
  than a clear coat; all editable.
- project_estimate prices the finish line per part by its finish kind × surface
  area; finishing labor sums per-part by kind; raw parts cost nothing.
- load_rates generically merges any dict-valued rate field; RatesEditDialog
  rewritten to render scalars + dict sub-sections automatically.
- tests: finish cost varies by kind, raw = no finish line, dict roundtrip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:04:44 -03:00
rob 30bfb3a9e0 Add project estimate: consumables, labor, and suggested selling price
The Cost tab now produces a full quote, not just material cost.

- estimate.py: project_estimate() = materials (incl HST) + consumables
  (screws per butt joint, glue per M&T connection / dado / rabbet, finish
  $/sq ft of finished surface) + labor (editable minutes per operation —
  setup, cut, butt joint, assembly, sanding, and per-feature tenon/mortise/
  hole/slot/dado/rabbet/chamfer — × counts from the scene/plan, × shop rate).
- Selling price = MARGIN on total cost: price = total_cost / (1 - margin),
  labor counted as cost. A target price overrides margin and back-solves the
  implied margin. EstimateRates persisted to estimate.json.
- Cost tab: live margin % spinbox + target $ field, "Edit rates…"
  (RatesEditDialog), existing "Edit prices…" / "Refresh from Kent…", Print.
- All counts are deterministic (count_ops off scene.joints / connections /
  features / finishes); nothing guessed.
- tests: op counts, screws/glue, labor scaling, margin formula, target
  back-solve, div-zero guard, rates roundtrip, format, and GUI cost-tab +
  margin/target controls. 163 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:40:15 -03:00