Kulcare Docs
General

Federated Deployment Architecture

Status: Analysis & Design Phase Created: 2025-12-29 Purpose: Database schema additions to support dual deployment model (Cloud SaaS + On-Premise)

Executive Summary

This document describes the database schema additions needed to support a federated deployment model for Kulcare, enabling:

  1. Cloud SaaS - Existing multi-tenant architecture (unchanged)
  2. On-Premise - Local app + database installed at hospital sites with outbound sync to central cloud

Key outcomes:

  • Hospitals get the reliability of local infrastructure
  • Patients get portable health records they control
  • Kulcare retains IP, maintains relationship with all patients, and delivers premium cloud services

Software Architecture and Code Reuse

The Single Codebase Philosophy

The federated deployment model is built on a single codebase philosophy: the same Ruby on Rails application powers both Cloud SaaS and On-Premise deployments. This approach maximizes code reuse, minimizes maintenance burden, and ensures feature parity where applicable.

+------------------------------------------------------------------+
|                    KULCARE RAILS APPLICATION                      |
|                       (Single Codebase)                           |
+------------------------------------------------------------------+
|                                                                   |
|  +--------------------+  +--------------------+  +--------------+ |
|  |   Core Modules     |  |  Optional Modules  |  | Sync Layer   | |
|  +--------------------+  +--------------------+  +--------------+ |
|  | - Identity         |  | - Growth           |  | - Outbound   | |
|  | - Clinical         |  | - Connect          |  | - Inbound    | |
|  | - Billing (basic)  |  | - Enterprise       |  | - Queue Mgmt | |
|  | - Integration      |  | - Analytics        |  +--------------+ |
|  | - Audit            |  | - AI Services      |                   |
|  +--------------------+  +--------------------+                   |
|                                                                   |
+------------------------------------------------------------------+
        |                           |
        v                           v
+------------------+        +------------------+
|  ON-PREMISE      |        |  CLOUD SAAS      |
|  Configuration   |        |  Configuration   |
+------------------+        +------------------+
| DEPLOYMENT_MODE= |        | DEPLOYMENT_MODE= |
|   on_premise     |        |   cloud          |
| SYNC_ENABLED=    |        | MULTI_TENANT=    |
|   true           |        |   true           |
| TENANT_ID=       |        | FEDERATION=      |
|   (single)       |        |   enabled        |
+------------------+        +------------------+

How Code Reuse Works

1. Environment-Based Configuration

The application behavior is controlled by environment variables and configuration files, not code branches:

# config/deployment.rb
module Kulcare
  class Deployment
    def self.mode
      ENV.fetch('DEPLOYMENT_MODE', 'cloud') # 'cloud' or 'on_premise'
    end

    def self.cloud?
      mode == 'cloud'
    end

    def self.on_premise?
      mode == 'on_premise'
    end

    def self.multi_tenant?
      cloud? && ENV.fetch('MULTI_TENANT', 'true') == 'true'
    end

    def self.sync_enabled?
      on_premise? && ENV.fetch('SYNC_ENABLED', 'true') == 'true'
    end
  end
end

2. Conditional Module Loading

Optional modules are loaded based on deployment mode:

# config/application.rb
class Application < Rails::Application
  config.after_initialize do
    if Kulcare::Deployment.cloud?
      # Load cloud-only modules
      require_dependency 'parts/growth'
      require_dependency 'parts/connect'
      require_dependency 'parts/enterprise'
      require_dependency 'parts/federation'  # Central cloud federation receiver
    end

    if Kulcare::Deployment.on_premise?
      # Load on-premise sync module
      require_dependency 'parts/sync'
    end
  end
end

3. Feature Flag Integration

Features are controlled through a unified feature flag system that respects deployment mode:

# app/services/feature_flags.rb
class FeatureFlags
  def self.enabled?(feature_key, facility: nil)
    if Kulcare::Deployment.on_premise?
      # Check local feature flags table
      LocalFeatureFlag.enabled?(feature_key)
    else
      # Check FeatureConfiguration with optional facility override
      FeatureConfiguration.enabled_for?(feature_key, facility)
    end
  end
end

What Ships to On-Premise

When deploying to an on-premise installation, the following components are included:

ComponentIncludedNotes
Rails ApplicationYesSame codebase, encrypted via RubyEncoder
Core Parts (Identity, Clinical, Billing, Audit)YesFull functionality
Integration Parts (ABDM)YesCountry-specific
Growth PartsNoCloud-only marketing features
Connect PartsNoCloud-only professional network
Enterprise PartsNoCloud-only employer benefits
Federation ReceiverNoCloud-only (receives sync data)
Sync SenderYesOn-premise only (sends sync data)
Frontend AppsYesCMS app included, Growth/Connect excluded

