```html

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 text
  • category: Determines color, size, and layout behavior
  • active: For greyed-out future platforms
  • drilldown_fn: Reference to a GAS function that populates the detail panel when clicked
  • health_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.gs introspection)
  • 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:

  1. Runs syntax checks on the HTML/JS (via Node.js runtime or browser console simulation)
  2. Uploads index.html to the S3 bucket via AWS CLI
  3. Invalidates the CloudFront cache with path /*
  4. Polls CloudFront until the distribution status