Date-Parser Consolidation

Shipped: 2026-08-05 (entirely-api 875e5a5, deployed). Second deliverable of the ML/workflow-intelligence plan — the mixed date formats were a blocking prerequisite for process mining on the new status-event-log.

The canonical family (config.py)

  • extract_date_str(raw) — YYYY-MM-DD from any of the 3 raw_data formats (dict blob / plain ISO / stringified JSON). Pre-existing; already widespread.
  • extract_datetime(raw) — tz-aware UTC for timed values, naive midnight for date-only. The blob’s include_time flag decides; unflagged exact-midnight-Z counts as date-only (that’s the storage convention for plain dates — treating it as timed would shift every plain date a day back in ET).
  • extract_date_et(raw) — correct Eastern calendar day for timed values.
  • ss_date_field(value, include_time=False) — write-side builder; routes no longer hand-assemble {"date": ..., "include_time": ...}.

Rule going forward: never hand-parse or hand-build raw_data dates.

The sweep

Three parallel agents, ~50 sites across 23 files, behavior-preserving. Deliberately left: smartsuite_helpers.extract_date (returns full timestamps its consumers rely on), payment-recon’s _clean_date* (handles slash formats the helper doesn’t), full-timestamp API-payload echoes, sites with stricter malformed-JSON handling than the helper. Notable enabled-by-fix behavior: same-day approval alert can now fire for dict-format dates (was dead); field arrival/completion matching now handles ‘on’-key and stringified blobs; calendar revenue no longer shifts date-only values to the prior ET day.

Data normalization (prod, same day)

projects.date_invoice_sent held 3 formats; one transactional SQL update (pre-checked: 0 malformed) → 2,459 plain dates, 4,694 honest NULLs, zero blobs. raw_data originals untouched. Field map now applies extract_date_str on write. triage_assigned_date/hold_since NOT truncated — real timestamps, and the column is their only storage.

Follow-up

~16 flagged ET day-boundary sites (UTC day vs ET “today”, off-by-one after 8 PM ET) — enumerated in memory project_et_day_boundary_fixes, fix pass with extract_date_et pending. Highest operational impact: field arrival/completion matching, visit-completion prompt, SLA deadline start, intake next-visit annotation.