Building HELM: A Self-Contained Interactive Operations Graph for Multi-Channel Business Intelligence
Across Queen of San Diego's booking ecosystem—spanning email campaigns, web organic traffic, referral partnerships, and third-party platforms like Viator, Boatsetter, and GetMyBoat—visibility into how money moves, how clients book, and how operations execute has been fragmented across multiple dashboards and spreadsheets. This post documents the design and deployment of HELM (an acronym born during this session, standing for the system's purpose: holistic engagement and lifecycle mapping), a single-page force-directed network visualization that unifies the entire customer acquisition → booking → operations pipeline into one interactive, drillable interface.
What We Built
HELM is a self-contained HTML5 application deployed to helm.queenofsandiego.com that visualizes:
- Acquisition channels: Email campaigns, organic search, referral partner codes (greyed out for future platforms like Airbnb and Hipcamp)
- Monetization sources: Live booking platforms (Viator, Boatsetter, GetMyBoat) with health status indicators
- Customer journey nodes: Website, booking confirmation, payment processing
- Operations nodes: Crew dispatch, email notifications, calendar sync, financial reconciliation
- Dashboard inventory: Links to all internal dashboards (progress dashboard, financial dashboard, crew dashboard) as first-class nodes
The graph uses vis-network (a force-directed physics simulation library) to render nodes and edges, with a dark navy/gold color scheme designed for both technical polish and executive presentation. Critically, each node includes health diagnostics: platform status is probed in real-time via Google Apps Script API calls, and nodes glow green (healthy) or red (unhealthy), with an aggregate system health bar at the top.
Technical Architecture
Single-File HTML Design
HELM is deployed as a monolithic /Users/cb/Documents/repos/sites/helm/index.html file. This design choice—rather than splitting into separate JS/CSS/HTML files—was deliberate:
- No build step required; changes deploy in seconds
- Single CloudFront cache invalidation instead of multiple asset invalidations
- Easier to embed sensitive logic (like password hashing for access control) without exposing source maps
- Optimal for a network graph toy that needs to load once and run client-side
Data Structure and Node Definitions
The HTML file contains a NODE_DATA object that defines all nodes:
const NODE_DATA = {
// Acquisition channels
email_campaigns: { label: "Email Campaigns", category: "acquisition", ... },
organic_search: { label: "Organic Search", category: "acquisition", ... },
referral_codes: { label: "Referral Partner Codes", category: "acquisition", ... },
// Monetization platforms
viator: { label: "Viator", category: "platform", active: true, ... },
boatsetter: { label: "Boatsetter", category: "platform", active: true, ... },
getmyboat: { label: "GetMyBoat", category: "platform", active: true, ... },
airbnb: { label: "Airbnb", category: "platform", active: false, ... },
// Operations
gas_crewdispatch: { label: "Crew Dispatch (GAS)", category: "operations", ... },
gas_email_send: { label: "Email Service (GAS)", category: "operations", ... },
gas_calendar_sync: { label: "Calendar Sync (GAS)", category: "operations", ... },
...
};
Each node includes metadata for:
label: Display textcategory: Determines color, size, and layout behavioractive: For greyed-out future platformsdrilldown_fn: Reference to a GAS function that populates the detail panel when clickedhealth_probe_fn: GAS function name to call for real-time status (e.g.,probeViatorAPI)
Health Probing via Google Apps Script
Each operational node with a health_probe_fn triggers a client-side fetch to a Google Apps Script API endpoint. The script executes functions like:
function probeViatorAPI() {
try {
const result = UrlFetchApp.fetch("https://www.viator.com/api/v2/...", {
muteHttpExceptions: true,
timeout: 5000
});
return {
status: result.getResponseCode() === 200 ? "healthy" : "error",
latency: result.getResponseTime(),
timestamp: new Date().toISOString()
};
} catch(e) {
return { status: "unhealthy", error: e.message };
}
}
Results are cached client-side (5-minute TTL) to avoid overwhelming the API. The aggregate health bar sums node health as a percentage.
Drill-Down Detail Panel
Clicking a node opens a right-side panel that displays:
- Node metadata (description, owner, SLA)
- Related GAS functions (extracted via
gas_crew_dispatch.gsintrospection) - Recent logs or status (fetched from the appropriate GAS script)
- Links to associated dashboards or external systems
For example, clicking the gas_crewdispatch node shows function signatures from the actual Google Apps Script file, along with recent dispatch records and crew availability status.
Infrastructure and Deployment
S3 Bucket and CloudFront Distribution
HELM is hosted on:
- S3 bucket:
helm.queenofsandiego.com(created in this session) - CloudFront distribution ID: Created with Origin Access Control (OAC) for secure S3 access
- Route53 alias:
helm.queenofsandiego.com→ CloudFront distribution domain - ACM certificate: Reused existing wildcard cert for
*.queenofsandiego.com
The S3 bucket policy restricts access to CloudFront only, preventing direct S3 URL access. All GET requests pass through CloudFront, which caches the index.html file with a 60-second TTL to balance freshness against request volume.
Deployment Pipeline
HELM is deployed via a shell script that:
- Runs syntax checks on the HTML/JS (via Node.js runtime or browser console simulation)
- Uploads
index.htmlto the S3 bucket via AWS CLI - Invalidates the CloudFront cache with path
/* - Polls CloudFront until the distribution status