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 separateunlikely.plpier-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 viaget_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.
Part 1 — Link display cleanup (Option 1, tappable text)
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:
- One upload button on the
unlikely.picspage. Drop the separate pier-log button. - 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.
- 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”. - Hard backstop (the important half): UND job + empty
/PIERLOGS/at EOD closeout = flag Darius, every time. The pieces already exist — EOD closeout andauto_validation_service.pyboth 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.
Related
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 existed — field_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.