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.completed event
  • 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.completed event
  • 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

  1. Azure OpenAI Deployment - Use provisioned throughput for predictable costs
  2. Batch Transcription - Process recordings in batches
  3. Prompt Optimization - Keep prompts concise
  4. Caching - Cache repeated analyses (same intake data)
  5. 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

  1. Azure OpenAI - HIPAA-eligible service with BAA available
  2. Text Only - No image analysis (not HIPAA-eligible in Azure)
  3. Minimum Data - Only send data required for analysis
  4. 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