photo-confirm-not-in-scope

Notes

  • Decision 2026-10-01 (after MAN-9238, 13104 Quail Creek Ct): a plain photo confirm of a product that is NOT on the SOW creates no SOW line. The old not_found fallback in routes/field_upload.py created a permitted-0 / as-built-NULL line (plus qty-0 add-on twins) that rendered “(0)” on cert and invoice, billed a 1x service inspection for service-division codes, spawned a billable parent line at cert validation for sub-components, and for UND/ANC/BRC blocked the cert on production days (MAN-9238 sat at Field Complete with a cert_blockers need until hand-fixed). Four other projects carried the same shape: MAN-9247 FND-BKFL (350, cert printed “CrawlSeal (0)”), MAN-9228 WTR-SUMP (spawned WTR-CRWL parent, not yet invoiced), BAL-9005 WTR-DISC (300 billed, paid). Rule now: field_photo_analyzer.apply_confirmed_product is the one SOW rule for the unlikely.pics confirm, WhatsApp CONFIRM and legacy /confirm-photo lanes. Plain confirm = classification only (existing line Inspected, as-built untouched; MAN-9075 None is never 0, MAN-9160 Vision’s per-frame count is never a total, now on all three lanes via count_source). Product not on the SOW: record_unscoped_confirmation writes a comm-log row and field_lane.event_proposal_not_in_scope asks the stop’s tech in the field thread (once per product per day); the unlikely.pics route peeks before consuming so the proposal stays alive and the page offers “Enter total installed count”; only a human total creates the scope-add line (permitted 0 / as-built N, the closeout picker’s shape); total 0 creates nothing. supabase_write_service.create_sow_line_from_product_code(from_field=True, as_built_qty=None) raises. Options (b) exempting the blocker and (c) production_days 0 as a warning were rejected because they keep the broken line and would have auto-advanced a wrong cert.