Kulcare Docs
DeveloperAuthentication Documentation04 featuresAi scribe

AI Scribe - Complete Implementation Documentation

Overview

AI Scribe is a comprehensive AI-powered medical transcription and suggestion system implemented for the Kulcare platform. This implementation includes real-time audio transcription using Deepgram, AI-powered medical suggestions using LLM, and seamless integration with the existing care plan system.

Table of Contents

  1. Architecture Overview
  2. System Architecture
  3. Data Flow Architecture
  4. Security Architecture
  5. Entity Relationship Diagram
  6. Core Components
  7. Database Schema
  8. API Endpoints
  9. Services and Integrations
  10. Background Jobs
  11. Configuration
  12. Security Considerations
  13. Testing Strategy
  14. Deployment and Monitoring

Architecture Overview

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Frontend App  │────▶│  Rails API       │────▶│   Deepgram API  │
│  (Audio Stream) │     │  (Transcription) │     │ (Speech-to-Text)│
└─────────────────┘     └──────────────────┘     └─────────────────┘
                                │
                                ▼
                       ┌──────────────────┐     ┌─────────────────┐
                       │  AI Suggestions  │────▶│   LLM API        │
                       │  Engine          │     │ (Ruby-LLM)      │
                       └──────────────────┘     └─────────────────┘
                                │
                                ▼
                       ┌──────────────────┐
                       │  Care Plan       │
                       │  Integration     │
                       └──────────────────┘

System Architecture

Data Flow Architecture

Security Architecture

Deepgram Temporary Token Authentication

Security Challenge: Direct frontend-to-Deepgram connection exposes API keys in client-side code, creating significant security vulnerabilities.

Solution: Deepgram temporary token system provides secure, time-limited authentication without exposing master API keys.

Security Flow

Security Benefits

✅ Master API Key Protection: Never exposed to client-side code ✅ Time-Limited Access: Tokens expire automatically (30s - 1hr) ✅ Audit Trail: All token generation logged for compliance ✅ Session-Based: Tokens tied to specific transcription sessions ✅ Healthcare Compliance: Meets HIPAA security requirements ✅ Zero Trust: No permanent credentials on frontend

Entity Relationship Diagram

Core Components

1. Transcription System

Models

TranscriptionSession (parts/clinic/models/transcription_session.rb)

class TranscriptionSession < ApplicationRecord
  belongs_to :doctor
  belongs_to :clinic
  belongs_to :appointment, optional: true

  has_many :transcription_segments, dependent: :destroy

  enum status: { init: 'init', recording: 'recording', completed: 'completed', failed: 'failed' }

  # Fields
  # - session_id: string (unique identifier)
  # - status: enum
  # - deepgram_session_id: string
  # - metadata: json
  # - created_at, updated_at
end

TranscriptionSegment (parts/clinic/models/transcription_segment.rb)

class TranscriptionSegment < ApplicationRecord
  belongs_to :transcription_session

  # Fields
  # - segment_id: string (from Deepgram)
  # - text: text
  # - speaker: integer
  # - start_time: float
  # - end_time: float
  # - confidence_score: float
  # - is_final: boolean
  # - created_at, updated_at
end

Controllers

TranscriptionSessionsController (parts/doctor/controllers/transcription_sessions_controller.rb)

class TranscriptionSessionsController < ApiController
  before_action :policy_check

  # POST /doctor/transcription_sessions
  def create
    result = TranscriptionSessions::CreateTranscriptionSession.call(params)
    render_result(result)
  end

  # POST /doctor/transcription_sessions/:id/segments
  def add_segment
    result = TranscriptionSessions::AddTranscriptionSegment.call(params)
    render_result(result)
  end

  # GET /doctor/transcription_sessions/:id
  def show
    result = TranscriptionSessions::ShowTranscriptionSession.call(params)
    render_result(result)
  end

  # GET /doctor/transcription_sessions/:id/full_transcript
  def get_full_transcript
    result = TranscriptionSessions::GetFullTranscript.call(params)
    render_result(result)
  end

  # POST /doctor/transcription_sessions/:id/stop
  def stop
    result = TranscriptionSessions::StopTranscriptionSession.call(params)
    render_result(result)
  end
