🔑

Vitalency Competitive Intel

Vitalency market research. Enter hub password to continue.

That's not right. Try again.

Vitalency - Private Hub

Product Spec (SPEC.md)

Source: SPEC.md

Back to Hub
Doc

Product Spec (SPEC.md)

Vitalency: Technical Specification

Status: v0.1 architecture sketch, 2026-04-25 Source plan: MASTER-PLAN.md

This document refines the architecture from MASTER-PLAN.md into engineering specifics. It's the document a developer (or Charlie) reads when building any agent or integration.


1. System overview

                    ┌────────────────────────────────────┐
                    │  EVENT INGESTION (every 20 min)    │
                    │  ─ Mindbody / Vagaro polling       │
                    │  ─ Twilio webhooks (SMS/voice)     │
                    │  ─ Lead-form webhooks (Meta, web)  │
                    └────────────────┬───────────────────┘
                                     │
                                     ▼
                    ┌────────────────────────────────────┐
                    │  CENTRAL BRAIN (decision engine)   │
                    │  ─ Update client profiles          │
                    │  ─ Recompute predictions:          │
                    │      next_visit_due                │
                    │      churn_risk                    │
                    │      upsell_score                  │
                    └────────────────┬───────────────────┘
                                     │
                ┌────────────────────┼────────────────────┐
                ▼                    ▼                    ▼
        ┌──────────────┐   ┌──────────────┐   ┌──────────────────┐
        │ REBOOKING    │   │ NO-SHOW      │   │ LEAD CONVERSION  │
        │ AGENT        │   │ RECOVERY     │   │ AGENT            │
        │              │   │ AGENT        │   │                  │
        └──────┬───────┘   └──────┬───────┘   └────────┬─────────┘
               │                   │                    │
               ▼                   ▼                    ▼
        ┌────────────────────────────────────────────────────┐
        │  ACTION LAYER                                      │
        │  ─ Compose message (LLM, brand-voice-aware)        │
        │  ─ Pick channel (SMS / email / call-back)          │
        │  ─ Schedule send                                   │
        │  ─ Log outcome                                     │
        └────────────────────────────────────────────────────┘

        + INBOUND: Voice booking agent (always-on, Twilio/Vapi)
        + INBOUND: Subscription optimization engine (weekly job)

2. Data model

