Planned-qty comparisons: quantity column, not raw_data

2026-07-30 (entirely-api a64031c) — sow_lines raw_data is a creation-time snapshot; a 2026-07-30 audit found 16 live rows where raw_data[SOW_QUANTITY] ≠ the quantity column (2026-04-28 qty batch + intake-era rows updated columns only). Two consumers compared against raw:

  • field_update_writer _update_sow_line_from_field — modified-flag decision on field-reported as-builts
  • supabase_write_service check_and_clear_sow_modified — auto-clear; had to move together with the writer, else stale raw would immediately undo a correctly-set flag

Both now read the denormalized quantity / as_built_qty columns with raw_data fallback only when the column is NULL. SOW_MODIFIED still comes from raw_data (no column exists). Regression tests: tests/test_field_planned_qty_column.py (anchor: blkhse.541201 SUP-JOIST, raw qty=1 vs column 12).

Rule: treat sow_lines raw_data as an immutable creation-time snapshot — never compare or display from raw_data.title / raw_data[sbcvngks] when a denormalized column exists. Related audit: all 60 raw-vs-column title mismatches were stale raw, zero mislinks (report in /home/claude/sow_title_audit_2026-07-30.md).