Source Code Protection

On-premise deployments use RubyEncoder to protect Kulcare intellectual property:

+------------------------------------------------------------------+
|                  ON-PREMISE DEPLOYMENT PACKAGE                    |
+------------------------------------------------------------------+
|                                                                   |
|  +------------------------+    +------------------------+         |
|  | Encrypted Ruby Files   |    | Configuration Files    |         |
|  | (.rbe extension)       |    | (plain text, editable) |         |
|  +------------------------+    +------------------------+         |
|  | - app/**/*.rbe         |    | - config/database.yml  |         |
|  | - lib/**/*.rbe         |    | - config/settings.yml  |         |
|  | - parts/**/*.rbe       |    | - .env                 |         |
|  +------------------------+    +------------------------+         |
|                                                                   |
|  +------------------------+    +------------------------+         |
|  | RubyEncoder Loader     |    | Database Migrations    |         |
|  +------------------------+    +------------------------+         |
|  | Decrypts at runtime    |    | Plain SQL (necessary   |         |
|  | Hardware-locked key    |    | for schema setup)      |         |
|  +------------------------+    +------------------------+         |
|                                                                   |
+------------------------------------------------------------------+

Protection Strategy:

  • All Ruby source files encrypted with RubyEncoder
  • Decryption keys hardware-locked to specific servers
  • License validation on startup
  • Configuration files remain editable for local customization
  • Database migrations remain readable for transparency

On-Premise Schema: A Subset of Cloud Schema

Schema Relationship

The on-premise database schema is a strict subset of the cloud schema, with specific exclusions and additions for sync functionality.

+------------------------------------------------------------------+
|                      CLOUD SCHEMA (Complete)                      |
+------------------------------------------------------------------+
|                                                                   |
|  INCLUDED IN ON-PREMISE          CLOUD-ONLY                       |
|  =======================         ==========                       |
|                                                                   |
|  [reference]                     [growth]                         |
|    - specialties                   - campaigns                    |
|    - drugs                         - leads                        |
|    - lab_tests                     - communications               |
|    - procedures                                                   |
|    - icd_codes                   [connect]                        |
|                                    - professional_profiles        |
|  [identity]                        - connections                  |
|    - tenants (single)              - job_postings                 |
|    - organizations (single)        - messages                     |
|    - facilities                                                   |
|    - users                       [enterprise]                     |
|    - practitioners                 - employers                    |
|    - practitioner_roles            - benefit_plans                |
|    - healthcare_services           - empaneled_providers          |
|    - vendors                                                      |
|    - service_packages            [federation]                     |
|                                    - federated_facilities         |
|  [clinical]                        - master_patient_index         |
|    - patients                      - patient_identity_links       |
|    - patient_facilities            - inbound_sync_records         |
|    - episodes_of_care              - federated_clinical_data      |
|    - encounters                    - (all 12 tables)              |
|    - conditions                                                   |
|    - observations                                                 |
|    - medication_requests                                          |
|    - care_plans                                                   |
|    - goals                                                        |
|    - (all clinical tables)                                        |
|                                                                   |
|  [billing]                                                        |
|    - invoices                                                     |
|    - invoice_items                                                |
|    - payments                                                     |
|    - (basic billing tables)                                       |
|                                                                   |
|  [integration]                                                    |
|    - abdm_records                                                 |
|    - webhook_events                                               |
|    - api_logs                                                     |
|                                                                   |
|  [audit]                                                          |
|    - activity_logs                                                |
|    - phi_access_logs                                              |
|    - llm_call_logs                                                |
|    - document_audit_logs                                          |
|                                                                   |
|  [sync] (ON-PREMISE ONLY)                                         |
|    - sync_configuration                                           |
|    - outbound_sync_queue                                          |
|    - inbound_appointment_queue                                    |
|    - local_feature_flags                                          |
|                                                                   |
+------------------------------------------------------------------+

Key Schema Differences

AspectCloud SaaSOn-Premise
TenancyMulti-tenant with tenant_id on all tablesSingle-tenant, no tenant_id columns
RLS PoliciesEnabled for tenant isolationDisabled (no multi-tenancy)
Growth SchemaFull marketing/CRM functionalityNot included
Connect SchemaProfessional networkingNot included
Enterprise SchemaEmployer benefits, empanelmentNot included
Federation SchemaReceives sync from on-premiseNot included
Sync SchemaNot includedSends data to cloud
Offline SchemaFor client-side offlineIncluded (same functionality)

