Kulcare Docs
DeveloperEncounters

Immunization Record Feature

Status: ✅ Complete Stream: Clinical — Pediatric Specialty Module Files: frontend/apps/cms/src/features/encounter/types/pediatricTypes.ts, frontend/apps/cms/src/features/encounter/components/tabs/ImmunizationRecordTab.tsx

Overview

The Immunization Record feature provides comprehensive pediatric vaccination tracking within the CMS encounter module. It supports recording individual vaccinations, loading the Indian Universal Immunization Programme (UIP) schedule, and tracking dose completion across multi-dose vaccine series.

Key Features

  • Comprehensive clinical documentation: Records lot number, expiration, manufacturer, dose number, route, site, provider, and adverse reactions for each vaccination
  • Standard schedule loading: One-click import of the 31-vaccine Indian UIP schedule with age-calculated due dates
  • Status tracking: Automatic status computation (Given, Due, Overdue, Upcoming, Missed) based on dates
  • Full CRUD operations: Add, edit, delete individual vaccination records
  • Due date calculation: Uses patient's birth date to compute personalized vaccination schedules
  • Summary dashboard: At-a-glance count of vaccinations by status

Data Model

ImmunizationRecordData

The entire immunization record is stored as a single JSON blob observation in the FHIR immunization_record category.

// frontend/apps/cms/src/features/encounter/types/pediatricTypes.ts

interface ImmunizationRecordData {
  vaccinations: VaccinationEntry[]  // List of all vaccination records
  generalNotes?: string            // Encounter-level immunization notes
}

interface VaccinationEntry {
  id: string                       // Unique identifier (client-generated)
  name: string                     // Vaccine name (e.g., "DPT-1", "BCG")
  ageRange: string                 // Schedule label (e.g., "Birth", "6 weeks")
  doseNumber?: DoseNumber           // Position in series: '0' | '1' | '2' | '3' | '4' | '5' | 'booster'
  dueDate?: string                 // YYYY-MM-DD — scheduled administration date
  givenDate?: string               // YYYY-MM-DD — actual administration date
  status: ImmunizationStatus       // 'given' | 'due' | 'overdue' | 'upcoming' | 'missed'
  // Clinical documentation fields
  lotNumber?: string               // Vaccine batch/lot number (for recall tracing)
  expirationDate?: string          // YYYY-MM-DD — vaccine expiration date
  manufacturer?: string            // Vaccine manufacturer (e.g., "Serum Institute")
  administeredBy?: string          // Name of administering healthcare provider
  administrationSite?: AdministrationSite  // Anatomical site of injection
  administrationRoute?: AdministrationRoute // IM, SC, ID, oral, nasal, other
  adverseReaction?: AdverseReaction        // none | mild | moderate | severe | anaphylaxis
  notes?: string                   // Free-text notes for this vaccination
}

Status Semantics

StatusMeaningComputation Rule
givenAdministeredgivenDate is set
dueScheduled and within windowgivenDate not set, dueDate is today or future
overduePast due dategivenDate not set, dueDate is past
upcomingNo due date setgivenDate not set, dueDate not set
missedPast due without administrationReserved for future auto-computation

Data Flow

Fetch Path

Patient encounter opened
  │
  ▼
useEncounterClinicalData (hooks/useEncounterClinicalData.ts)
  │
  ├─► useObservations({ encounter_id })
  │     └─► API: GET /clinical/observations?encounter_id=...
  │
  ▼
obsByCategory.get('immunization_record')
  │
  ▼
parseImmunizationRecord()  (hooks/clinicalDataParsers.ts)
  │   └─► JSON.parse(observation.value) → ImmunizationRecordData
  │
  ▼
immunizationRecord passed to ImmunizationRecordTab as `record` prop

Save Path

User modifies vaccination in ImmunizationRecordTab
  │
  ▼
onRecordChange({ ...record, vaccinations: [...] })
  │
  ▼
EncounterPage: setClinicalData(prev => ({ ...prev, immunizationRecord }))
  │
  ▼
User clicks Save / auto-save triggers
  │
  ▼
prepareClinicalDataForSave(data)
  │
  ├─► extracts immunizationRecord
  ├─► checks hasContent: vaccinations.length > 0 OR generalNotes exists
  │
  ├─► [Has content] → builds batch save payload:
  │     observations['immunization_record'] = [{
  │       code: { display: 'Immunization Record', text: 'Immunization Record' },
  │       value: JSON.stringify(immunizationRecord)
  │     }]
  │
  └─► [No content] → observation cleared on save (empty array)
        └─► DELETE /clinical/observations/:id (old record removed)

EncounterPage Integration

// frontend/apps/cms/src/features/encounter/pages/EncounterPage.tsx

// Patient birth date is extracted from clinicalPatientResponse
const normalizedPatient = useMemo(() => {
  // ...
  birth_date: p.birth_date || undefined,
  // ...
}, [clinicalPatientResponse, clinicalEncounter]);

// Passed to the tab for schedule due date calculation
{activeTab === "immunization" && (
  <ImmunizationRecordTab
    record={clinicalData.immunizationRecord}
    onRecordChange={handleImmunizationRecordChange}
    birthDate={normalizedPatient?.birth_date}
  />
)}

User Workflows

Workflow 1: Load Standard Schedule for a New Patient

