```html

Automating Boat Cleaning Dispatch and Calendar Synchronization for Event Logistics

This session focused on solving a critical operational gap: integrating third-party boat platform calendars (GetMyBoat, Boatsetter) with our Google Calendar infrastructure, and automating the dispatch workflow for boat cleaning services between events. The challenge was coordinating multiple calendar sources without manual intervention while ensuring cleaning crews knew exactly when to arrive.

The Problem: Fragmented Calendar and Dispatch Data

Previously, boat rental platforms maintained their own calendars in silos. When events were booked through GetMyBoat or Boatsetter, they didn't automatically sync to our master Google Calendar. This meant:

  • No unified view of boat availability across all rental platforms
  • Cleaning crews had no automated notification of turnaround times between bookings
  • Risk of double-booking or missed cleaning windows
  • Manual coordination overhead for operations

Technical Architecture: Multi-Source Calendar Synchronization

We implemented a two-layer solution:

Layer 1: Google Apps Script Calendar Sync Engine

Located at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs, this script handles iCalendar feed ingestion from boat platforms.

Core functionality:

  • iCal feed polling: Retrieves calendar exports from GetMyBoat and Boatsetter via their public calendar URLs
  • Event parsing: Converts iCal format to Google Calendar Event objects with proper field mapping
  • Conflict detection: Checks for overlapping events before insertion to prevent duplicates
  • Scheduled execution: Time-based triggers run the sync at configurable intervals (we set 30-minute polling for near-real-time sync)

Key implementation detail: The script uses Google Apps Script's UrlFetchApp to retrieve remote iCal feeds, then parses them using standard RFC 5545 parsing logic. This avoids platform-specific APIs and works even if boat platforms change their API terms.

// Pseudo-structure (sanitized)
function syncCalendarFeeds() {
  const feeds = [
    { name: 'getmyboat', url: getSecretFromEnv('GETMYBOAT_ICAL_URL') },
    { name: 'boatsetter', url: getSecretFromEnv('BOATSETTER_ICAL_URL') }
  ];
  
  feeds.forEach(feed => {
    const response = UrlFetchApp.fetch(feed.url);
    const events = parseIcalFeed(response.getContentText());
    events.forEach(event => insertEventIfNotExists(event));
  });
}

Layer 2: Lambda-Based Dispatch Automation

Created /Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py as a standalone Python utility that triggers when calendar events are added via our API Gateway integration.

Dispatch workflow:

  • Monitors for boat rental events in Google Calendar (tags: boat_rental, dispatch_cleanup)
  • Calculates cleaning window: event end time + 15 minutes buffer to event start of next rental (or end-of-day if no next rental)
  • Generates dispatch task with: boat ID, location, required crew size, cleaning checklist (hull inspection, interior sanitization, fuel, supplies restock)
  • Posts dispatch to task queue (currently SES email to operations manager; later migrates to dedicated task management platform)

Why Python over Node.js: The team's existing tools stack heavily uses Python. This keeps deployment, dependency management, and local testing consistent across tooling.

Calendar API Integration via Lambda

Rather than exposing Google Calendar API directly, we proxy through our existing Lambda infrastructure at endpoint /calendar/add-event. This provides:

  • Centralized auth: Single token validation point instead of GCP service account keys scattered across systems
  • Audit trail: CloudWatch Logs capture all calendar mutations with requestor metadata
  • Rate limiting: API Gateway throttling prevents runaway automation
  • Atomic operations: Calendar event + dispatch task are wrapped in transaction logic

The Lambda function (rady-shell-calendar-api) accepts POST requests with event details:

{
  "action": "add-calendar-event",
  "calendarId": "primary",
  "event": {
    "summary": "Sea Scout Boats - Wednesday Hold",
    "start": { "dateTime": "2025-04-30T16:00:00Z" },
    "end": { "dateTime": "2025-04-30T18:00:00Z" },
    "description": "Scout group rental - dispatch cleanup crew after event"
  }
}

Deployment and Infrastructure Changes

Files deployed to production:

  • CalendarSync.gs → Google Apps Script project (ID from .clasp.json in replacement directory)
  • dispatch_boat_cleaner.py → Lambda layer; referenced by calendar API function
  • campaign_scheduler.py, deployment shells → Docker container in ECR for scheduled maintenance tasks

CloudWatch integration: All dispatch events log to /aws/lambda/rady-shell-calendar-api with structured JSON including event ID, dispatch decision, and crew assignment timestamp.

Secrets management: iCal feed URLs and boat platform credentials stored in AWS Secrets Manager (referenced as boat-platforms/credentials). CalendarSync.gs retrieves these via PropertiesService (Apps Script equivalent).

Key Decisions and Trade-offs

1. iCal feeds vs. native APIs: We chose iCal polling over direct API integration because boat platforms are third-party services with unpredictable API stability. iCal is a commodity standard; if a platform changes their API, we only update the feed URL, not our sync logic.

2. 30-minute polling interval: Real-time webhooks would be ideal, but neither GetMyBoat nor Boatsetter reliably support outbound webhooks. 30 minutes represents acceptable latency for cleaning crew dispatch (gives operations ~20 minutes to react) without excessive API calls.

3. Separate dispatch script vs. embedded in sync: We kept dispatch logic in its own module (dispatch_boat_cleaner.py) rather than embedding in CalendarSync.gs. This allows independent testing, retry logic, and future migration to event-driven architecture (SNS → SQS → dispatch) without touching the calendar sync code.

4. Email-first dispatch: Initially we dispatch via SES email to the operations manager. This is human-readable, includes full event context, and provides fallback if the task system is down. Next iteration will push to dedicated task management platform (likely Asana or Monday.com) for crew assignment and status tracking.

What's Next

Phase 2 priorities:

  • Webhook integration: Implement callback URLs in