Multi-Tenancy Removal for On-Premise

In cloud deployments, every table has a tenant_id column and RLS policies enforce tenant isolation. For on-premise, these are removed:

Cloud Schema (Multi-Tenant):

CREATE TABLE clinical.patients (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES identity.tenants(id),  -- REMOVED
    first_name VARCHAR(100),
    last_name VARCHAR(100),
    -- ...
);

CREATE POLICY tenant_isolation ON clinical.patients
    USING (tenant_id = current_setting('app.current_tenant_id')::uuid);  -- REMOVED

On-Premise Schema (Single-Tenant):

CREATE TABLE clinical.patients (
    id UUID PRIMARY KEY,
    -- No tenant_id column
    first_name VARCHAR(100),
    last_name VARCHAR(100),
    -- ...
);

-- No RLS policies

Migration Strategy for Schema Differences

The same migration files work for both deployments using conditional logic:

# db/migrate/20251229000001_create_patients.rb
class CreatePatients < ActiveRecord::Migration[7.0]
  def change
    create_table :patients, id: :uuid do |t|
      # Only add tenant_id in cloud deployments
      if Kulcare::Deployment.cloud?
        t.uuid :tenant_id, null: false
        t.index :tenant_id
      end

      t.string :first_name, limit: 100
      t.string :last_name, limit: 100
      # ...
    end

    # Only add RLS in cloud deployments
    if Kulcare::Deployment.cloud?
      execute <<-SQL
        ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
        CREATE POLICY tenant_isolation ON patients
          USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
      SQL
    end
  end
end

Architecture Overview

+---------------------------+          +---------------------------+
|   ON-PREMISE (Hospital)   |          |   KULCARE CENTRAL CLOUD   |
+---------------------------+          +---------------------------+
|                           |          |                           |
|  Local Rails App          |  Sync    |  Full SaaS Platform       |
|  Local PostgreSQL DB      | -------> |  Central PostgreSQL DB    |
|  (Single-Organization)    | (outbound)|  (Multi-tenant)          |
|                           |  + appts |                           |
|  - Encounters             |          |  - Master Patient Index   |
|  - Prescriptions          |          |  - Federated Clinical Data|
|  - Vitals                 |          |  - Patient Portal         |
|  - Documents              |          |  - Premium AI Services    |
|  - Multi-facility         |          |  - Analytics              |
+---------------------------+          +---------------------------+

Key Design Decisions

1. Sync Direction

Data TypeDirectionRationale
Clinical dataOutbound onlyOn-premise is source of truth
Patient demographicsOutbound onlyOn-premise is source of truth
Available appointment slotsOutboundCloud needs for booking UI
New online bookingsInboundPortal-initiated bookings
Booking confirmationsOutboundAfter on-premise confirms

2. Patient Identity

  • Master Patient Index (MPI) on central cloud aggregates patient identities
  • Matching via ABHA ID (India), phone+DOB+name, or fuzzy matching
  • Patients can create Kulcare accounts to access unified health record
  • Data from all facilities (cloud and on-premise) aggregated in portal

3. Multi-Facility On-Premise

  • Hospital groups can have multiple facilities in one on-premise installation
  • Single organization, multiple facilities
  • Patients can be seen across facilities within the installation
  • All facilities sync to central cloud

4. Feature Availability

FeatureCloudOn-PremiseCloud-Proxied
Patient QueueYesYesNo
PrescriptionsYesYesNo
Online BookingYesYesYes
Patient PortalYesYes (read-only)Yes
SMS/WhatsAppYesNoYes
AI ScribeYesNoYes
AnalyticsYesNoYes
Payment GatewayYesNoNo
Growth ToolsYesNoNo

Schema Additions

Central Cloud: New federation Schema

12 new tables in the federation schema:

EntityPurpose
FederatedFacilityRegistry of on-premise installations
FacilitySyncCredentialsAPI authentication for sync
MasterPatientIndexGlobal patient identity (cross-facility)
PatientIdentityLinkLinks facility patient IDs to MPI
InboundSyncRecordTracks all incoming sync operations
SyncConflictResolutionConflict resolution audit trail
FederatedClinicalDataAggregated clinical data from all facilities
SyncDataProvenanceData origin tracking
FeatureConfigurationFeature availability matrix
FacilityFeatureOverridePer-facility feature overrides
AppointmentSlotsAvailable slots synced from on-premise
AppointmentBookingsBookings made via portal

See Federation Schema for detailed entity definitions.