Prerequisites: Pediatric patient with birth date on file

  1. Open encounter for pediatric patient
  2. Navigate to Immunization tab
  3. Click Load Schedule button
  4. System calculates personalized due dates from birth date
  5. All 31 vaccines appear grouped by age (Birth → 10-12 years)
  6. Click individual vaccine status badges to mark as "Given"
  7. Fill in clinical details (lot number, manufacturer, route, reaction) for each given vaccine

Workflow 2: Add Individual Vaccination Record

  1. Click Add button in the Vaccinations section
  2. Fill the inline form:
    • Vaccine Name (required)
    • Dose # (optional — dropdown)
    • Due Date (optional)
    • Given Date (optional)
    • Lot # (optional — recommended)
    • Manufacturer (optional)
    • Expiration (optional)
    • Provider (optional)
    • Route (optional — IM/SC/ID/Oral/Nasal)
    • Site (optional — Left Deltoid/Right Thigh/etc.)
    • Reaction (optional — None/Mild/Moderate/Severe/Anaphylaxis)
    • Notes (optional)
  3. Click Add — entry appended to the list with computed status
  4. Validation prevents: future given dates, given date before due date, invalid dates

Workflow 3: Edit Existing Vaccination

  1. Click the pencil icon on any vaccination row
  2. Edit Modal opens with all fields pre-populated
  3. Modify any field(s)
  4. Click Save Changes — entry updated, status recomputed
  5. Or click Cancel — changes discarded

Workflow 4: Mark Vaccine as Given (Quick Toggle)

  1. Click the status badge on any vaccination row
  2. System sets givenDate to today
  3. Status recomputes from 'due'/'overdue' → 'given'
  4. Click again to undo (clears givenDate)

Validation Rules

RuleBehaviorError Message
Vaccine name requiredBlocks save if empty"Vaccine name is required"
Given date not in futureBlocks save if future"Given date cannot be in the future"
Given date not before due dateBlocks save if violated"Given date cannot be before due date"
Expiration date formatBlocks if invalid YYYY-MM-DD"Invalid expiration date"

Architecture

File Structure

frontend/apps/cms/src/features/encounter/
├── types/
│   └── pediatricTypes.ts          # All immunization types, constants, schedule
├── components/tabs/
│   └── ImmunizationRecordTab.tsx   # Main component (add/edit/display)
└── hooks/
    ├── useEncounterClinicalData.ts      # Data fetching & parsing
    ├── clinicalDataParsers.ts           # parseImmunizationRecord()
    └── useEncounterTypeConfig.ts       # Tab registry (immunization tab)

Components

ComponentPurpose
ImmunizationRecordTabMain tab container. Manages add/edit state, renders list
VaccinationAddFormInline form for adding new vaccination entries
VaccinationEditModalModal dialog for editing existing entries

Key Constants (from pediatricTypes.ts)

// Immunization Status
IMMUNIZATION_STATUS_LABELS   // Display labels for each status
IMMUNIZATION_STATUS_COLORS   // Badge color classes per status

// Dose Numbers
DOSE_NUMBER_LABELS           // '0' → 'Birth Dose (0)', '1' → '1st Dose (1)', etc.

// Administration
ADMIN_ROUTE_LABELS           // IM, SC, ID, oral, nasal, other
ADMIN_SITE_LABELS            // Left Deltoid, Right Thigh, Buttock, etc.

// Adverse Reactions
ADVERSE_REACTION_LABELS      // none, mild, moderate, severe, anaphylaxis
ADVERSE_REACTION_COLORS     // Color-coded badge classes

// Schedule
STANDARD_VACCINATION_SCHEDULE  // 31 vaccines with ageDays offsets
calculateDueDate(birthDate, ageDays)  // Returns YYYY-MM-DD string

Clinical Compliance

Data Captured for Regulatory Compliance

FieldPurposeStandard Reference
lotNumberVaccine traceability for batch recallsCDC, WHO, U-WIN
expirationDateVerify non-expired vaccine administeredStandard of care
manufacturerAdverse event reporting (PvPI/VAERS)PVPI (India), VAERS (US)
administeredByClinical accountability and auditHIPAA, NABH
doseNumberMulti-dose series completion trackingImmunization schedules
administrationRouteClinical documentation standardImmunization practice
administrationSiteClinical documentation standardImmunization practice
adverseReactionAEFI (Adverse Event Following Immunization) reportingPvPI, VAERS

Indian UIP Compliance

The standard schedule (STANDARD_VACCINATION_SCHEDULE) covers all vaccines in India's Universal Immunization Programme:

  • Birth doses: BCG, OPV-0, Hep B
  • 6/10/14 week vaccines: DPT, IPV, Hep B, Rotavirus, PCV
  • 6-7 months: Influenza
  • 9-12 months: Measles/MR, MMR, Hep A
  • 15-18 months: Varicella, DPT/IPV boosters, Hep A
  • 5-6 years: MMR, Varicella, DPT boosters
  • 10-12 years: Tdap/Td
  • 9-14 years: HPV (girls)


Troubleshooting

"Load Schedule requires birth date"

The patient's birth_date is not available in the encounter context. Ensure:

  1. The patient record has birth_date populated in the backend
  2. The patient has been fetched via useClinicalPatient(patientId)

Vaccination not saving

The batch save only persists when:

  • vaccinations.length > 0, OR
  • generalNotes is non-empty

Deleting all entries and clearing notes will remove the observation.

Status badge shows "Upcoming" for a past vaccine

The vaccine has no dueDate set. Add a due date in the edit modal, or use "Load Schedule" which calculates due dates from the patient's birth date.

On this page