end

Interactors

CreateTranscriptionSession (parts/doctor/interactors/transcription_sessions/create_transcription_session.rb)

module TranscriptionSessions
  class CreateTranscriptionSession < BaseInteractor
    def call
      require_context(:doctor_id)
      require_context(:clinic_id)

      # Create session
      session = TranscriptionSession.create!(
        doctor_id: context.doctor_id,
        clinic_id: context.clinic_id,
        appointment_id: context.appointment_id,
        session_id: SecureRandom.uuid,
        status: 'init'
      )

      context.transcription_session = session
      context.message = 'Transcription session created'
    end
  end
end

2. AI Suggestions Engine

Models

AiSuggestion (parts/clinic/models/ai_suggestion.rb)

class AiSuggestion < ApplicationRecord
  belongs_to :appointment
  belongs_to :transcription_session, optional: true
  belongs_to :processed_by_user, class_name: 'User', optional: true

  validates :suggestion_type, presence: true,
            inclusion: { in: %w[medication test diagnosis advice procedure note] }
  validates :content, :confidence_score, :reasoning, presence: true
  validates :confidence_score, inclusion: { in: 0.0..1.0 }
  validates :status, presence: true,
            inclusion: { in: %w[pending accepted rejected modified] }

  scope :pending, -> { where(status: 'pending') }
  scope :by_confidence, -> { order(confidence_score: :desc) }
  scope :by_type, ->(type) { where(suggestion_type: type) }

  def processed?
    %w[accepted rejected modified].include?(status)
  end

  def clinic
    appointment.clinic
  end

  def patient
    appointment.patient
  end

  def doctor
    appointment.doctor
  end
end

AiSuggestionItem (Referenced but not explicitly shown in files)

# This model would store individual suggestion items
# Fields:
# - ai_suggestion_id: references
# - item_type: string (medication, test, advice)
# - item_data: json
# - status: enum

Controllers

AiSuggestionsController (parts/doctor/controllers/ai_suggestions_controller.rb)

class AiSuggestionsController < ApiController
  before_action :policy_check

  # POST /doctor/ai_suggestions/generate
  def generate
    result = AiSuggestions::GenerateAiSuggestions.call(params)
    render_result(result)
  end

  # POST /doctor/ai_suggestions/generate_async
  def generate_async
    result = AiSuggestions::GenerateAiSuggestionsAsync.call(params)
    render_result(result)
  end

  # GET /doctor/ai_suggestions
  def list
    result = AiSuggestions::ListAiSuggestions.call(params)
    render_result(result)
  end

  # GET /doctor/ai_suggestions/:id
  def show
    result = AiSuggestions::ShowAiSuggestion.call(params)
    render_result(result)
  end

  # POST /doctor/ai_suggestions/:id/accept
  def accept
    result = AiSuggestions::AcceptAiSuggestion.call(params)
    render_result(result)
  end

  # POST /doctor/ai_suggestions/:id/reject
  def reject
    result = AiSuggestions::RejectAiSuggestion.call(params)
    render_result(result)
  end

  # PUT /doctor/ai_suggestions/:id
  def update
    result = AiSuggestions::UpdateAiSuggestion.call(params)
    render_result(result)
  end

  # GET /doctor/ai_suggestions/stats
  def get_stats
    result = AiSuggestions::GetAiSuggestionStats.call(params)
    render_result(result)
  end
end

Services

AiSuggestionService (parts/doctor/services/ai_suggestion_service.rb)

class AiSuggestionService
  def initialize(transcription_session, doctor)
    @transcription_session = transcription_session
    @doctor = doctor
  end

  def generate_suggestions
    # Get full transcript
    transcript = get_full_transcript

    # Build medical prompt
    prompt = MedicalPromptBuilder.build(
      transcript: transcript,
      doctor_profile: build_doctor_profile,
      patient_context: build_patient_context
    )

    # Call LLM
    llm_service = RubyLlmService.new
    response = llm_service.generate_suggestions(prompt)

    # Process and save suggestions
    process_llm_response(response)
  end

  private

  def get_full_transcript
    # Implementation to get complete transcript
  end

  def build_doctor_profile
    # Implementation to build doctor profile
  end

  def build_patient_context
    # Implementation to build patient context
  end

  def process_llm_response(response)
    # Implementation to process and save AI suggestions
  end
