Pier Log Folder-Wide Rescan (multi-file upload overwrite fix)

Bug (MAN-9155, folder 758bff90e3f4649b300141c3, 2026-08-06): Zoe uploaded two pier-log images ~1s apart via the portal. Each upload Vision-parsed only its own file and wrote PIERLOGS/pier_log.json wholesale — the 6-pier sheet (11:48:14) was overwritten by the 2-pier sheet (11:48:27), leaving 2 of 8 piers. The “SOW expected 8, got 2” warning fired but nothing acted on it. Data repaired same day via ensure_pier_log_parsed(force=True).

Fix (entirely-api 1a2515d): rescan_pier_log_folder() in pier_log_parser is now the single write path for Vision pier-log results:

  • Folder-wide: one Vision call over ALL parseable files in PIERLOGS/ (same as ensure_pier_log_parsed force=True), so a later sheet can never erase an earlier one. Skips pier_log.json and the _tmp_vision staging subdir.
  • Debounce/coalesce: first caller sets a Redis pierlog:rescan:pending:* key and sleeps 5s; uploads landing in that window return coalesced and are covered by the pending holder’s scan. Invariant: raw file is written BEFORE the rescan call, and pending is released BEFORE the folder listing — so a coalesced upload is always on disk when the scan runs.
  • Cross-process lock: pierlog:rescan:lock:* (TTL 15 min) serializes execution across the api AND worker containers — both write pier_log.json, an in-process asyncio.Lock was never enough. In-process fallbacks cover Redis outages. Deliberately NOT built on ARQ _job_id dedup (keep_result collision suppresses repeats ~1h — reference_arq_dedup_bug).
  • No clobber on failure: if Vision fails folder-wide and a valid pier_log.json exists, it is kept (kept_existing) instead of being overwritten by a single-file legacy placeholder.

Entry points routed through it: auto_parse_pier_log (portal files background pipeline — its outer per-folder lock removed so coalescing works; RFI uploads — auto-response now handles coalesced), worker task_parse_pierlog_vision (field media single+batch, field_upload, WhatsApp — enqueued file list is now informational only), document_parser_service._parse_and_save_pier_log (chat uploads / reparse), and ensure_pier_log_parsed (delegates with debounce 0). CSV/Excel keep the legacy single-file parser. The unlikely.pics draft flow (pierlog_upload.py) already batched all files into one Vision call and is unchanged.

Tests: tests/test_pierlog_rescan.py (9, incl. the burst repro). Suite 450 passed. Deployed 2026-08-06 via up -d --build unlikely-api worker scheduler; new code verified inside all three containers.