AI Integration
Last updated: 10/20/2018
AI Integration
Overview
CareHub uses AI (Azure OpenAI GPT-4o or OpenAI GPT-4o) to assist clinicians with:
- Intake analysis and risk assessment
- Session transcript summarization
- Care plan suggestions
- Progress assessment
- Risk detection
Important: AI provides suggestions and assistance. All clinical decisions are made by licensed clinicians.
Status Legend:
- ✅ IMPLEMENTED - Functional in codebase
- 🚧 PARTIAL - Partially implemented
- 📋 PLANNED - In specification, not yet built
Architecture ✅ IMPLEMENTED
Provider Configuration
CareHub supports both Azure OpenAI (production) and OpenAI (development):
// src/lib/ai.ts
import OpenAI from "openai";
// Auto-detect Azure vs OpenAI based on environment
const isAzure = !!process.env.AZURE_OPENAI_ENDPOINT;
const openai = isAzure
? new OpenAI({
apiKey: process.env.AZURE_OPENAI_API_KEY || "",
baseURL: `${process.env.AZURE_OPENAI_ENDPOINT}/openai/deployments/${process.env.AZURE_OPENAI_DEPLOYMENT}`,
defaultQuery: { "api-version": "2024-02-15-preview" },
defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY },
})
: new OpenAI({
apiKey: process.env.OPENAI_API_KEY || "",
});
const MODEL = isAzure
? (process.env.AZURE_OPENAI_DEPLOYMENT || "gpt-4o")
: "gpt-4o";
Environment Variables
# Azure OpenAI (Production - HIPAA Compliant)
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_ENDPOINT=https://oai-carehub-dev.openai.azure.com
AZURE_OPENAI_DEPLOYMENT=gpt-4o
# OpenAI (Development)
OPENAI_API_KEY=sk-...
Configurable Prompts ✅ IMPLEMENTED
AI prompts are configurable via the Admin workflow settings:
import { getPrompt } from "@/lib/workflow-config";
// Get prompt from database, fall back to default
let promptTemplate = await getPrompt(
"intake-complete",
"intake_analysis_prompt",
{}
);
if (!promptTemplate) {
promptTemplate = DEFAULT_INTAKE_PROMPT;
}
Feature 1: Intake Analysis ✅ IMPLEMENTED
Purpose
Analyze completed intake form and provide clinical summary, risk assessment, and treatment recommendations.
Trigger
- Workflow:
carehub/intake.completedevent - Manual: "Re-analyze" button in case detail
Input Data
interface IntakeAnalysisInput {
primaryConcern: string;
symptoms: string;
onsetDate: string;
phq9Score?: number;
gad7Score?: number;
previousTreatment?: string | null;
suicidalIdeation?: boolean;
homicidalIdeation?: boolean;
safetyPlan?: string | null;
substanceUse?: string | { alcohol?: string; drugs?: string; tobacco?: string };
familyHistory?: string | null;
employer?: string | null;
jobTitle?: string | null;
// WC causation factors
workCausation?: number;
personnelActions?: {
discipline?: boolean;
termination?: boolean;
details?: string | null;
};
nonIndustrialStressors?: {
financialStress?: boolean;
relationshipIssues?: boolean;
otherStressors?: string | null;
};
}
Output
interface IntakeAnalysis {
summary: string;
riskLevel: "LOW" | "MODERATE" | "HIGH";
riskIndicators: string[];
primaryConcerns: string[];
recommendations: string[];
}
Default Prompt
You are a clinical psychologist assistant helping with intake assessments
for a California Workers' Compensation mental health case (per Labor Code 3208.3).
INTAKE DATA:
- Primary Concern: {{primaryConcern}}
- Symptoms: {{symptoms}}
- PHQ-9 Score: {{phq9Score}} (0-27 scale, >15 = severe depression)
- GAD-7 Score: {{gad7Score}} (0-21 scale, >15 = severe anxiety)
- Suicidal Ideation: {{suicidalIdeation}}
- Homicidal Ideation: {{homicidalIdeation}}
...
Important considerations:
1. If suicidal or homicidal ideation is present, risk level MUST be HIGH.
2. Note any personnel actions (discipline/termination) affecting causation.
3. Consider non-industrial stressors as potential contributing factors.
Error Handling
// Returns safe default on AI failure
catch (error) {
console.error("AI intake analysis error:", error);
return {
summary: "Unable to generate AI analysis. Manual review required.",
riskLevel: intakeData.suicidalIdeation || intakeData.homicidalIdeation
? "HIGH" : "MODERATE",
riskIndicators: intakeData.suicidalIdeation
? ["Suicidal ideation reported"] : [],
primaryConcerns: [intakeData.primaryConcern || "See intake form"],
recommendations: ["Complete clinical evaluation", "Establish treatment plan"],
};
}
Feature 2: Session Summarization ✅ IMPLEMENTED
Purpose
Analyze therapy session transcript/notes to generate summary, identify themes, track goal progress, and flag concerns.
Trigger
- Workflow:
carehub/session.completedevent - Manual: "Generate AI Summary" button in session notes
Input Data
interface SessionSummaryInput {
sessionNumber: number;
sessionType: string;
duration: number;
notes: string;
transcript?: string; // From video transcription
previousSummary?: string; // Previous session AI summary
carePlanGoals?: string[]; // Current care plan goals
}
Output
interface SessionSummary {
summary: string;
keyThemes: string[];
moodAssessment: string;
progressNotes: string;
interventionsUsed: string[];
recommendationsForNext: string;
riskLevel: "LOW" | "MODERATE" | "HIGH";
}
Default Prompt
You are a clinical psychologist assistant helping document therapy sessions
for a Workers' Compensation mental health case.
SESSION DATA:
- Session Number: {{sessionNumber}}
- Session Type: {{sessionType}}
- Duration: {{duration}} minutes
- Session Notes: {{notes}}
{{transcript}}
{{previousSummary}}
{{carePlanGoals}}
Provide your analysis in JSON format with: summary, keyThemes, moodAssessment,
progressNotes, interventionsUsed, recommendationsForNext, riskLevel.
Be professional and objective. Focus on clinical observations.
Feature 3: Session Transcription ✅ IMPLEMENTED
Purpose
Transcribe video session recordings using Azure Speech Services batch API.
Architecture
┌─────────────────┐
│ Video Session │
│ (Twilio) │
└────────┬────────┘
│ recording URL
▼
┌─────────────────┐
│ Azure Speech │
│ Batch API │
│ v3.1 │
└────────┬────────┘
│ transcription
▼
┌─────────────────┐
│ Session Record │
│ (transcript) │
└─────────────────┘
Implementation
// src/lib/azure-speech.ts
/**
* Create a batch transcription job
*/
export async function createBatchTranscription(
audioUrls: string[],
displayName: string,
locale: string = "en-US"
): Promise<string> {
const response = await fetch(
`${SPEECH_ENDPOINT}/speechtotext/v3.1/transcriptions`,
{
method: "POST",
headers: {
"Ocp-Apim-Subscription-Key": SPEECH_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
contentUrls: audioUrls,
locale,
displayName,
properties: {
wordLevelTimestampsEnabled: true,
diarizationEnabled: true, // Speaker separation
punctuationMode: "DictatedAndAutomatic",
profanityFilterMode: "None", // Clinical context
},
}),
}
);
const data = await response.json();
return data.self.split("/").pop(); // Extract transcription ID
}
Types
interface TranscriptionJob {
id: string;
status: "NotStarted" | "Running" | "Succeeded" | "Failed";
createdDateTime: string;
lastActionDateTime: string;
displayName: string;
locale: string;
}
interface TranscriptionResult {
text: string;
duration: string;
phrases: TranscriptionPhrase[];
}
interface TranscriptionPhrase {
text: string;
offset: string;
duration: string;
confidence: number;
speaker?: number; // Diarization result
}
Polling & Results
/**
* Poll for transcription completion
*/
export async function waitForTranscription(
transcriptionId: string,
maxWaitMs: number = 10 * 60 * 1000, // 10 minutes
pollIntervalMs: number = 10 * 1000 // 10 seconds
): Promise<TranscriptionResult | null> {
const startTime = Date.now();
while (Date.now() - startTime < maxWaitMs) {
const status = await getTranscriptionStatus(transcriptionId);
if (status.status === "Succeeded") {
return await getTranscriptionResult(transcriptionId);
}
if (status.status === "Failed") {
console.error(`Transcription ${transcriptionId} failed`);
return null;
}
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
}
return null; // Timeout
}
Speaker-Labeled Output
/**
* Format transcript with speaker labels for clinical notes
*/
export function formatTranscriptWithSpeakers(
result: TranscriptionResult
): string {
// Group consecutive phrases by speaker
const lines: string[] = [];
let currentSpeaker: number | undefined;
let currentText = "";
for (const phrase of result.phrases) {
if (phrase.speaker !== currentSpeaker) {
if (currentText) {
const speakerLabel = currentSpeaker !== undefined
? `[Speaker ${currentSpeaker + 1}]`
: "[Unknown]";
lines.push(`${speakerLabel}: ${currentText.trim()}`);
}
currentSpeaker = phrase.speaker;
currentText = phrase.text;
} else {
currentText += " " + phrase.text;
}
}
// Output format:
// [Speaker 1]: Hello, how are you feeling today?
// [Speaker 2]: I've been struggling with anxiety this week...
return lines.join("\n\n");
}
Environment Variables
AZURE_SPEECH_KEY=...
AZURE_SPEECH_REGION=eastus2 # or westus2
Feature 4: Care Plan Suggestions ✅ IMPLEMENTED
Purpose
Generate suggested treatment plan based on intake and evaluation data.
Trigger
- Manual: "Get AI Suggestions" button in care plan builder
- Workflow: After evaluation session is documented
Input Data
interface CarePlanInput {
diagnosis: string;
primaryConcerns: string[];
phq9Score?: number;
gad7Score?: number;
gafInitial?: number;
workStatus: string;
injuryType: string; // Physical-Mental, Mental-Mental, Mental-Physical
previousTreatment?: string;
}
Output
interface CarePlanSuggestion {
diagnosisCodes: string[]; // ICD-10 codes
presentingIssues: string;
treatmentGoals: Array<{
description: string;
measurableOutcome: string;
targetDate: string;
}>;
interventions: string;
frequency: "WEEKLY" | "BI_WEEKLY" | "MONTHLY";
estimatedSessions: number; // 5-12 typical
expectedOutcomes: string;
rtwGoal: string; // Return-to-work goal
}
Prompt Considerations
The prompt includes California Workers' Compensation context:
- Standard authorization is 7 sessions
- Focus on return-to-work goals
- SMART goal format
- ICD-10 diagnosis codes
Feature 5: Risk Assessment ✅ IMPLEMENTED
Purpose
Assess and flag safety concerns based on clinical data.
Immediate High-Risk Detection
// Immediate high-risk indicators (no AI needed)
if (
clinicalData.suicidalIdeation ||
clinicalData.homicidalIdeation ||
clinicalData.previousAttempts
) {
return {
riskLevel: "HIGH",
riskFactors: [
clinicalData.suicidalIdeation ? "Active suicidal ideation" : "",
clinicalData.homicidalIdeation ? "Homicidal ideation" : "",
clinicalData.previousAttempts ? "History of previous attempts" : "",
].filter(Boolean),
recommendations: [
"Immediate clinical evaluation required",
"Safety planning essential",
"Consider crisis intervention",
],
};
}
AI-Assisted Assessment
interface RiskAssessmentInput {
suicidalIdeation: boolean;
homicidalIdeation: boolean;
phq9Score?: number;
gad7Score?: number;
substanceUse?: string;
recentCrisis?: boolean;
supportSystem?: string;
previousAttempts?: boolean;
}
interface RiskAssessmentOutput {
riskLevel: "LOW" | "MODERATE" | "HIGH";
riskFactors: string[];
protectiveFactors: string[];
recommendations: string[];
}
Risk Thresholds
| Indicator | Threshold | Risk Level |
|---|---|---|
| Suicidal Ideation | Present | HIGH |
| Homicidal Ideation | Present | HIGH |
| Previous Attempts | History | HIGH |
| PHQ-9 | ≥20 | HIGH |
| PHQ-9 | 15-19 | MODERATE |
| GAD-7 | ≥15 | HIGH |
| GAD-7 | 10-14 | MODERATE |
Feature 6: Progress Assessment 📋 PLANNED
Purpose
Generate periodic progress reports analyzing treatment effectiveness.
Trigger
- Manual: "Generate Progress Report" button
- Scheduled: Weekly/monthly summary
- Before RFA submission
Input Data
interface ProgressAssessmentInput {
caseId: string;
sessionSummaries: SessionSummary[];
checkIns: CheckIn[]; // Mood, pain levels over time
carePlanGoals: CarePlanGoal[];
sessionsCompleted: number;
sessionsRemaining: number;
}
Planned Output
interface ProgressAssessment {
overallProgress: "On Track" | "Ahead" | "Behind" | "At Risk";
progressSummary: string;
goalAssessments: Array<{
goal: string;
status: "Not Started" | "In Progress" | "Nearing" | "Achieved";
evidence: string;
}>;
symptomTrends: {
pain: "Improving" | "Stable" | "Worsening";
mood: "Improving" | "Stable" | "Worsening";
function: "Improving" | "Stable" | "Worsening";
};
treatmentEffectiveness: string;
recommendations: string[];
rtwReadiness: {
status: "Not Ready" | "Progressing" | "Nearly Ready" | "Ready";
barriersRemaining: string[];
};
prognosis: string;
}
Integration with Workflows ✅ IMPLEMENTED
Intake Complete Workflow
// src/inngest/functions/intake-complete.ts
export const intakeCompleteWorkflow = inngest.createFunction(
{ id: "intake-complete", retries: 3 },
{ event: "carehub/intake.completed" },
async ({ event, step }) => {
// Step 1: Run AI analysis
const analysis = await step.run("analyze-intake", async () => {
return await analyzeIntake({
primaryConcern: event.data.primaryConcern,
symptoms: event.data.symptoms,
phq9Score: event.data.phq9Score,
gad7Score: event.data.gad7Score,
suicidalIdeation: event.data.suicidalIdeation,
homicidalIdeation: event.data.homicidalIdeation,
// ... other fields
});
});
// Step 2: Update case with AI results
await step.run("update-case", async () => {
await prisma.case.update({
where: { id: event.data.caseId },
data: {
aiIntakeSummary: analysis.summary,
riskLevel: analysis.riskLevel,
currentPhase: "EVALUATION",
},
});
});
// Step 3: Notify on high risk
if (analysis.riskLevel === "HIGH") {
await step.run("notify-coordinator", async () => {
// Send alert
});
}
}
);
Session Complete Workflow
// src/inngest/functions/session-complete.ts
export const sessionCompleteWorkflow = inngest.createFunction(
{ id: "session-complete", retries: 3 },
{ event: "carehub/session.completed" },
async ({ event, step }) => {
// Generate AI summary from notes + transcript
const summary = await step.run("summarize-session", async () => {
return await summarizeSession({
sessionNumber: event.data.sessionNumber,
notes: event.data.notes,
transcript: event.data.transcript,
// ...
});
});
// Update session with AI summary
await step.run("update-session", async () => {
await prisma.session.update({
where: { id: event.data.sessionId },
data: {
aiSummary: summary.summary,
riskLevel: summary.riskLevel,
},
});
});
}
);
Cost Management
Token Usage Estimates
| Feature | Input Tokens | Output Tokens | Est. Cost (GPT-4o) |
|---|---|---|---|
| Intake Analysis | ~1,500 | ~400 | ~$0.025 |
| Session Summary | ~1,200 | ~350 | ~$0.020 |
| Care Plan Suggestions | ~1,000 | ~400 | ~$0.020 |
| Risk Assessment | ~500 | ~200 | ~$0.010 |
| Transcription | N/A | N/A | ~$0.016/min |
Cost Optimization
- Azure OpenAI Deployment - Use provisioned throughput for predictable costs
- Batch Transcription - Process recordings in batches
- Prompt Optimization - Keep prompts concise
- Caching - Cache repeated analyses (same intake data)
- Conditional Processing - Only run AI when needed
Error Handling
// All AI functions return safe defaults on failure
async function analyzeIntake(data: IntakeData): Promise<IntakeAnalysis> {
try {
const response = await openai.chat.completions.create({
model: MODEL,
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
response_format: { type: "json_object" },
});
return JSON.parse(response.choices[0]?.message?.content);
} catch (error) {
console.error("AI intake analysis error:", error);
// Safe default - preserves critical safety flags
return {
summary: "Unable to generate AI analysis. Manual review required.",
riskLevel: data.suicidalIdeation || data.homicidalIdeation
? "HIGH" : "MODERATE",
riskIndicators: data.suicidalIdeation
? ["Suicidal ideation reported"] : [],
primaryConcerns: [data.primaryConcern || "See intake form"],
recommendations: ["Complete clinical evaluation"],
};
}
}
Privacy & HIPAA Compliance
Data Handling
- Azure OpenAI - HIPAA-eligible service with BAA available
- Text Only - No image analysis (not HIPAA-eligible in Azure)
- Minimum Data - Only send data required for analysis
- No Storage - AI providers don't store requests (per BAA)
Audit Logging
// All AI interactions are logged for HIPAA compliance
await prisma.auditLog.create({
data: {
action: "AI_ANALYSIS",
entityType: "INTAKE",
entityId: intakeId,
userId: systemUserId,
details: {
model: MODEL,
feature: "intake_analysis",
success: true,
},
},
});
Environment Separation
| Environment | AI Provider | PHI Allowed |
|---|---|---|
| Development | OpenAI | No (use test data) |
| QA | Azure OpenAI | Limited (test data) |
| Production | Azure OpenAI | Yes (with BAA) |
API Routes
| Route | Method | Description |
|---|---|---|
/api/admin/workflows/[slug]/configs |
GET/PUT | Configure AI prompts |
/api/psych/cases/[id]/analyze-intake |
POST | Re-run intake analysis |
/api/lpcc/sessions/[id]/summarize |
POST | Generate session summary |
/api/inngest |
POST | Workflow event handler |