end

MedicalPromptBuilder (parts/doctor/services/medical_prompt_builder.rb)

class MedicalPromptBuilder
  def self.build(transcript:, doctor_profile:, patient_context:)
    prompt = <<~PROMPT
      You are an AI medical assistant helping a doctor with consultation documentation.

      Doctor Profile:
      - Specialization: #{doctor_profile[:specialization]}
      - Experience: #{doctor_profile[:experience]} years

      Patient Context:
      - Age: #{patient_context[:age]}
      - Gender: #{patient_context[:gender]}
      - Chief Complaint: #{patient_context[:chief_complaint]}

      Transcript:
      #{transcript}

      Please analyze the transcript and provide:
      1. Potential diagnoses (with confidence scores)
      2. Recommended medications (with dosage)
      3. Lab tests to order
      4. Follow-up instructions
      5. Medical history to update

      Format the response as structured JSON.
    PROMPT

    prompt
  end
end

3. Deepgram Integration

Controllers

IssueTokenController (parts/core/controllers/ai_scribe/issue_token_controller.rb)

class AiScribe::IssueTokenController < ApiController
  before_action :policy_check

  # POST /ai_scribe/issue_token
  def create
    result = Deepgram::Auth::CreateTemporaryKey.call(params)
    render_result(result, result.success? ? DeepgramAuthSerializer : nil)
  end

  private

  def policy_check
    authorize params, policy_class: AiScribeIssueTokenControllerPolicy
  end
end

Services

Deepgram::Auth::GenerateKey (parts/core/services/deepgram/auth/generate_key.rb)

module Deepgram
  module Auth
    class GenerateKey
      def initialize
        @api_key = Rails.application.credentials.deepgram[:api_key]
        @project_id = Rails.application.credentials.deepgram[:project_id]
      end

      def generate_temporary_key
        # Generate temporary Deepgram API key for client-side use
        response = HTTParty.post(
          "https://api.deepgram.com/v1/projects/#{@project_id}/keys",
          headers: {
            'Authorization' => "Token #{@api_key}",
            'Content-Type' => 'application/json'
          },
          body: {
            name: "Temporary key for session #{SecureRandom.uuid}",
            scopes: ['membership:write'],
            time_to_live_in_seconds: 3600 # 1 hour
          }.to_json
        )

        JSON.parse(response.body)
      end
    end
  end
end

4. Clinic AI Scribe Settings

Models

ClinicAiScribeSetting (parts/clinic/models/clinic_ai_scribe_setting.rb)

class ClinicAiScribeSetting < ApplicationRecord
  belongs_to :clinic

  # Fields
  # - is_enabled: boolean
  # - auto_generate_suggestions: boolean
  # - suggestion_types: json (array of enabled suggestion types)
  # - custom_prompt_template: text
  # - deepgram_settings: json
  # - llm_settings: json
  # - created_at, updated_at

  serialize :suggestion_types, JSON
  serialize :deepgram_settings, JSON
  serialize :llm_settings, JSON
end

Controllers

ClinicAiScribeSettingsController (parts/clinic/controllers/clinic_ai_scribe_settings_controller.rb)

class ClinicAiScribeSettingsController < ApiController
  before_action :policy_check

  # GET /clinic_ai_scribe_settings
  def get_settings
    result = ClinicAiScribeSettings::GetSettings.call(params)
    render_result(result, result.success? ? ClinicAiScribeSettingsSerializer : nil)
  end

  # POST /clinic_ai_scribe_settings
  def save_settings
    result = ClinicAiScribeSettings::SaveSettings.call(params)
    render_result(result, result.success? ? ClinicAiScribeSettingsSerializer : nil)
  end

  private

  def policy_check
    authorize params, policy_class: ClinicAiScribeSettingsControllerPolicy
  end
end

5. Care Plan Integration