On-Premise Database: New Tables

4 new tables for sync management:

EntityPurpose
SyncConfigurationSync settings and credentials
OutboundSyncQueueQueue of records pending sync
InboundAppointmentQueueIncoming booking requests from cloud
LocalFeatureFlagsLocal feature availability

On-Premise Simplifications

Compared to the cloud multi-tenant schema:

  • Remove tenant_id columns (single-organization)
  • Remove RLS policies (no multi-tenancy)
  • Remove unused schemas: growth, connect, enterprise
  • Facility-level access control instead of tenant-level
  • Multi-facility supported within single installation

Sync Protocol

Outbound Sync (On-Premise to Cloud)

On-Premise                              Central Cloud
-----------                             -------------
1. Record created/updated
   |
2. DB trigger adds to OutboundSyncQueue
   |
3. Sync worker (scheduled, every N minutes)
   |
4. Batch records by type, sign payload
   |
5. POST to cloud sync endpoint -------> 6. Authenticate facility
                                        7. Validate payload signature
                                        8. Process each record:
                                           - Match patient identity (MPI)
                                           - Check for conflicts
                                           - Store in FederatedClinicalData
                                           - Track provenance
                                        9. Return acknowledgment
   |
10. Mark records as acknowledged
11. Update last_sync_success_at

Payload Structure

{
  "facility_code": "FAC-12345",
  "batch_id": "batch-uuid-123",
  "timestamp": "2025-12-29T10:30:00Z",
  "signature": "base64-signature",
  "records": [
    {
      "operation": "create",
      "resource_type": "patient",
      "facility_resource_id": "local-patient-123",
      "facility_timestamp": "2025-12-29T10:00:00Z",
      "data": {
        "first_name": "John",
        "last_name": "Doe",
        "date_of_birth": "1985-03-15",
        "gender": "male",
        "phone": "+919876543210",
        "abha_id": "12-3456-7890-1234"
      }
    },
    {
      "operation": "create",
      "resource_type": "encounter",
      "facility_resource_id": "local-encounter-456",
      "facility_patient_id": "local-patient-123",
      "facility_timestamp": "2025-12-29T10:15:00Z",
      "data": {
        "encounter_type": "consultation",
        "encounter_date": "2025-12-29",
        "practitioner_name": "Dr. Smith",
        "chief_complaint": "Fever, cough",
        "diagnosis": [
          { "code": "J06.9", "display": "Acute upper respiratory infection" }
        ]
      }
    }
  ]
}

Response Structure

{
  "batch_id": "batch-uuid-123",
  "status": "partial",
  "processed_at": "2025-12-29T10:30:05Z",
  "results": [
    {
      "facility_resource_id": "local-patient-123",
      "status": "synced",
      "cloud_resource_id": "mpi-uuid-abc",
      "identity_action": "linked_existing"
    },
    {
      "facility_resource_id": "local-encounter-456",
      "status": "synced",
      "cloud_resource_id": "fed-data-uuid-xyz"
    }
  ],
  "errors": []
}

Cloud-Proxied Online Booking

Since on-premise facilities want online booking, we enable it via cloud proxy:

Booking Flow

1. On-premise syncs practitioner schedules/available slots to cloud
   |
2. Patient visits Kulcare portal
   |
3. Patient selects facility, practitioner, available slot
   |
4. Cloud creates "pending" appointment in AppointmentBookings
   |
5. Cloud pushes booking request to on-premise
   |
6. On-premise checks slot availability (source of truth)
   |
   +-- If available: Confirms appointment, sends notification
   |
   +-- If conflict: Rejects, patient notified to choose another slot
   |
7. On-premise syncs confirmation/rejection back to cloud

Conflict Handling

  • On-premise is source of truth for slot availability
  • If a slot is taken locally before cloud booking syncs, cloud booking is rejected
  • Patient receives notification to select another slot
  • No double-booking possible since on-premise confirms

Patient Identity Federation

Master Patient Index (MPI)

The MPI provides a global patient identity across all facilities:

+------------------+       +---------------------+
| Master Patient   |       | Patient Identity    |
| Index (MPI)      |<------| Links               |
+------------------+       +---------------------+
| id (UUID)        |       | master_patient_id   |
| abha_id (unique) |       | federated_facility_id|
| phone_hash       |       | facility_patient_id |
| email_hash       |       | facility_mrn        |
| first_name       |       | match_method        |
| last_name        |       | match_confidence    |
| date_of_birth    |       | verified            |
| gender           |       +---------------------+
| facility_count   |
| primary_facility |
+------------------+

Matching Algorithm