Postgres schema (target, see schemas/*.sql once written)

clients

CREATE TABLE clients (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    spa_id          UUID NOT NULL REFERENCES spas(id),
    external_id     TEXT,                   -- Mindbody/Vagaro client ID
    name            TEXT NOT NULL,
    phone           TEXT,                   -- E.164
    email           TEXT,
    treatments      JSONB DEFAULT '[]',     -- [{name, last_done, count, avg_spend}]
    last_visit      TIMESTAMPTZ,
    total_spent     NUMERIC(10,2) DEFAULT 0,
    visit_count     INT DEFAULT 0,
    tags            TEXT[] DEFAULT '{}',    -- vip, churn-risk, no-show-prone
    created_at      TIMESTAMPTZ DEFAULT now(),
    updated_at      TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX clients_spa_lastvisit ON clients(spa_id, last_visit DESC);

events

CREATE TABLE events (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    spa_id          UUID NOT NULL,
    client_id       UUID REFERENCES clients(id),
    type            TEXT NOT NULL,          -- booking, no-show, lead, call, sms
    payload         JSONB,                  -- raw event data
    occurred_at     TIMESTAMPTZ NOT NULL,
    processed_at    TIMESTAMPTZ
);
CREATE INDEX events_spa_processed ON events(spa_id, processed_at);
CREATE INDEX events_client_time ON events(client_id, occurred_at DESC);

predictions

CREATE TABLE predictions (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    client_id           UUID NOT NULL REFERENCES clients(id),
    next_visit_due      TIMESTAMPTZ,
    churn_risk          NUMERIC(3,2),       -- 0.00–1.00
    upsell_score        NUMERIC(3,2),       -- 0.00–1.00
    upsell_target       TEXT,               -- name of recommended treatment/subscription
    rationale           TEXT,               -- LLM-generated explanation
    computed_at         TIMESTAMPTZ DEFAULT now()
);

calls

CREATE TABLE calls (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    spa_id          UUID NOT NULL,
    client_id       UUID REFERENCES clients(id),
    direction       TEXT,                   -- inbound, outbound
    transcript      TEXT,
    intent          TEXT,                   -- book, reschedule, cancel, faq, complaint, other
    outcome         TEXT,                   -- booked, transferred, dropped, escalated
    duration_sec    INT,
    started_at      TIMESTAMPTZ,
    ended_at        TIMESTAMPTZ
);

subscriptions

CREATE TABLE subscriptions (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    client_id       UUID NOT NULL REFERENCES clients(id),
    plan            TEXT NOT NULL,          -- spa-defined plan name
    credits         NUMERIC(10,2),          -- if credit-based
    renewal_date    DATE,
    status          TEXT DEFAULT 'active',  -- active, paused, churned
    created_at      TIMESTAMPTZ DEFAULT now()
);

Tenant isolation

Every data row carries spa_id. Queries are spa-scoped at the DB layer (RLS in v2; app-layer filter in v1). One client = one Postgres database, partitioned by spa_id row-level until volume justifies separate DBs per spa.

What we explicitly do NOT store

  • Treatment details that constitute PHI under HIPAA (most medspa Botox/filler/laser data is not PHI; medical-weight-loss / hormone / IV-nutrition data IS, flag at intake)
  • Payment card numbers (Stripe / square processes; we hold tokens only)
  • Photos / images of clients (out of scope for v1)
  • Session notes from clinical encounters

3. Agent specs

3.1 Rebooking Agent

Trigger cadence: every 20 min, scans clients where now() > predictions.next_visit_due and no booking event in the future.

Decision logic:

  1. Fetch client + treatment history
  2. For each treatment, look up the spa's configured cadence (Botox 90d, filler 6mo, laser package per package terms)
  3. If last_visit + cadence < now() and no future booking, queue rebooking action
  4. Pick channel: SMS if phone + opted-in; else email
  5. Compose message (LLM, brand-voice-tuned, NO "your treatment is overdue!" robotic tone)
  6. Send via Twilio / SMTP
  7. Log to events (type=outbound_rebook)

Key parameters per spa:

  • Treatment cadence map
  • Brand voice profile (tone snippet, dos/don'ts, sample messages)
  • Quiet hours (don't text after 9 PM local)
  • Opt-out list

3.2 No-Show Recovery Agent

Trigger: appointment status flipped to no_show in Mindbody/Vagaro.

Decision logic:

  1. Within 30 min of no-show: send empathetic recovery SMS with one-tap rebooking link
  2. If no response in 24 hr: second message, soft tone, possibly with small re-engagement offer (spa-configured)
  3. If no response in 72 hr: flag for human follow-up (front desk receives a daily digest)

3.3 Lead Conversion Agent

Trigger: lead webhook from Meta / web form / Instagram DM.

Decision logic:

  1. Within 5 min: respond with consultation-booking link + one-line warm hook
  2. Score lead intent (LLM): high / medium / low
  3. High-intent: continue conversation, book consultation directly
  4. Medium: nurture with 2-3 message sequence over 5 days
  5. Low: archive, optional re-engagement at 30 days

3.4 Voice Booking Agent

Tech: Vapi or Retell (both abstract over Twilio Voice + LLM + STT/TTS). Decide based on Mindbody/Vagaro integration support.

Call flow (per MASTER-PLAN):

  1. Greeting (spa-branded), "Thanks for calling [Spa Name]. I'm the booking assistant. How can I help?"
  2. Intent detection, book / reschedule / cancel / FAQ / complaint
  3. Qualification, name, phone, treatment desired, preferred time window
  4. Offer time slots, query Mindbody for openings
  5. Confirm + book
  6. SMS confirmation
  7. Escalate to human if: complaint, complication, "not a good fit" detection, repeated misunderstanding

Hard rule: any sentence the agent says that touches dosing, contraindications, "is X safe for me," or post-procedure symptoms triggers an immediate human transfer. The agent literally cannot say "yes you can take...", that's a transfer trigger.

3.5 Subscription Optimization Engine

Cadence: weekly batch.

Logic:

  1. For each client with ≥3 visits and ≥6 months tenure: compute treatment-frequency model
  2. If frequency × annual price > spa's subscription tier: client is a candidate
  3. LLM generates: (a) staff talking-point card for next visit, (b) optional client-facing nudge
  4. Front-desk dashboard shows top 10 subscription candidates per week

4. Integration adapters

4.1 Mindbody

  • Auth: OAuth 2.0
  • Pull: appointments, clients, services, sales (incremental via LastModifiedDateTime)
  • Push: create appointment, update client tag

4.2 Vagaro

  • Auth: API key
  • Pull / push: similar to Mindbody but flatter API
  • Lower priority than Mindbody (smaller medspa share)

4.3 Twilio

  • SMS send + receive (opt-out compliance: STOP keyword, log to subscriptions_optout)
  • Voice (if not using Vapi/Retell stack on top)

4.4 Email (Sendgrid / Postmark / Mailgun)

  • Decide based on Annette's existing accounts

5. Brand-voice-per-spa system

Each spa onboards with a voice profile:

spa_id: <uuid>
spa_name: "Glow Aesthetics Boulder"
voice_profile:
  tone: "warm, professional, slightly elevated; sounds like a luxury hotel concierge, not a tech company"
  dos:
    - "use the client's first name once per message"
    - "reference the specific treatment they had"
    - "acknowledge the time since last visit gently"
  donts:
    - "no exclamation marks"
    - "no emojis except heart for VIP-tagged clients"
    - "no urgency language ('don't miss out')"
  sample_message: "Hi Sarah, it's been about 12 weeks since your last Botox visit. Looking ahead, would you like to schedule for the week of June 3rd? Happy to pencil something in."
quiet_hours: "21:00-08:00 America/Denver"

The LLM composes every outbound message conditioned on this profile. Audit trail: every message logs the prompt + the model's output for compliance.


6. Security + compliance

  • Auth: per-spa admin login (email + magic link or SSO)
  • Tenant isolation: spa_id enforced at app + DB layer
  • TCPA compliance: SMS opt-in tracked; STOP/HELP handled
  • Data retention: configurable per spa (default 24 months of event history)
  • Backups: daily Postgres snapshots, 30-day retention
  • PII handling: phone/email logged; medical detail (treatment notes) NOT logged in plaintext at LLM layer (use treatment-name-only abstractions for prompt)

7. Open architectural questions

# Question Owner
1 Python or Node for orchestration? Annette
2 Self-host on VPS or Vercel/Railway? Charlie recommends VPS once VPS is stood up (Phase 2 of MyExecAssistant). Vercel/Railway in interim.
3 Voice stack: Vapi vs. Retell vs. roll-our-own on Twilio? Spike all three week 1 of Boulder, pick by integration depth
4 Multi-tenant Postgres vs. DB-per-spa? Multi-tenant w/ RLS until 50+ spas; revisit then
5 LLM provider for message composition? DeepSeek V3.1 via OpenRouter for short-form per global rules; Opus 4.7 for high-stakes voice calibration / brand-voice training

8. v1 ship definition

Minimum viable Revenue Brain (what client #1 in Boulder gets, $199–$399/mo Starter or Growth tier):

  1. Mindbody integration polling every 20 min
  2. Rebooking Agent firing SMS via Twilio
  3. No-Show Recovery Agent firing SMS within 30 min of marking
  4. Front-desk dashboard showing daily action queue
  5. Brand-voice profile configured for that one spa

Not in v1: voice agent, lead conversion, subscription engine, multi-spa support. Those are Premium/Pro tier features added in v2 and v3.

Rule: client #1's contract should reflect Starter or Growth pricing, the system delivers MORE than the Starter scope to ensure delight, but billing matches the tier sold.

Private - Hub