CareplanIntegrationService (parts/doctor/services/careplan_integration_service.rb)

class CareplanIntegrationService
  def initialize(doctor, patient, ai_suggestion)
    @doctor = doctor
    @patient = patient
    @ai_suggestion = ai_suggestion
  end

  def integrate_to_careplan
    ActiveRecord::Base.transaction do
      # Process diagnosis
      process_diagnoses

      # Process medications
      process_medications

      # Process lab tests
      process_lab_tests

      # Process follow-up instructions
      process_follow_up

      # Update medical history
      update_medical_history
    end
  end

  private

  def process_diagnoses
    diagnoses = @ai_suggestion.processed_suggestion['diagnoses'] || []
    diagnoses.each do |diagnosis|
      # Create or update diagnosis records
      Diagnosis.create!(
        patient: @patient,
        doctor: @doctor,
        diagnosis_name: diagnosis['name'],
        diagnosis_type: diagnosis['type'],
        confidence: diagnosis['confidence'],
        source: 'ai_suggestion',
        source_id: @ai_suggestion.id
      )
    end
  end

  def process_medications
    medications = @ai_suggestion.processed_suggestion['medications'] || []
    medications.each do |medication|
      # Create prescription records
      Prescription.create!(
        patient: @patient,
        doctor: @doctor,
        medicine_name: medication['name'],
        dosage: medication['dosage'],
        frequency: medication['frequency'],
        duration: medication['duration'],
        source: 'ai_suggestion',
        source_id: @ai_suggestion.id
      )
    end
  end

  def process_lab_tests
    lab_tests = @ai_suggestion.processed_suggestion['lab_tests'] || []
    lab_tests.each do |test|
      # Create lab order records
      LabOrder.create!(
        patient: @patient,
        doctor: @doctor,
        test_name: test['name'],
        test_type: test['type'],
        urgency: test['urgency'] || 'routine',
        source: 'ai_suggestion',
        source_id: @ai_suggestion.id
      )
    end
  end

  def process_follow_up
    follow_up = @ai_suggestion.processed_suggestion['follow_up']
    return unless follow_up

    # Create follow-up appointment or instructions
    FollowUp.create!(
      patient: @patient,
      doctor: @doctor,
      instructions: follow_up['instructions'],
      follow_up_date: follow_up['date'],
      follow_up_type: follow_up['type'],
      source: 'ai_suggestion',
      source_id: @ai_suggestion.id
    )
  end

  def update_medical_history
    history = @ai_suggestion.processed_suggestion['medical_history'] || []
    history.each do |item|
      # Update patient medical history
      MedicalHistory.create!(
        patient: @patient,
        doctor: @doctor,
        condition: item['condition'],
        status: item['status'],
        notes: item['notes'],
        source: 'ai_suggestion',
        source_id: @ai_suggestion.id
      )
    end
  end
end

Database Schema

