mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat: team module, multi-stage Dockerfile, doctor utils, AI config overhaul
- Team module: POST /api/team/members (in-place employee creation with temp password + Redis cache), PUT /api/team/members/:id, GET temp password endpoint. Uses signUpInWorkspace — no email invites. - Dockerfile: rewritten as multi-stage build (builder + runtime) so native modules compile for target arch. Fixes darwin→linux crash. - .dockerignore: exclude dist, node_modules, .env, .git, data/ - package-lock.json: regenerated against public npmjs.org (was pointing at localhost:4873 Verdaccio — broke docker builds) - Doctor utils: shared DOCTOR_VISIT_SLOTS_FRAGMENT + normalizeDoctors helper for visit-slot-aware queries across 6 consumers - AI config: full admin CRUD (GET/PUT/POST reset), workspace-scoped setup-state with workspace ID isolation, AI prompt defaults overhaul - Agent config: camelCase field fix for SDK-synced workspaces - Session service: workspace-scoped Redis key prefixing for setup state - Recordings/supervisor/widget services: updated to use doctor-utils shared fragments instead of inline visitingHours queries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,34 +1,286 @@
|
||||
// Admin-editable AI assistant config. Holds the user-facing knobs (provider,
|
||||
// model, temperature, optional system prompt override) — API keys themselves
|
||||
// stay in env vars because they are true secrets and rotation is an ops event.
|
||||
// model, temperature) AND a per-actor system prompt template map. API keys
|
||||
// themselves stay in env vars because they are true secrets and rotation is
|
||||
// an ops event.
|
||||
//
|
||||
// Each "actor" is a distinct AI persona used by the sidecar — widget chat,
|
||||
// CC agent helper, supervisor, lead enrichment, etc. Pulling these out of
|
||||
// hardcoded service files lets the hospital admin tune tone, boundaries,
|
||||
// and instructions per persona without a sidecar redeploy. The 7 actors
|
||||
// listed below cover every customer-facing AI surface in Helix Engage as
|
||||
// of 2026-04-08; internal/dev-only prompts (rules engine config helper,
|
||||
// recording speaker-channel identification) stay hardcoded since they are
|
||||
// not customer-tunable.
|
||||
//
|
||||
// Templating: each actor's prompt is a string with `{{variable}}` placeholders
|
||||
// that the calling service fills in via AiConfigService.renderPrompt(actor,
|
||||
// vars). The variable shape per actor is documented in the `variables` field
|
||||
// so the wizard UI can show admins what they can reference.
|
||||
|
||||
export type AiProvider = 'openai' | 'anthropic';
|
||||
|
||||
// Stable keys for each configurable persona. Adding a new actor:
|
||||
// 1. add a key here
|
||||
// 2. add a default entry in DEFAULT_AI_PROMPTS below
|
||||
// 3. add the corresponding renderPrompt call in the consuming service
|
||||
export const AI_ACTOR_KEYS = [
|
||||
'widgetChat',
|
||||
'ccAgentHelper',
|
||||
'supervisorChat',
|
||||
'leadEnrichment',
|
||||
'callInsight',
|
||||
'callAssist',
|
||||
'recordingAnalysis',
|
||||
] as const;
|
||||
export type AiActorKey = (typeof AI_ACTOR_KEYS)[number];
|
||||
|
||||
export type AiPromptConfig = {
|
||||
// Human-readable name shown in the wizard UI.
|
||||
label: string;
|
||||
// One-line description of when this persona is invoked.
|
||||
description: string;
|
||||
// Variables the template can reference, with a one-line hint each.
|
||||
// Surfaced in the edit slideout so admins know what `{{var}}` they
|
||||
// can use without reading code.
|
||||
variables: Array<{ key: string; description: string }>;
|
||||
// The current template (may be admin-edited).
|
||||
template: string;
|
||||
// The original baseline so we can offer a "reset to default" button.
|
||||
defaultTemplate: string;
|
||||
// Audit fields — when this prompt was last edited and by whom.
|
||||
// null on the default-supplied entries.
|
||||
lastEditedAt: string | null;
|
||||
lastEditedBy: string | null;
|
||||
};
|
||||
|
||||
export type AiConfig = {
|
||||
provider: AiProvider;
|
||||
model: string;
|
||||
// 0..2, controls randomness. Default 0.7 matches the existing hardcoded
|
||||
// values used in WidgetChatService and AI tools.
|
||||
temperature: number;
|
||||
// Optional admin-supplied system prompt addendum. Appended to the
|
||||
// hospital-specific prompts WidgetChatService generates from the doctor
|
||||
// roster, so the admin can add hospital-specific tone / boundaries
|
||||
// without rewriting the entire prompt.
|
||||
systemPromptAddendum: string;
|
||||
// Per-actor system prompt templates. Keyed by AiActorKey so callers can
|
||||
// do `config.prompts.widgetChat.template` and missing keys are caught
|
||||
// at compile time.
|
||||
prompts: Record<AiActorKey, AiPromptConfig>;
|
||||
version?: number;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Default templates — extracted verbatim from the hardcoded versions in:
|
||||
// - widget-chat.service.ts → widgetChat
|
||||
// - ai-chat.controller.ts → ccAgentHelper, supervisorChat
|
||||
// - ai-enrichment.service.ts → leadEnrichment
|
||||
// - ai-insight.consumer.ts → callInsight
|
||||
// - call-assist.service.ts → callAssist
|
||||
// - recordings.service.ts → recordingAnalysis
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const WIDGET_CHAT_DEFAULT = `You are a helpful, concise assistant for {{hospitalName}}.
|
||||
You are chatting with a website visitor named {{userName}}.
|
||||
|
||||
{{branchContext}}
|
||||
|
||||
TOOL USAGE RULES (STRICT):
|
||||
- When the user asks about departments, call list_departments and DO NOT also list departments in prose.
|
||||
- When they ask about clinic timings, visiting hours, or "when is X open", call show_clinic_timings.
|
||||
- When they ask about doctors in a department, call show_doctors and DO NOT also list doctors in prose.
|
||||
- When they ask about a specific doctor's availability or want to book with them, call show_doctor_slots.
|
||||
- When the conversation is trending toward booking, call suggest_booking.
|
||||
- After calling a tool, DO NOT restate its contents in prose. At most write a single short sentence
|
||||
(under 15 words) framing the widget, or no text at all. The widget already shows the data.
|
||||
- If you are about to write a bulleted or numbered list of departments, doctors, hours, or slots,
|
||||
STOP and call the appropriate tool instead.
|
||||
- NEVER use markdown formatting (no **bold**, no *italic*, no bullet syntax). Plain text only in
|
||||
non-tool replies.
|
||||
- NEVER invent or mention specific dates in prose or tool inputs. The server owns "today".
|
||||
If the visitor asks about a future date, tell them to use the Book tab's date picker.
|
||||
|
||||
OTHER RULES:
|
||||
- Answer other questions (directions, general info) concisely in prose.
|
||||
- If you do not know something, say so and suggest they call the hospital.
|
||||
- Never quote prices. No medical advice. For clinical questions, defer to a doctor.
|
||||
|
||||
{{knowledgeBase}}`;
|
||||
|
||||
const CC_AGENT_HELPER_DEFAULT = `You are an AI assistant for call center agents at {{hospitalName}}.
|
||||
You help agents answer questions about patients, doctors, appointments, clinics, and hospital services during live calls.
|
||||
|
||||
IMPORTANT — ANSWER FROM KNOWLEDGE BASE FIRST:
|
||||
The knowledge base below contains REAL clinic locations, timings, doctor details, health packages, and insurance partners.
|
||||
When asked about clinic timings, locations, doctor availability, packages, or insurance — ALWAYS check the knowledge base FIRST before saying you don't know.
|
||||
|
||||
RULES:
|
||||
1. For patient-specific questions (history, appointments, calls), use the lookup tools. NEVER guess patient data.
|
||||
2. For doctor details beyond what's in the KB, use the lookup_doctor tool.
|
||||
3. For clinic info, timings, packages, insurance → answer directly from the knowledge base below.
|
||||
4. If you truly cannot find the answer in the KB or via tools, say "I couldn't find that in our system."
|
||||
5. Be concise — agents are on live calls. Under 100 words unless asked for detail.
|
||||
6. NEVER give medical advice, diagnosis, or treatment recommendations.
|
||||
7. Format with bullet points for easy scanning.
|
||||
|
||||
KNOWLEDGE BASE (this is real data from our system):
|
||||
{{knowledgeBase}}`;
|
||||
|
||||
const SUPERVISOR_CHAT_DEFAULT = `You are an AI assistant for supervisors at {{hospitalName}}'s call center (Helix Engage).
|
||||
You help supervisors monitor team performance, identify issues, and make data-driven decisions.
|
||||
|
||||
## YOUR CAPABILITIES
|
||||
You have access to tools that query real-time data:
|
||||
- **Agent performance**: call counts, conversion rates, NPS scores, idle time, pending follow-ups
|
||||
- **Campaign stats**: lead counts, conversion rates per campaign, platform breakdown
|
||||
- **Call summary**: total calls, inbound/outbound split, missed call rate, disposition breakdown
|
||||
- **SLA breaches**: missed calls that haven't been called back within the SLA threshold
|
||||
|
||||
## RULES
|
||||
1. ALWAYS use tools to fetch data before answering. NEVER guess or fabricate performance numbers.
|
||||
2. Be specific — include actual numbers from the tool response, not vague qualifiers.
|
||||
3. When comparing agents, use their configured thresholds (minConversionPercent, minNpsThreshold, maxIdleMinutes) and team averages. Let the data determine who is underperforming — do not assume.
|
||||
4. Be concise — supervisors want quick answers. Use bullet points.
|
||||
5. When recommending actions, ground them in the data returned by tools.
|
||||
6. If asked about trends, use the call summary tool with different periods.
|
||||
7. Do not use any agent name in a negative context unless the data explicitly supports it.`;
|
||||
|
||||
const LEAD_ENRICHMENT_DEFAULT = `You are an AI assistant for a hospital call center.
|
||||
An inbound call is coming in from a lead. Summarize their history and suggest what the call center agent should do.
|
||||
|
||||
Lead details:
|
||||
- Name: {{leadName}}
|
||||
- Source: {{leadSource}}
|
||||
- Interested in: {{interestedService}}
|
||||
- Current status: {{leadStatus}}
|
||||
- Lead age: {{daysSince}} days
|
||||
- Contact attempts: {{contactAttempts}}
|
||||
|
||||
Recent activity:
|
||||
{{activities}}`;
|
||||
|
||||
const CALL_INSIGHT_DEFAULT = `You are a CRM assistant for {{hospitalName}}.
|
||||
Generate a brief, actionable insight about this lead based on their interaction history.
|
||||
Be specific — reference actual dates, dispositions, and patterns.
|
||||
If the lead has booked appointments, mention upcoming ones.
|
||||
If they keep calling about the same thing, note the pattern.`;
|
||||
|
||||
const CALL_ASSIST_DEFAULT = `You are a real-time call assistant for {{hospitalName}}.
|
||||
You listen to the customer's words and provide brief, actionable suggestions for the CC agent.
|
||||
|
||||
{{context}}
|
||||
|
||||
RULES:
|
||||
- Keep suggestions under 2 sentences
|
||||
- Focus on actionable next steps the agent should take NOW
|
||||
- If customer mentions a doctor or department, suggest available slots
|
||||
- If customer wants to cancel or reschedule, note relevant appointment details
|
||||
- If customer sounds upset, suggest empathetic response
|
||||
- Do NOT repeat what the agent already knows`;
|
||||
|
||||
const RECORDING_ANALYSIS_DEFAULT = `You are a call quality analyst for {{hospitalName}}.
|
||||
Analyze the following call recording transcript and provide structured insights.
|
||||
Be specific, brief, and actionable. Focus on healthcare context.
|
||||
{{summaryBlock}}
|
||||
{{topicsBlock}}`;
|
||||
|
||||
// Helper that builds an AiPromptConfig with the same template for both
|
||||
// `template` and `defaultTemplate` — what every actor starts with on a
|
||||
// fresh boot.
|
||||
const promptDefault = (
|
||||
label: string,
|
||||
description: string,
|
||||
variables: Array<{ key: string; description: string }>,
|
||||
template: string,
|
||||
): AiPromptConfig => ({
|
||||
label,
|
||||
description,
|
||||
variables,
|
||||
template,
|
||||
defaultTemplate: template,
|
||||
lastEditedAt: null,
|
||||
lastEditedBy: null,
|
||||
});
|
||||
|
||||
export const DEFAULT_AI_PROMPTS: Record<AiActorKey, AiPromptConfig> = {
|
||||
widgetChat: promptDefault(
|
||||
'Website widget chat',
|
||||
'Patient-facing bot embedded on the hospital website. Handles general questions, finds doctors, suggests appointments.',
|
||||
[
|
||||
{ key: 'hospitalName', description: 'Branded hospital display name from theme.json' },
|
||||
{ key: 'userName', description: 'Visitor first name (or "there" if unknown)' },
|
||||
{ key: 'branchContext', description: 'Pre-rendered branch-selection instructions block' },
|
||||
{ key: 'knowledgeBase', description: 'Pre-rendered list of departments + doctors + clinics' },
|
||||
],
|
||||
WIDGET_CHAT_DEFAULT,
|
||||
),
|
||||
ccAgentHelper: promptDefault(
|
||||
'CC agent helper',
|
||||
'In-call assistant the CC agent uses to look up patient history, doctor details, and clinic info while on a call.',
|
||||
[
|
||||
{ key: 'hospitalName', description: 'Branded hospital display name' },
|
||||
{ key: 'knowledgeBase', description: 'Pre-rendered hospital knowledge base (clinics, doctors, packages)' },
|
||||
],
|
||||
CC_AGENT_HELPER_DEFAULT,
|
||||
),
|
||||
supervisorChat: promptDefault(
|
||||
'Supervisor assistant',
|
||||
'AI tools the supervisor uses to query agent performance, campaign stats, and SLA breaches.',
|
||||
[
|
||||
{ key: 'hospitalName', description: 'Branded hospital display name' },
|
||||
],
|
||||
SUPERVISOR_CHAT_DEFAULT,
|
||||
),
|
||||
leadEnrichment: promptDefault(
|
||||
'Lead enrichment',
|
||||
'Generates an AI summary + suggested action for a new inbound lead before the agent picks up.',
|
||||
[
|
||||
{ key: 'leadName', description: 'Lead first + last name' },
|
||||
{ key: 'leadSource', description: 'Source channel (WHATSAPP, GOOGLE_ADS, etc.)' },
|
||||
{ key: 'interestedService', description: 'What the lead enquired about' },
|
||||
{ key: 'leadStatus', description: 'Current lead status' },
|
||||
{ key: 'daysSince', description: 'Days since the lead was created' },
|
||||
{ key: 'contactAttempts', description: 'Prior contact attempts count' },
|
||||
{ key: 'activities', description: 'Pre-rendered recent activity summary' },
|
||||
],
|
||||
LEAD_ENRICHMENT_DEFAULT,
|
||||
),
|
||||
callInsight: promptDefault(
|
||||
'Post-call insight',
|
||||
'After each call, generates a 2-3 sentence summary + a single suggested next action for the lead record.',
|
||||
[
|
||||
{ key: 'hospitalName', description: 'Branded hospital display name' },
|
||||
],
|
||||
CALL_INSIGHT_DEFAULT,
|
||||
),
|
||||
callAssist: promptDefault(
|
||||
'Live call whisper',
|
||||
'Real-time suggestions whispered to the CC agent during a call, based on the running transcript.',
|
||||
[
|
||||
{ key: 'hospitalName', description: 'Branded hospital display name' },
|
||||
{ key: 'context', description: 'Pre-rendered call context (current lead, recent activities, available doctors)' },
|
||||
],
|
||||
CALL_ASSIST_DEFAULT,
|
||||
),
|
||||
recordingAnalysis: promptDefault(
|
||||
'Call recording analysis',
|
||||
'Analyses post-call recording transcripts to extract key topics, action items, coaching notes, and compliance flags.',
|
||||
[
|
||||
{ key: 'hospitalName', description: 'Branded hospital display name' },
|
||||
{ key: 'summaryBlock', description: 'Optional pre-rendered "Call summary: ..." line (empty when none)' },
|
||||
{ key: 'topicsBlock', description: 'Optional pre-rendered "Detected topics: ..." line (empty when none)' },
|
||||
],
|
||||
RECORDING_ANALYSIS_DEFAULT,
|
||||
),
|
||||
};
|
||||
|
||||
export const DEFAULT_AI_CONFIG: AiConfig = {
|
||||
provider: 'openai',
|
||||
model: 'gpt-4o-mini',
|
||||
temperature: 0.7,
|
||||
systemPromptAddendum: '',
|
||||
prompts: DEFAULT_AI_PROMPTS,
|
||||
};
|
||||
|
||||
// Field-by-field mapping from the legacy env vars used by ai-provider.ts
|
||||
// (AI_PROVIDER + AI_MODEL). API keys are NOT seeded — they remain in env.
|
||||
export const AI_ENV_SEEDS: Array<{ env: string; field: keyof AiConfig }> = [
|
||||
export const AI_ENV_SEEDS: Array<{ env: string; field: keyof Pick<AiConfig, 'provider' | 'model'> }> = [
|
||||
{ env: 'AI_PROVIDER', field: 'provider' },
|
||||
{ env: 'AI_MODEL', field: 'model' },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user