Kulcare Docs
DeveloperEncountersAi scribe

AI Scribe — Feature Access & Gating

Audience: Backend / full-stack engineers touching AI Scribe code paths. Companion docs: general/ai-scribe-feature-access.md (product), testing/ai-scribe-qa.md (QA).

Summary

AI Scribe access is gated entirely at the facility level by two boolean columns on identity.facility_ai_settings:

ColumnPurpose
is_enabledMaster toggle for all facility-level AI features
transcription_enabledSpecific capability toggle for AI Scribe transcription sessions

Both must be true for a facility to create transcription sessions. There is no feature flag in this path — ENABLE_AI_SCRIBE was retired because it duplicated is_enabled without adding control.

Resolver

Shared::AiSettingsResolver (backend/domains/shared/services/ai_settings_resolver.rb) is the only entry point for "is AI X allowed for this caller?" questions. Construct one per request:

resolver = Shared::AiSettingsResolver.for(
  facility: encounter.facility,
  practitioner: current_practitioner # optional
)

resolver.ai_enabled?            # master toggle (facility-only)
resolver.transcription_enabled? # AI Scribe gate (facility-only)
resolver.suggestions_enabled?   # AI suggestions (facility-only)
resolver.confidence_threshold   # practitioner-overridable
resolver.language               # practitioner-overridable

Identity::FacilityAiSettings#ai_enabled? exists for places that already hold the row (blueprints, internal helpers). Anything that needs to consider practitioner overrides must go through the resolver.

Practitioner overrides — narrow by design

Identity::PractitionerAiSettings exists to let individual clinicians tune workflow preferences. It does not give a practitioner the power to flip facility-level gates.

PRACTITIONER_OVERRIDABLE_KEYS (in the resolver) is the single source of truth:

PRACTITIONER_OVERRIDABLE_KEYS = %i[
  language medical_terminology_enabled confidence_threshold
].freeze

Anything outside that list — is_enabled, transcription_enabled, suggestions_enabled, auto_documentation_enabled, ai_provider, ai_model, retention/limit columns — is read straight from facility settings, even if the practitioner row has a non-null value. If you need to add a new overridable key, update this constant and the doc here in the same PR.

Lifecycle

  • Identity::Facility runs after_create :build_default_ai_settings, which calls Identity::FacilityAiSettings.find_or_create_for_facility!. Every facility created from now on starts with a settings row (defaults: everything off).

  • Facilities created before this callback existed may not have a row. The resolver tolerates a missing row (treats every key as nil/default), but you should backfill via:

    Identity::Facility.find_each { |f| Identity::FacilityAiSettings.find_or_create_for_facility!(f) }
  • The FacilityAiSettingsController (GET / PATCH /identity/facilities/:facility_id/ai_settings) is what clinic admins call to flip the toggles. Auth is enforced via FacilityAiSettingsPolicy: tenant_owner?, tenant_admin?, or any membership with the manage_facilities permission. (An earlier version also accepted a "facility-level practitioner admin" via practitioner_roles.role — that column never existed on the new schema and the branch was dropped, see commit b44e4712b.)

  • The corresponding CMS admin surface is Settings → Practice Settings → AI Scribe (apps/cms/src/features/settings/practiceSettings/components/tabs/AiScribeTab.tsx), which renders the master toggle + per-capability switches.

Controller gate

Clinical::TranscriptionSessionsController#ai_scribe_enabled? is the runtime check:

def ai_scribe_enabled?
  encounter = Clinical::Encounter.find_by(id: session_params[:encounter_id] || @session&.encounter_id)
  return false unless encounter&.facility

  Shared::AiSettingsResolver.for(facility: encounter.facility).transcription_enabled?
end

When this returns false, the API responds with 403 ai_scribe_disabled. That error is config-driven, not a bug — see the QA guide for how to enable a facility.

Why two flags and not three (or one)

  • is_enabled is the billing/entitlement toggle. Off = AI bundle not active for this facility, no AI features anywhere.
  • transcription_enabled / suggestions_enabled / auto_documentation_enabled let an admin keep the bundle on but disable individual capabilities (e.g., transcription on, auto-doc off).
  • A feature-flag layer on top would only duplicate is_enabled — engineering can already toggle a single row when needed. We dropped it.

If we ever need an engineering-only kill switch (e.g., shut off AI globally during an incident), introduce that as a global feature flag (see Feature Flags), not per-facility.

Testing

See testing/ai-scribe-qa.md for the dev/staging enablement recipe and end-to-end checks.

On this page