Migrations

  1. Create Transcription Sessions (20250109120000_create_transcription_sessions.rb)

    create_table :transcription_sessions do |t|
      t.references :doctor, null: false, foreign_key: true
      t.references :clinic, null: false, foreign_key: true
      t.references :appointment, foreign_key: true
      t.string :session_id, null: false
      t.string :deepgram_session_id
      t.enum :status, default: 'init'
      t.json :metadata
      t.timestamps
    end
  2. Create Transcription Segments (20250109120001_create_transcription_segments.rb)

    create_table :transcription_segments do |t|
      t.references :transcription_session, null: false, foreign_key: true
      t.string :segment_id
      t.text :text
      t.integer :speaker
      t.float :start_time
      t.float :end_time
      t.float :confidence_score
      t.boolean :is_final, default: false
      t.timestamps
    end
  3. Create AI Suggestions (20250109120002_create_ai_suggestions.rb)

    create_table :ai_suggestions do |t|
      t.references :appointment, null: false, foreign_key: true
      t.references :transcription_session, foreign_key: true
      t.references :processed_by_user, foreign_key: { to_table: :users }
      t.string :suggestion_type, null: false
      t.text :content, null: false
      t.text :reasoning, null: false
      t.float :confidence_score, null: false
      t.json :metadata
      t.enum :status, default: 'pending'
      t.timestamps
    end
    
    add_index :ai_suggestions, :appointment_id
    add_index :ai_suggestions, :transcription_session_id
    add_index :ai_suggestions, :status
    add_index :ai_suggestions, [:suggestion_type, :status]
  4. Add Soft Delete to AI Suggestions (20250909125536_add_deleted_at_to_ai_suggestions.rb)

    add_column :ai_suggestions, :deleted_at, :datetime
    add_index :ai_suggestions, :deleted_at
  5. Create Clinic AI Scribe Settings (20250109120004_create_clinic_ai_scribe_settings.rb)

    create_table :clinic_ai_scribe_settings do |t|
      t.references :clinic, null: false, foreign_key: true
      t.boolean :is_enabled, default: false
      t.boolean :auto_generate_suggestions, default: true
      t.json :suggestion_types
      t.text :custom_prompt_template
      t.json :deepgram_settings
      t.json :llm_settings
      t.timestamps
    end
  6. Create LLM Call Logs (20250109120003_create_llm_call_logs.rb)

    create_table :llm_call_logs do |t|
      t.string :request_id
      t.string :model
      t.text :prompt
      t.text :response
      t.integer :tokens_used
      t.float :response_time
      t.string :status
      t.text :error_message
      t.references :user, foreign_key: true
      t.timestamps
    end
  7. Additional Migrations

    Make Confidence Score Nullable (20250109104644_make_confidence_score_nullable_in_ai_suggestions.rb)

    change_column :ai_suggestions, :confidence_score, :float, null: true

    Remove Unused Fields (20250109130000_remove_sample_rate_and_speaker_detection_from_transcription_sessions.rb)

    remove_column :transcription_sessions, :sample_rate, :integer
    remove_column :transcription_sessions, :speaker_detection, :boolean

API Endpoints

Routes Structure

The AI Scribe feature uses nested routes under appointments and clinics:

# Doctor Routes
resources :appointments do
  resources :transcription_sessions, only: [:create, :show, :update]
  resources :ai_suggestions, only: [:index, :create, :update]

  member do
    get 'transcription_sessions', to: 'transcription_sessions#index'
    post :generate_ai_suggestions
  end
end

resources :transcription_sessions, only: [:show, :update] do
  member do
    patch :stop
  end
end

resources :ai_suggestions, only: [:show, :update] do
  member do
    patch :accept
    patch :reject
  end
end

# Clinic Routes (for AI Scribe settings)
resources :clinics do
  resources :ai_scribe_settings, only: [:show, :create, :update] do
    collection do
      get :current
    end
  end
end

# Core Routes (for Deepgram authentication)
namespace :ai_scribe do
  resources :issue_token, only: [:create]
end

Transcription Endpoints

MethodEndpointDescriptionAuth Required
POST/api/v1/appointments/:id/transcription_sessionsCreate new transcription sessionYes
GET/api/v1/appointments/:id/transcription_sessionsList sessions for appointmentYes
GET/api/v1/transcription_sessions/:idGet session detailsYes
PATCH/api/v1/transcription_sessions/:idUpdate sessionYes
PATCH/api/v1/transcription_sessions/:id/stopStop transcription sessionYes

AI Suggestions Endpoints

MethodEndpointDescriptionAuth Required
GET/api/v1/appointments/:id/ai_suggestionsList AI suggestions for appointmentYes
POST/api/v1/appointments/:id/ai_suggestionsCreate AI suggestionYes
POST/api/v1/appointments/:id/generate_ai_suggestionsGenerate AI suggestionsYes
GET/api/v1/ai_suggestions/:idGet AI suggestion detailsYes
PATCH/api/v1/ai_suggestions/:idUpdate AI suggestionYes
PATCH/api/v1/ai_suggestions/:id/acceptAccept AI suggestionYes
PATCH/api/v1/ai_suggestions/:id/rejectReject AI suggestionYes

Deepgram Authentication

MethodEndpointDescriptionAuth Required
POST/api/v1/ai_scribe/issue_tokenIssue temporary Deepgram tokenYes