When a patient record arrives from on-premise:

1. Extract identifiers:
   - ABHA ID (deterministic match if present)
   - Phone + DOB + Name (strong match)
   - Phone + Name (moderate match)
   - Name + DOB + Gender (weak match)

2. If ABHA ID exists and found in MPI:
   - Link to existing MPI record
   - match_method = "abha"

3. If no ABHA ID, search by phone_hash:
   - If exact match with DOB + name: Link (match_method = "exact")
   - If fuzzy match: Link but flag for verification (match_method = "fuzzy")

4. If no match found:
   - Create new MPI record
   - Create PatientIdentityLink

5. Store PatientIdentityLink with confidence score

Match Methods

MethodCriteriaConfidenceVerification
abhaABHA ID match1.0Not required
exactPhone + DOB + Name0.95Not required
fuzzyPhone + similar name0.7Required
manualHuman verified1.0Already verified

Patient Portal Integration

Account Creation Flow

1. Patient visits on-premise facility (or any facility)
   |
2. Clinical data syncs to central cloud
   |
3. Patient creates Kulcare account (phone/email verification)
   |
4. System matches account to MPI record
   |
5. Patient sees unified health record from ALL facilities

Portal Capabilities for On-Premise Patients

  • View encounters, prescriptions, vitals, lab results from all facilities
  • Download health records
  • Share records with other providers
  • View upcoming appointments
  • Book online via cloud-proxied booking
  • Request data export/deletion per DPDP/GDPR
  • Cannot: Update clinical data (read-only view)

Data Ownership

  • Clinical data owned by patient
  • On-premise facility retains operational copy
  • Central cloud aggregates for patient portal
  • Patient can request data export or deletion

Relationship to Offline Mode

The Offline Mode schema handles client-side offline (browser/app loses connectivity temporarily).

This federated schema handles facility-level sync (separate on-premise database syncing to central cloud continuously).

Reusable Patterns

  • Version tracking columns for conflict detection
  • Sync status enums (pending, processing, synced, conflict, failed)
  • Batch-based sync with retry logic
  • Conflict resolution strategies

Key Differences

AspectOffline ModeFederated Deployment
DatabaseSame databaseSeparate databases
DurationTemporary (hours)Continuous
DirectionBi-directionalOutbound + appointments inbound
ScopeClient deviceEntire facility

Security Considerations

Data in Transit

  • All sync traffic over HTTPS with TLS 1.3
  • Payloads signed with RSA keys
  • API key + secret authentication
  • IP allowlisting optional

Data at Rest

LocationDataEncryption
On-premiseFull PHIEncrypted at rest
Central cloudAggregated clinical dataEncrypted at rest
MPIHashed identifiers onlyEncrypted at rest

Access Control

DeploymentAccess Model
On-premiseStandard RBAC (no tenant isolation needed)
Central cloudFacility-based access for admin
Patient portalPatient authenticates, sees data from ALL their facilities

Audit Trail

  • All sync operations logged in InboundSyncRecord
  • Provenance tracking via SyncDataProvenance
  • Conflict resolutions audited in SyncConflictResolution
  • Standard PHI access logging applies

Compliance Considerations

HIPAA (USA)

  • PHI transmitted securely (encrypted in transit)
  • Access logging for all data access
  • Business Associate Agreement with on-premise customers

DPDP (India)

  • Patient consent for data sync (obtained at facility)
  • Data retention limits enforced
  • Right to erasure supported via MPI

Source Code Protection

  • On-premise Ruby source encrypted via RubyEncoder
  • Protects Kulcare intellectual property
  • Not a database schema concern

Entity Summary

Central Cloud (federation schema)

EntityRLSPurpose
FederatedFacilityNoOn-premise registry
FacilitySyncCredentialsNoSync authentication
MasterPatientIndexNoGlobal patient identity
PatientIdentityLinkNoFacility-MPI mapping
InboundSyncRecordNoSync tracking
SyncConflictResolutionNoConflict audit
FederatedClinicalDataNoAggregated clinical data
SyncDataProvenanceNoData origin tracking
FeatureConfigurationNoFeature matrix
FacilityFeatureOverrideNoPer-facility overrides
AppointmentSlotsNoAvailable slots from on-premise
AppointmentBookingsNoPortal bookings

Note: Federation schema tables do NOT use RLS. Access control is facility-based, not tenant-based.

On-Premise

EntityPurpose
SyncConfigurationSync settings
OutboundSyncQueuePending sync queue
InboundAppointmentQueueIncoming bookings
LocalFeatureFlagsFeature availability

On this page