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:
- Cloud SaaS - Existing multi-tenant architecture (unchanged)
- 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
end2. 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
end3. 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
endWhat Ships to On-Premise
When deploying to an on-premise installation, the following components are included:
| Component | Included | Notes |
|---|---|---|
| Rails Application | Yes | Same codebase, encrypted via RubyEncoder |
| Core Parts (Identity, Clinical, Billing, Audit) | Yes | Full functionality |
| Integration Parts (ABDM) | Yes | Country-specific |
| Growth Parts | No | Cloud-only marketing features |
| Connect Parts | No | Cloud-only professional network |
| Enterprise Parts | No | Cloud-only employer benefits |
| Federation Receiver | No | Cloud-only (receives sync data) |
| Sync Sender | Yes | On-premise only (sends sync data) |
| Frontend Apps | Yes | CMS 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
| Aspect | Cloud SaaS | On-Premise |
|---|---|---|
| Tenancy | Multi-tenant with tenant_id on all tables | Single-tenant, no tenant_id columns |
| RLS Policies | Enabled for tenant isolation | Disabled (no multi-tenancy) |
| Growth Schema | Full marketing/CRM functionality | Not included |
| Connect Schema | Professional networking | Not included |
| Enterprise Schema | Employer benefits, empanelment | Not included |
| Federation Schema | Receives sync from on-premise | Not included |
| Sync Schema | Not included | Sends data to cloud |
| Offline Schema | For client-side offline | Included (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); -- REMOVEDOn-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 policiesMigration 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
endArchitecture 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 Type | Direction | Rationale |
|---|---|---|
| Clinical data | Outbound only | On-premise is source of truth |
| Patient demographics | Outbound only | On-premise is source of truth |
| Available appointment slots | Outbound | Cloud needs for booking UI |
| New online bookings | Inbound | Portal-initiated bookings |
| Booking confirmations | Outbound | After 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
| Feature | Cloud | On-Premise | Cloud-Proxied |
|---|---|---|---|
| Patient Queue | Yes | Yes | No |
| Prescriptions | Yes | Yes | No |
| Online Booking | Yes | Yes | Yes |
| Patient Portal | Yes | Yes (read-only) | Yes |
| SMS/WhatsApp | Yes | No | Yes |
| AI Scribe | Yes | No | Yes |
| Analytics | Yes | No | Yes |
| Payment Gateway | Yes | No | No |
| Growth Tools | Yes | No | No |
Schema Additions
Central Cloud: New federation Schema
12 new tables in the federation schema:
| Entity | Purpose |
|---|---|
| FederatedFacility | Registry of on-premise installations |
| FacilitySyncCredentials | API authentication for sync |
| MasterPatientIndex | Global patient identity (cross-facility) |
| PatientIdentityLink | Links facility patient IDs to MPI |
| InboundSyncRecord | Tracks all incoming sync operations |
| SyncConflictResolution | Conflict resolution audit trail |
| FederatedClinicalData | Aggregated clinical data from all facilities |
| SyncDataProvenance | Data origin tracking |
| FeatureConfiguration | Feature availability matrix |
| FacilityFeatureOverride | Per-facility feature overrides |
| AppointmentSlots | Available slots synced from on-premise |
| AppointmentBookings | Bookings made via portal |
See Federation Schema for detailed entity definitions.
On-Premise Database: New Tables
4 new tables for sync management:
| Entity | Purpose |
|---|---|
| SyncConfiguration | Sync settings and credentials |
| OutboundSyncQueue | Queue of records pending sync |
| InboundAppointmentQueue | Incoming booking requests from cloud |
| LocalFeatureFlags | Local feature availability |
On-Premise Simplifications
Compared to the cloud multi-tenant schema:
- Remove
tenant_idcolumns (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_atPayload 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 cloudConflict 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 scoreMatch Methods
| Method | Criteria | Confidence | Verification |
|---|---|---|---|
abha | ABHA ID match | 1.0 | Not required |
exact | Phone + DOB + Name | 0.95 | Not required |
fuzzy | Phone + similar name | 0.7 | Required |
manual | Human verified | 1.0 | Already 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 facilitiesPortal 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
| Aspect | Offline Mode | Federated Deployment |
|---|---|---|
| Database | Same database | Separate databases |
| Duration | Temporary (hours) | Continuous |
| Direction | Bi-directional | Outbound + appointments inbound |
| Scope | Client device | Entire 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
| Location | Data | Encryption |
|---|---|---|
| On-premise | Full PHI | Encrypted at rest |
| Central cloud | Aggregated clinical data | Encrypted at rest |
| MPI | Hashed identifiers only | Encrypted at rest |
Access Control
| Deployment | Access Model |
|---|---|
| On-premise | Standard RBAC (no tenant isolation needed) |
| Central cloud | Facility-based access for admin |
| Patient portal | Patient 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)
| Entity | RLS | Purpose |
|---|---|---|
| FederatedFacility | No | On-premise registry |
| FacilitySyncCredentials | No | Sync authentication |
| MasterPatientIndex | No | Global patient identity |
| PatientIdentityLink | No | Facility-MPI mapping |
| InboundSyncRecord | No | Sync tracking |
| SyncConflictResolution | No | Conflict audit |
| FederatedClinicalData | No | Aggregated clinical data |
| SyncDataProvenance | No | Data origin tracking |
| FeatureConfiguration | No | Feature matrix |
| FacilityFeatureOverride | No | Per-facility overrides |
| AppointmentSlots | No | Available slots from on-premise |
| AppointmentBookings | No | Portal bookings |
Note: Federation schema tables do NOT use RLS. Access control is facility-based, not tenant-based.
On-Premise
| Entity | Purpose |
|---|---|
| SyncConfiguration | Sync settings |
| OutboundSyncQueue | Pending sync queue |
| InboundAppointmentQueue | Incoming bookings |
| LocalFeatureFlags | Feature availability |
Related Documentation
- Federation Schema - Detailed entity definitions
- Offline Mode - Client-side offline sync
- Schema Overview - Overall architecture
- Architectural Decisions - ADR-015 for federation