Clinic Settings

MethodEndpointDescriptionAuth Required
GET/api/v1/clinic_ai_scribe_settingsGet clinic AI scribe settingsYes
POST/api/v1/clinic_ai_scribe_settingsSave clinic AI scribe settingsYes

Services and Integrations

1. Ruby LLM Service

RubyLlmService (parts/core/services/ruby_llm_service.rb)

class RubyLlmService
  include Singleton

  attr_reader :chat, :token_encoder

  def initialize
    @config = Rails.application.config.ai_services
    configure_ruby_llm
    @chat = build_chat_client
    @token_encoder = Tiktoken.encoding_for_model(@config[:default_model])
    @rate_limiter = initialize_rate_limiter
  end

  def configure_ruby_llm
    provider = @config[:default_provider].to_sym

    RubyLLM.configure do |config|
      case provider
      when :openai
        config.openai_api_key = @config[:providers][:openai][:api_key]
        config.openai_organization_id = @config[:providers][:openai][:organization]
      when :azure_openai
        azure = @config[:providers][:azure_openai]
        config.openai_api_key = azure[:api_key]
        config.openai_api_base = azure[:endpoint]
      end

      config.request_timeout = @config[:request_timeout_seconds] || 30
      config.max_retries = 3
      config.logger = Rails.logger
    end
  end

  def generate_suggestions(prompt, options = {})
    start_time = Time.current

    begin
      # Check rate limit
      @rate_limiter.check_limit!

      # Build messages
      messages = build_messages(prompt, options)

      # Call LLM
      response = @chat.chat(
        messages: messages,
        model: options[:model] || @config[:default_model],
        temperature: options[:temperature] || 0.3,
        max_tokens: options[:max_tokens] || 2000
      )

      # Log successful call
      log_llm_call(prompt, response, Time.current - start_time, 'success')

      response

    rescue => e
      # Log error
      log_llm_call(prompt, nil, Time.current - start_time, 'error', e.message)
      raise
    end
  end

  private

  def build_messages(prompt, options)
    system_prompt = options[:system_prompt] || default_system_prompt
    [
      { role: 'system', content: system_prompt },
      { role: 'user', content: prompt }
    ]
  end

  def default_system_prompt
    "You are an AI medical assistant helping doctors with clinical documentation. Always provide accurate, evidence-based suggestions."
  end

  def initialize_rate_limiter
    # Rate limiting implementation
  end

  def log_llm_call(prompt, response, duration, status, error = nil)
    LlmCallLog.create!(
      request_id: SecureRandom.uuid,
      model: @config[:default_model],
      prompt: prompt,
      response: response,
      tokens_used: estimate_tokens(prompt + response.to_s),
      response_time: duration,
      status: status,
      error_message: error,
      user: Current.user&.class&.name == 'User' ? Current.user : nil
    )
  end

  def estimate_tokens(text)
    @token_encoder.encode(text).length
  end
end

2. LLM Configuration

Ruby LLM Initializer (config/initializers/ruby_llm.rb)

# Configure Ruby-LLM gem
require 'ruby_llm'

RubyLLM.configure do |config|
  config.openai_api_key = Rails.application.credentials.llm[:openai_api_key]
  config.anthropic_api_key = Rails.application.credentials.llm[:anthropic_api_key]
  config.google_api_key = Rails.application.credentials.llm[:google_api_key]

  # Set default model
  config.default_model = Rails.application.credentials.llm[:default_model] || 'gpt-4'

  # Configure timeout
  config.timeout = 30.seconds

  # Configure retry
  config.max_retries = 3

  # Logging
  config.logger = Rails.logger
end

Background Jobs

Generate AI Suggestions Job

GenerateAiSuggestionsJob (app/jobs/generate_ai_suggestions_job.rb)

