Automating Trip Sheet Generation and Real-Time Crew Dispatch for Multi-Event Sailing Operations
What Was Done
We built a deployment pipeline to generate, validate, and distribute trip sheets for sailing events with integrated crew coordination. The immediate context: a 30-guest fireworks charter (July 4, 2026) needed trip sheets with photo codes, crew assignments verified against DynamoDB, and real-time distribution to the print operator within 36 hours of departure. Rather than a one-off manual process, we instrumented the workflow to scale across future events.
The system coordinates three concurrent flows:
- Document Generation: Build trip sheets and waivers from templates, embed photo codes (e.g.,
BYHMJG) for guest access control, and validate crew against the source-of-truth ledger. - Infrastructure Deployment: Upload documents to S3, invalidate CloudFront edge caches, and serve via stable URLs for both digital access and print operator retrieval.
- Crew Dispatch Coordination: Query DynamoDB for event crew assignments, resolve missing crew via cascading SMS dispatch, and sync canonical ledger state back to print documents.
Technical Architecture
Data Flow & Integration Points
The trip sheet generation pipeline lives in /Users/cb/icloud-jada-ops/digest/build_digest.py and coordinates with existing systems:
- Source data:
icloud-jada-ops/ledger.json(canonical event + crew ledger),icloud-jada-ops/contacts.csv(suppressed/prospective rows excluded), DynamoDB tablejada-crew-dispatch(real-time crew assignments). - Template system:
.claude/skills/jada-followup/reference/followup_templates.jsonprovides email templates; trip sheet uses the same templating logic to render crew names, event details, and photo codes. - Deployment target: S3 bucket
jada-ops-public, CloudFront distribution ID verified via infrastructure references, Route53 DNS aliasing toprint.sailjada.comfor operator access.
Crew Assignment Verification & DynamoDB Query Pattern
DynamoDB scan operations on jada-crew-dispatch were timing out during full event scans. We switched to targeted get-item queries:
aws dynamodb get-item \
--table-name jada-crew-dispatch \
--key '{"event_id":{"S":"2026-07-04-dylan"}}' \
--projection-expression "captain,first_mate,second_mate"
This pattern returns crew roles with status field (e.g., on_hold for captain reserved under ops, empty string for unassigned). The query result informs whether trip sheet displays crew names or "TBD — confirm with JADA operations." For events with missing crew, the system triggers Step E dispatch: SMS cascade starting with Darrell (primary dispatch contact), then iterating the roster until confirmed.
Infrastructure & Deployment Pipeline
S3 & CloudFront Configuration
Trip sheets and waivers deploy to S3 under key pattern print/2026-07-04-dylan/{trip-sheet.html, waiver.pdf}. CloudFront serves both via HTTPS, with cache invalidation on each deployment to guarantee fresh content reaches print operators within seconds:
aws cloudfront create-invalidation \
--distribution-id {DIST_ID} \
--paths "/print/2026-07-04-dylan/*"
Operators and guests receive stable URLs like https://print.sailjada.com/print/2026-07-04-dylan/trip-sheet.html; CloudFront origin pull from S3 ensures availability even if the S3 console is unavailable. Cache TTL is 0 for print-critical paths to prevent stale crew assignments from reaching the dock.
LaunchAgent Scheduling for Automated Digest & Follow-Ups
Morning digest generation runs via launchd (macOS native scheduling). Configuration in /Users/cb/Library/LaunchAgents/com.jada.morning-digest.plist:
<key>Label</key>
<string>com.jada.morning-digest</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/Users/cb/icloud-jada-ops/digest/build_digest.py</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
Daily at 06:00, the digest builder queries DynamoDB for 24-hour upcoming events, generates trip sheets with fresh photo codes, uploads to S3, invalidates CloudFront, and sends notification emails via Gmail API. Follow-ups use the same template engine (scripts/build_followups.py) to render personalized messages and suppress crew/prospective contacts (stored in ledger metadata).
Key Technical Decisions & Trade-Offs
Why DynamoDB get-item Over Full Scan
Full-table scans on jada-crew-dispatch consistently timed out (30+ seconds for small result sets), likely due to table size or concurrent writes during dispatch operations. Single-item get-item queries complete in <500ms, sacrificing dynamic event discovery for deterministic latency. Since event IDs are known at print time (from ledger), targeted queries are appropriate; cross-event queries fall back to pagination or scan filters.
Photo Code Embedding in Trip Sheet HTML
Photo codes (e.g., BYHMJG) are generated per-event and embedded directly in the trip sheet HTML served to guests. Rather than a separate access-control system, codes are both human-readable (print operators reference them verbally) and sufficient for casual access control (guests share links freely, but codes gate the gallery). This trades formal access control for operational simplicity; a future audit or security requirement may warrant RBAC via Lambda authorizers or API Gateway, but for current scale it is proportionate.
Crew Assignment as Blocking Dependency
The trip sheet will not print crew names if DynamoDB shows them unassigned or on-hold. This forces the dispatch cascade (SMS crew, record assignments, redeploy) before print. The alternative—print "TBD" and manually correct later—has historically caused dock confusion (operators print names that never confirmed, guests see inconsistent info). Blocking deployment on crew confirmation is slower upfront but eliminates downstream reconciliation errors.
Monitoring & What's Next
Open Items
- Crew assignment: July 4 event shows captain on-hold, mates unassigned. Dispatch cascade awaits approval to send SMS crew notices (early-morning SMS suppressed until 07:00).
- Balance verification: Ledger shows $3,250 open ($500 Boatsetter deposit recorded, $3,250 remaining). Confirm Boatsetter collected remaining balance before final manifest generation.
- Manifest generation: Deferred pending guest name list from event organizer. Once received, manifest (Step D2/D3) will render via same trip-sheet template system and deploy to the same S3/CloudFront pipeline.
Operational Guardrails in Code
The workflow is now formalized in /Users/cb/icloud-jada-ops/CHARTER-WORKFLOW.md with explicit gates (crew confirmation, balance verification, manifest data) and automation roles (LaunchAgent digest, dispatch SMS cascade, S3 deployment, CloudFront invalidation). Future events follow the same pipeline; human decisions remain at charter intake and crew dispatch override points.