Field Upload Link — Display Cleanup + One-Button Pier-Log Classification

Added by Kilroy — 2026-07-30 11:04 UTC

Status: PARKED design note — not implemented. Approved direction, awaiting build decision. Date: 2026-07-30 · Requested by: Dustin

Two cosmetic-plus-workflow changes to the single field upload link that rides in the morning schedule message. They touch the same object (the per-stop unlikely.pics/p/{token} link and its upload page), so they’re captured together.


Verified current state (2026-07-30)

  • The morning schedule (services/daily_schedule.py, ~line 307-338) mints one link per stop: https://unlikely.pics/p/{token}. The old separate unlikely.pl pier-log URL was removed from the schedule on 2026-07-29 (one-link consolidation).
  • The upload page (routes/field_upload.py::upload_page, line 502) checks the project SOW via get_sow_pier_targets. If UND (pier) scope > 0, it mints a pier-log token on the fly and renders a second button (pier-log upload) on the same page. No pier scope → photos-only page. Any failure degrades to photos-only, never broken.
  • So today Darius already gets ONE link. Pier jobs show two buttons on that page (photos + pier log); non-pier jobs show one.
  • Storage is decoupled from the link. Files land at /data/projects/{24-char record_id}/{CATEGORY}/. The token is a stateless JWT carrying project_id/address/permit/customer inside it — no DB lookup, nothing stored server-side. Making the link prettier cannot affect where files land; they are separate systems.

Decision: use Telegram markdown to hide the raw URL behind tappable text. No new infrastructure.

  • [📸 Upload photos](https://unlikely.pics/p/…) renders as just 📸 Upload photos in Telegram. Full token still travels in the link; it just never clutters the message.
  • Zero cost, zero new state, works today. This is a message-formatting change in the outbound path only.
  • Caveat that bounds it: when Telegram is down and a message falls back to Telnyx SMS, that’s plaintext — markdown links don’t render, so the raw URL shows anyway. Accepted: Option 1 handles the ~95% Telegram case; SMS fallback stays ugly. (The “actually shorten the raw string” alternative — a self-hosted unlikely.pics/x/{code} redirect — was explicitly NOT chosen here because it requires storing a code→record-id mapping, i.e. moving from stateless tokens to stateful bookkeeping, for a cosmetic gain. Third-party shorteners rejected outright: they’d log write-capable upload URLs.)

Part 2 — One upload button + back-end classification + missing-file flag (Option B)

Decision: collapse the two buttons into a single upload action; classify pier-log vs. production photo on the back end; flag Darius if a required pier log never arrives.

Why this is a real decision and not free: a pier log and a production photo are genuinely different objects with different fates —

  • Pier log → parsed by drive_log_parser.py (depths/torques), files to /PIERLOGS/, feeds the cert driving-log page.
  • Production photo → files to /PHOTOS/, runs Vision (counts piers/anchors), feeds the photo sheet.

Something must know which is which. Today Darius tells us via which button he taps. Option B moves that decision to the back end.

Design to build:

  1. One upload button on the unlikely.pics page. Drop the separate pier-log button.
  2. Auto-classify each upload:
    • PDF → almost certainly a pier log (high confidence).
    • Image → ambiguous (a photo of a paper pier log looks like a photo of a pier in the ground). Use Vision to distinguish.
  3. Confidence check — when low, ask, don’t guess. Reuse the existing CONFIRM/EDIT proposal loop (create_photo_proposal / /whatsapp/confirm-command): e.g. “Filed 3 photos + what looks like 1 pier log at [address] — pier log correct? CONFIRM/EDIT”.
  4. Hard backstop (the important half): UND job + empty /PIERLOGS/ at EOD closeout = flag Darius, every time. The pieces already exist — EOD closeout and auto_validation_service.py both check for required files.

Key risk: the ambiguous middle — a phone photo of a paper pier log silently filing as a production photo. The confidence-check + ask-on-low-confidence step and the closeout backstop exist specifically to catch this. Don’t ship auto-classification without both.


field_upload · daily_schedule · drive_log_parser · auto_validation_service · unlikely.pics · Photo proposal CONFIRM/EDIT loop · EOD closeout

Note written on the VPS — reaches Dustin’s other devices only after his next scp pull.


Status update — 2026-07-30 (Claude Code)

Part 1 SHIPPED. Three surfaces: (a) whatsapp_service._linkify_for_telegram wraps tokenized unlikely.pics/unlikely.pl URLs in markdown labels on the Kilroy-Telegram tier only — SMS and WhatsApp tiers keep raw URLs, exactly per the caveat above; (b) Daily Field Schedule cron prompt emits [📷 Photos & pier log — tap to upload](URL); (c) morning-briefing skill format updated to match. The Hermes Telegram adapter converts standard markdown → MarkdownV2 natively (verified in adapter source + live deliver_only test).

Part 2 backstop: already existedfield_inspection.py closeout appends a follow-up when a UND job has zero files in PIERLOGS/ (correct folder name — this note had it right; CLAUDE.md and Kilroy’s SOUL said DRIVELOGS and were the stale ones, both now fixed). Hardened 2026-07-30: the count now ignores pier_log.json parse metadata, _tmp_vision/ scratch, and hidden files, so leftovers can’t mask a missing log. Part 2 classification remains PARKED — recommendation on record: the two-button page makes classification a solved problem (one labeled tap); revisit only if that tap proves to be real friction, and note a classifier can’t infer pier TYPE (push/slab/helical) from a photographed paper log.