class GenerateAiSuggestionsJob < ApplicationJob
  queue_as :ai_suggestions

  def perform(transcription_session_id, doctor_id)
    transcription_session = TranscriptionSession.find(transcription_session_id)
    doctor = Doctor.find(doctor_id)

    # Check if session is complete
    return unless transcription_session.completed?

    # Check if AI scribe is enabled for clinic
    clinic_setting = transcription_session.clinic.clinic_ai_scribe_setting
    return unless clinic_setting&.is_enabled?
    return unless clinic_setting.auto_generate_suggestions

    # Generate suggestions
    service = AiSuggestionService.new(transcription_session, doctor)
    service.generate_suggestions

    # Send notification to doctor
    send_doctor_notification(doctor, transcription_session)
  end

  private

  def send_doctor_notification(doctor, session)
    # Implementation to send notification
  end
end

Credentials

The implementation uses Rails encrypted credentials for storing sensitive API keys:

  • Deepgram API credentials
  • LLM provider API keys (OpenAI, Anthropic, Google)
  • Storage provider credentials

Security Considerations

  1. API Key Management

    • All API keys stored in Rails encrypted credentials
    • Temporary keys generated for client-side use
    • Key rotation support
  2. Data Privacy

    • Audio streams encrypted in transit
    • PII detection and redaction in transcripts
    • HIPAA compliance considerations
  3. Access Control

    • Role-based permissions for AI scribe features
    • Clinic-level settings enforcement
    • Audit logging for all AI interactions
  4. Rate Limiting

    • LLM API call rate limiting
    • Deepgram API quotas management
    • Per-clinic usage limits

Testing Strategy

Unit Tests

  1. Model Tests

    • clinic_ai_scribe_setting_spec.rb
    • Test validations, associations, and serializations
  2. Service Tests

    • careplan_integration_service_spec.rb
    • medical_prompt_builder_spec.rb
    • Test prompt building and care plan integration
  3. Interactor Tests

    • clinic_ai_scribe_settings/get_settings_spec.rb
    • clinic_ai_scribe_settings/save_settings_spec.rb
    • Test business logic

Integration Tests

  1. Controller Tests

    • Test all endpoints with authentication
    • Test authorization policies
    • Test error handling
  2. Service Integration Tests

    • Test Deepgram integration
    • Test LLM integration with mock responses
    • Test background job processing

Test Factories

test_helpers.rb provides factories for testing:

# Example factory usage
let(:clinic) { create(:clinic) }
let(:doctor) { create(:doctor, clinic: clinic) }
let(:transcription_session) { create(:transcription_session, doctor: doctor, clinic: clinic) }
let(:ai_suggestion) { create(:ai_suggestion, transcription_session: transcription_session, doctor: doctor) }

Deployment and Monitoring

Health Checks

  1. Deepgram API Health

    • Check API connectivity
    • Monitor rate limits
    • Track transcription accuracy
  2. LLM Service Health

    • Check provider API status
    • Monitor response times
    • Track token usage
  3. Background Job Monitoring

    • Monitor Sidekiq queue health
    • Track job failure rates
    • Alert on job queue buildup

Metrics to Track

  1. Usage Metrics

    • Number of transcription sessions
    • AI suggestion generation rate
    • Acceptance/rejection rates
  2. Performance Metrics

    • Transcription latency
    • AI suggestion generation time
    • API response times
  3. Quality Metrics

    • Transcription accuracy scores
    • AI suggestion relevance
    • User satisfaction scores

Alerts

  1. Critical Alerts

    • Deepgram API failures
    • LLM service outages
    • High error rates (>5%)
  2. Warning Alerts

    • Low AI suggestion acceptance rates
    • High transcription latency
    • Approaching rate limits

Future Enhancements

  1. Phase 4: Production Optimization

    • Implement caching strategies
    • Optimize LLM prompts
    • Add A/B testing for suggestions
  2. Additional Features

    • Multi-language support
    • Real-time suggestion generation
    • Voice activity detection
    • Speaker diarization improvements
  3. Analytics Dashboard

    • AI scribe usage analytics
    • Suggestion quality metrics
    • Clinical outcome correlations

Conclusion

The AI Scribe implementation provides a comprehensive solution for automating medical transcription and generating AI-powered clinical suggestions. The modular architecture ensures maintainability and extensibility while integrating seamlessly with the existing Kulcare platform. The implementation follows Rails best practices and includes proper security measures, testing strategies, and monitoring capabilities.

On this page