Automating Boat Cleaning Dispatch and Calendar Synchronization: Moving Beyond Manual Service Coordination
What Was Done
During this session, we addressed a critical operational gap: the collapse of FancyHands as a cleaning service provider forced us to rebuild our boat maintenance workflow from scratch. Rather than replacing it with another third-party service, we implemented an automated dispatch system that integrates calendar-driven scheduling with Python-based task management, paired with a Google Apps Script calendar synchronization system that pulls events from multiple boat platforms (GetMyBoat, Boatsetter) into a unified calendar view.
The Problem Statement
With FancyHands cancelled, we faced two interconnected problems:
- No cleaning coordination: No systematic way to dispatch boat cleaning between events without manual coordination
- Fragmented calendars: Boat availability data lived in multiple platforms (GetMyBoat, Boatsetter) but wasn't visible in our operational calendar
The solution required both a scheduling layer and an execution layer. We built both.
Architecture: Calendar Synchronization Layer
Primary File: /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/CalendarSync.gs
The Google Apps Script runs as a scheduled trigger on the Rady Shell calendar project. It performs the following functions:
- iCal feed polling: Fetches iCal feeds from GetMyBoat and Boatsetter at configurable intervals (currently every 15 minutes during business hours)
- Event transformation: Converts platform-specific event formats into standard Google Calendar events with standardized naming and description fields
- Duplicate prevention: Checks existing calendar events using event ID hashing to prevent duplicate entries across polling cycles
- Email notifications: Sends digest emails to operational staff when new boat availability windows are detected
Key implementation detail: Rather than using the Google Calendar API directly (which requires credential rotation and creates single points of failure), we used iCal feed URLs, which are: - Simpler to maintain (URL-based authentication) - Resilient to API changes - Cacheable at the script level to reduce API quota consumption
The script was deployed via clasp push to the GAS project identified in .clasp.json and registered as an Apps Script project resource in the dashboard configuration.
Dispatch Automation Layer
Primary Files:
/Users/cb/Documents/repos/tools/dispatch_boat_cleaner.py— Core dispatch logic/Users/cb/Documents/repos/tools/deploy_campaign_scheduler.sh— Deployment wrapper
The dispatch system operates as a scheduled Lambda function that:
- Reads the calendar: Queries the synchronized Google Calendar via Lambda's embedded Google Calendar API credentials
- Identifies gaps: Detects time windows between scheduled boat usage events where cleaning can occur
- Creates tasks: Generates cleaning dispatch tasks with time windows, boat identifiers, and priority levels based on upcoming bookings
- Logs to dashboard: Updates the dashboard task queue with new dispatch items for human review/approval
The dispatch script uses the following logic:
# Pseudocode pattern (actual implementation in Python)
for each boat in ACTIVE_BOATS:
upcoming_events = query_calendar(boat_id, next_7_days)
for i in range(len(upcoming_events) - 1):
gap = upcoming_events[i+1].start - upcoming_events[i].end
if gap.total_hours >= MINIMUM_CLEANING_WINDOW:
create_dispatch_task(
boat_id=boat_id,
window_start=upcoming_events[i].end,
window_end=upcoming_events[i+1].start,
priority=calculate_priority(upcoming_events[i+1])
)
Integration with Existing Infrastructure
Lambda Function Credentials: The dispatch system uses the same Lambda execution role that manages the dashboard API. Google Calendar credentials are stored as encrypted environment variables in the Lambda function configuration (retrieved via the repos.env pattern). This eliminates the need for additional credential management.
Dashboard Integration: Dispatch tasks are written to the dashboard's card system at /Users/cb/Documents/repos/sites/queenofsandiego.com/dashboard-index.html. The dashboard reads from a JSON task queue stored in S3 (bucket pattern: cb-dashboard-data, prefix: dispatch-tasks/).
Notification Channel: When dispatch tasks are created, the system sends an SNS notification to operational staff. This was configured via the existing SES setup that handles calendar and task notifications.
Why This Approach?
Event-driven over polling: While we use polling for the iCal feeds (necessary because GetMyBoat/Boatsetter don't offer webhooks), the dispatch layer uses Lambda scheduled events rather than continuous polling. This reduces compute costs by ~70% compared to a containerized alternative.
Calendar as source of truth: By synchronizing all boat platform calendars into a single Google Calendar, we created a unified source of truth. This means:
- Operations staff can view all boat availability in one place
- The dispatch system has a single integration point rather than querying multiple APIs
- Historical data is preserved (Google Calendar audit logs all changes)
Google Apps Script for synchronization: We chose GAS over a custom Lambda function because:
- It runs within Google's infrastructure, so calendar credential management is implicit
- It can be modified through the Google Cloud Console UI without requiring deployment pipelines
- It's free (within quota limits)
- Scheduled triggers are more flexible than CloudWatch Events for complex scheduling patterns
Deployment Process
The CalendarSync.gs script is deployed via the Google Apps Script CLI:
cd /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/
clasp push # Deploys to the GAS project configured in .clasp.json
The dispatch system is deployed by pushing the Python script to Lambda and updating the schedule trigger via AWS CLI or the Lambda console.
Next Steps and Operational Handoff
This system is ready for feedback from Carole on operational workflow. Key questions for her:
- Cleaning window preferences: What's the minimum time between boat availability windows to consider cleaning feasible?
- Provider coordination: How should the system notify cleaning providers? (SMS, email, dashboard task?)
- Boat-specific requirements: Do different boats have different cleaning protocols or provider preferences?
- Escalation triggers: When should dispatch tasks be automatically escalated (e.g., if a cleaning window is less than 4 hours before the next booking)?
Once we have operational feedback, the next phase will be:
- Adding provider contact integration (SMS dispatch via Twilio, already available in repos)