mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat: Claude skill for generating WhatsApp flow JSON definitions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Skill documents the full flow schema (Groups, Blocks, Edges, Variables), all available tools, WhatsApp constraints, system variables, and deployment steps. Enables generating new flows from natural language descriptions — e.g., "create a prescription refill flow". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
203
.claude/skills/generate-whatsapp-flow.md
Normal file
203
.claude/skills/generate-whatsapp-flow.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Generate WhatsApp Flow
|
||||
|
||||
Generate a config-driven WhatsApp conversation flow JSON for the Helix Engage flow runtime engine.
|
||||
|
||||
## When to use
|
||||
|
||||
When the user asks to create a new WhatsApp flow, chatbot flow, or conversation automation — e.g., "create a WhatsApp flow for prescription refills", "build a feedback collection flow", "add a lab report flow".
|
||||
|
||||
## Flow Runtime Architecture
|
||||
|
||||
The flow engine reads JSON flow definitions from `src/messaging/flow/default-flows/` and executes them at runtime. Each flow is a graph of **Groups** (containers) containing **Blocks** (steps), connected by **Edges**.
|
||||
|
||||
### Execution Model
|
||||
|
||||
```
|
||||
Inbound WhatsApp message → match flow by trigger → create/resume session
|
||||
→ walk forward through Groups → Blocks:
|
||||
MessageBlock → send text/buttons/list to patient
|
||||
InputBlock → PAUSE, wait for next message
|
||||
ConditionBlock → evaluate variable, follow matching edge
|
||||
SetVariableBlock → assign/transform variable
|
||||
ToolCallBlock → call registered tool
|
||||
AIBlock → generate LLM response
|
||||
JumpBlock → jump to another group
|
||||
→ End of group → follow outgoing edge → next group
|
||||
→ No more edges → flow complete, session cleared
|
||||
```
|
||||
|
||||
Session state stored in Redis with 24h TTL. Per-phone execution lock prevents concurrent flows.
|
||||
|
||||
### Flow JSON Schema
|
||||
|
||||
```typescript
|
||||
type Flow = {
|
||||
id: string; // "flow-{kebab-name}"
|
||||
name: string; // Human-readable name
|
||||
description: string; // Admin-facing description
|
||||
trigger: FlowTrigger; // What starts this flow
|
||||
groups: Group[]; // Ordered containers of blocks
|
||||
edges: Edge[]; // Connections between blocks/groups
|
||||
variables: VariableDefinition[];// Flow-scoped variables
|
||||
version: number; // Start at 1
|
||||
status: 'draft' | 'published'; // Only published flows execute
|
||||
};
|
||||
|
||||
type FlowTrigger =
|
||||
| { type: 'message'; conditions?: { keywords?: string[]; regex?: string } }
|
||||
| { type: 'default' }; // Catch-all when no other flow matches
|
||||
|
||||
type Group = {
|
||||
id: string; // "g1", "g2", etc.
|
||||
title: string; // "Greeting", "Department Selection"
|
||||
blocks: Block[]; // Executed in order
|
||||
};
|
||||
|
||||
type Edge = {
|
||||
id: string; // "e1", "e2", etc.
|
||||
from: { blockId: string; conditionId?: string };
|
||||
to: { groupId: string; blockId?: string };
|
||||
};
|
||||
|
||||
type VariableDefinition = {
|
||||
id: string; // "v1", "v2", etc.
|
||||
name: string; // "selectedDepartment"
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
||||
defaultValue?: any;
|
||||
};
|
||||
```
|
||||
|
||||
### Block Types
|
||||
|
||||
```typescript
|
||||
// Send text, buttons, or list to patient
|
||||
type MessageBlock = {
|
||||
id: string; type: 'message';
|
||||
content:
|
||||
| { format: 'text'; text: string } // Supports {{variables}}
|
||||
| { format: 'buttons'; text: string; buttons: { id: string; title: string }[] } // Max 3 buttons, title max 20 chars
|
||||
| { format: 'list'; text: string; buttonText: string; sections: { title: string; rows: { id: string; title: string; description?: string }[] }[] }; // Section title max 24 chars, row title max 24 chars, max 10 rows total
|
||||
};
|
||||
|
||||
// Wait for patient reply — PAUSES execution
|
||||
type InputBlock = {
|
||||
id: string; type: 'input';
|
||||
inputType: 'text' | 'interactive_reply' | 'any';
|
||||
variableId: string; // Store reply in this variable
|
||||
validation?: { regex?: string; errorMessage?: string };
|
||||
};
|
||||
|
||||
// Branch based on variable value
|
||||
type ConditionBlock = {
|
||||
id: string; type: 'condition';
|
||||
conditions: {
|
||||
id: string; // "c1" — used in edge.from.conditionId
|
||||
variableId: string;
|
||||
operator: 'equals' | 'contains' | 'exists' | 'not_exists' | 'gt' | 'lt' | 'starts_with';
|
||||
value?: string; // Supports {{variables}}
|
||||
}[];
|
||||
};
|
||||
|
||||
// Assign or transform a variable
|
||||
type SetVariableBlock = {
|
||||
id: string; type: 'set_variable';
|
||||
variableId: string;
|
||||
value: string;
|
||||
expression?: 'extract_id' | 'extract_datetime' | 'date_tomorrow' | 'date_day_after';
|
||||
// extract_id: "doc:{uuid}:{name}" → uuid (second segment)
|
||||
// extract_datetime: "slot:{id}:{datetime}" → datetime (third+ segments, rejoined with :)
|
||||
// date_tomorrow/date_day_after: computes date string YYYY-MM-DD
|
||||
};
|
||||
|
||||
// Execute a registered tool
|
||||
type ToolCallBlock = {
|
||||
id: string; type: 'tool_call';
|
||||
toolName: string; // Must be a registered tool (see below)
|
||||
inputs: Record<string, string>; // Values support {{variables}} and {{var.field}} dot notation
|
||||
outputVariableId?: string;
|
||||
};
|
||||
|
||||
// Generate dynamic LLM response
|
||||
type AIBlock = {
|
||||
id: string; type: 'ai';
|
||||
prompt: string; // Supports {{variables}}
|
||||
outputVariableId?: string;
|
||||
sendToPatient: boolean; // true = send as WhatsApp message
|
||||
};
|
||||
|
||||
// Jump to another group
|
||||
type JumpBlock = {
|
||||
id: string; type: 'jump';
|
||||
targetGroupId: string;
|
||||
};
|
||||
```
|
||||
|
||||
### Available Tools (ToolRegistry)
|
||||
|
||||
| Tool Name | Description | Inputs | Output |
|
||||
|---|---|---|---|
|
||||
| `resolve_caller` | Phone → Lead + Patient | phone? (defaults to current) | { leadId, patientId, isNew, phone } |
|
||||
| `send_department_list` | Interactive department list | (none) | { sent, departments[] } |
|
||||
| `send_doctor_list` | Interactive doctor list | department | { sent, count } |
|
||||
| `send_slot_list` | Time slots for doctor+date | doctorId, doctorName, date | { sent, slots } |
|
||||
| `send_confirm_buttons` | Confirm/Cancel buttons | summary | { sent } |
|
||||
| `book_appointment` | Book with conflict check | patientName, phoneNumber, department, doctorName, scheduledAt, reason | { booked, appointmentId, reference } |
|
||||
| `lookup_appointments` | Check existing appointments | (none — uses current caller) | { appointments[] } |
|
||||
| `send_appointment_qr` | Generate and send QR code | appointmentId, reference, patientName, doctorName, department, scheduledAt | { sent, qrUrl } |
|
||||
|
||||
### System Variables (auto-injected)
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `_initialMessage` | The first message the patient sent |
|
||||
| `_senderName` | WhatsApp profile name |
|
||||
| `_phone` | Phone number (E.164 without +) |
|
||||
| `_callerName` | Resolved patient name from platform |
|
||||
| `_leadId` | Lead ID if exists |
|
||||
| `_patientId` | Patient ID if exists |
|
||||
| `_isNew` | true if no prior records |
|
||||
|
||||
### Variable Interpolation
|
||||
|
||||
- `{{variableName}}` — simple substitution
|
||||
- `{{result.fieldName}}` — dot notation for object fields (e.g., `{{bookingResult.appointmentId}}`)
|
||||
- Interactive reply IDs stored in `variableId`, display titles in `variableId_title`
|
||||
|
||||
### WhatsApp Constraints
|
||||
|
||||
- Button title: max 20 characters
|
||||
- List section title: max 24 characters
|
||||
- List row title: max 24 characters
|
||||
- List row description: max 72 characters
|
||||
- Max 3 buttons per message
|
||||
- Max 10 list rows total across all sections
|
||||
- No markdown in text messages (plain text only)
|
||||
- Interactive messages only work within 24h session window
|
||||
|
||||
## How to Generate
|
||||
|
||||
1. **Ask the user** what the flow should do — purpose, steps, what data to collect
|
||||
2. **Design the groups** — each logical phase is a group (Greeting, Selection, Confirmation, etc.)
|
||||
3. **Define variables** — what data flows through the conversation
|
||||
4. **Build blocks** — MessageBlocks for output, InputBlocks to pause for reply, ConditionBlocks for branching, ToolCallBlocks for platform operations, AIBlocks for dynamic responses
|
||||
5. **Wire edges** — connect groups via edges, condition edges for branching
|
||||
6. **Write the JSON** to `src/messaging/flow/default-flows/{flow-name}.json`
|
||||
7. **Register new tools** if needed in `src/messaging/flow/tool-registry.ts`
|
||||
|
||||
## Reference
|
||||
|
||||
See `src/messaging/flow/default-flows/appointment-booking.json` for a complete working example with:
|
||||
- AI greeting
|
||||
- Intent routing (book / check / question)
|
||||
- Interactive lists (departments, doctors, slots)
|
||||
- Date selection with custom date AI parsing
|
||||
- Confirmation buttons
|
||||
- Booking with conflict check
|
||||
- QR code generation
|
||||
|
||||
## Deployment
|
||||
|
||||
After creating the flow JSON:
|
||||
1. `npm run build` — verifies the JSON is copied to dist (via nest-cli.json assets)
|
||||
2. Deploy to EC2 — the flow store auto-seeds on first run if `data/flows/` is empty
|
||||
3. If updating an existing flow: `docker exec sidecar cp /app/dist/.../flow.json /app/data/flows/flow-id.json && docker compose restart sidecar`
|
||||
Reference in New Issue
Block a user