Kulcare Docs
General

Longitudinal Health Record (LHR) — product overview

Audience: product, clinical, support, and engineering leads who need to understand what LHR is, what it does for users, and how the pieces fit together.

Status: Same-tenant LHR views shipped in CMS. Cross-tenant timeline + JIT consent live. Adherence capture and shared-package extraction tracked as follow-ups.

Related: CMS developer guide · Patient-app developer guide · QA guide · Cross-tenant patient experience


What is LHR?

The Longitudinal Health Record is the patient's full health story over time, surfaced in two places:

  • Patient app (patient/ — the standalone Vite + React app): the patient's own view, with self-reported entries (vitals, symptoms, medications) alongside clinician-authored records from every clinic they've been to.
  • CMS (the clinic-facing app at apps/cms/): the clinician's view of the same patient, on the patient detail page and inside an active encounter via a Sheet.

Same data model, same view components, two surfaces. A symptom logged in the patient app shows up to the doctor inside CMS with a "patient-reported" source chip. A prescription written in CMS shows up in the patient app with the prescribing clinic's name.


What the user sees

Patient app

A single Medical Records page with eight tabs: Timeline, Symptoms, Vitals, Problems, Medications, Correlations, Visits, Lab Results. Each row carries a source chip (self-reported / clinical-imported / device / external) and, for clinic-authored rows, the clinic name. There's also a "Manage clinic access" page under Settings that lists every clinic the patient has appeared at and lets them grant or revoke that clinic's access to records from other clinics.

CMS

A "LHR" tab on the patient detail page (the 6th tab) and a "View patient history" Sheet inside the encounter page. Both render the same eight tabs as the patient app, against the same data, with two clinic-only affordances:

  • 3-dot menus on Problems and Medications rows let the clinician link conditions to medications and vice versa.
  • Import-to-encounter on rows from other clinics (gated by patient consent) copies the row into the current tenant's clinical record as clinical_imported provenance.

A doctor at Clinic B can see Clinic A's records about a shared patient only if the patient has granted consent. The flow:

  1. Doctor at Clinic B opens the patient's LHR. Same-tenant data shows immediately. A banner offers to "Request access to records from other clinics".
  2. Doctor clicks request → patient gets a notification (SMS + push) and a banner in the patient app.
  3. Patient grants → consent artifact is created (30-day per-tenant time-boxed), and other-clinic rows appear in Clinic B's LHR.
  4. Patient can revoke any time from the "Manage clinic access" page in the patient app. Already-imported rows persist; future reads are blocked.

The patient can also pre-grant access from "Manage clinic access" before a doctor asks — useful when the patient is referring themselves or wants to share proactively.


How the surfaces fit together

            ┌─────────────────────────────────────────────────────┐
            │           Patient (the actual person)               │
            └────────────────────┬────────────────────────────────┘
                                 │
                  ┌──────────────┴──────────────┐
                  │                             │
            Patient app                     CMS app
            (own records)                   (clinic's view)
                  │                             │
                  ▼                             ▼
        ┌──────────────────┐         ┌───────────────────────┐
        │   patient.*      │         │   clinical.*           │
        │ (user-owned,     │         │  (tenant-scoped, RLS,  │
        │  no RLS)         │         │   FHIR-aligned)        │
        └──────────────────┘         └───────────────────────┘
                  │                             │
                  └─────────────┬───────────────┘
                                │
                                ▼
               Cross-tenant aggregator + consent gate
              (Clinical::Patients::LongitudinalRecordService)

Same React view components render on top of either schema thanks to a backend transformer that emits the patient-app row shape from clinical.* reads.


What's in each tab

TabWhat it showsWhere it comes from
TimelineMixed chronological summary across all resource types and all clinics the patient has visited (where consent exists).Clinical::Patients::LongitudinalRecordService (cross-tenant)
SymptomsSymptom log — patient-reported in the patient app, clinician-captured observations in CMS.patient.symptoms (patient app) or clinical.observations (CMS)
VitalsTrend charts (line + bar) for BP, heart rate, weight, temp, glucose, SpO2, respiratory rate, pain score, BMI. Out-of-reference-range points flagged.patient.vitals or clinical.observations (category=vital_signs)
ProblemsActive conditions with severity badges, ICD code, and linked medications. Resolved conditions collapsed below.patient.conditions or clinical.conditions
MedicationsActive + past medications with dosage, prescriber, and (eventually) adherence.patient.medications or clinical.medication_requests
CorrelationsPlots a symptom against a medication to spot patterns.Derived from Symptoms + Medications
VisitsChronological encounter history with practitioner, facility, reason.clinical.encounters
Lab ResultsLab reports — uploaded by patient or attached by clinic.patient.documents or clinical.documents (lab-typed)

Design principles

Patient owns the data

The patient is the canonical owner of their health story. Clinical-authored rows are also visible to the authoring facility (a doctor needs access to what they wrote), but the patient sees everything. Patient-reported rows are patient-only unless explicitly shared. Cross-tenant reads are time-boxed and revocable.

One surface for both audiences

A symptom shown to the patient and to their doctor renders in the same component. The backend translates clinical.* rows into the patient-app shape so CMS doesn't need duplicate views. This means features added on one surface appear on the other for free.

Source chips are first-class

Every row, every surface, carries a source chip: self-reported, clinical-imported, device, external. Clinicians know what they can trust as primary. Patients can tell what they entered versus what a clinic added.

There's no "share my whole history with everyone" toggle. Consent is per-tenant, time-boxed, and either patient-initiated (pre-grant) or doctor-requested (JIT). This mirrors how ABDM works in India and keeps the privacy story explainable.


What's deliberately out of scope (for now)

  • ABDM linkage: deferred per ADR-002. The data model is compatible when we wire it.
  • Bulk consent: no "share everything with everyone" mode.
  • Adherence capture UI: medications show a "coming soon" affordance where adherence would render. Backend returns adherence_log: nil. Capture flow lives in the patient-app vertical.
  • Write-through from CMS to patient-reported tables: CMS only reads from patient.* (via import-to-encounter, which copies into clinical.*). Patient-reported tables remain patient-only writes.
  • Apple Health / Google Fit ingestion: device source exists in the schema and chips but no ingestion pipeline yet.

Where it lives in code

  • Backend (same-tenant LHR view endpoints): backend/domains/clinical/controllers/patients/lhr_views_controller.rb + transformers under backend/domains/clinical/services/patients/lhr_views/.
  • Backend (cross-tenant aggregator): backend/domains/clinical/services/patients/longitudinal_record_service.rb.
  • Backend (consent): backend/domains/identity/models/cross_tenant_consent.rb + Identity::CrossTenantConsentsController.
  • CMS frontend: frontend/apps/cms/src/features/patientLhr/.
  • Patient app frontend: patient/src/features/health-records/.
  • Shared types/hooks for cross-tenant reads: frontend/packages/shared-services/src/domains/clinical/ (the usePatientLongitudinalTimeline and useCrossTenantConsent hooks).

For implementation details, see the CMS developer guide. For the patient app side, see the patient-app LHR guide. For test plans, see the QA guide.

On this page