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
| Status | Meaning | Computation Rule |
|---|---|---|
given | Administered | givenDate is set |
due | Scheduled and within window | givenDate not set, dueDate is today or future |
overdue | Past due date | givenDate not set, dueDate is past |
upcoming | No due date set | givenDate not set, dueDate not set |
missed | Past due without administration | Reserved 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` propSave 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
- Open encounter for pediatric patient
- Navigate to Immunization tab
- Click Load Schedule button
- System calculates personalized due dates from birth date
- All 31 vaccines appear grouped by age (Birth → 10-12 years)
- Click individual vaccine status badges to mark as "Given"
- Fill in clinical details (lot number, manufacturer, route, reaction) for each given vaccine
Workflow 2: Add Individual Vaccination Record
- Click Add button in the Vaccinations section
- 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)
- Click Add — entry appended to the list with computed status
- Validation prevents: future given dates, given date before due date, invalid dates
Workflow 3: Edit Existing Vaccination
- Click the pencil icon on any vaccination row
- Edit Modal opens with all fields pre-populated
- Modify any field(s)
- Click Save Changes — entry updated, status recomputed
- Or click Cancel — changes discarded
Workflow 4: Mark Vaccine as Given (Quick Toggle)
- Click the status badge on any vaccination row
- System sets
givenDateto today - Status recomputes from
'due'/'overdue'→'given' - Click again to undo (clears
givenDate)
Validation Rules
| Rule | Behavior | Error Message |
|---|---|---|
| Vaccine name required | Blocks save if empty | "Vaccine name is required" |
| Given date not in future | Blocks save if future | "Given date cannot be in the future" |
| Given date not before due date | Blocks save if violated | "Given date cannot be before due date" |
| Expiration date format | Blocks 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
| Component | Purpose |
|---|---|
ImmunizationRecordTab | Main tab container. Manages add/edit state, renders list |
VaccinationAddForm | Inline form for adding new vaccination entries |
VaccinationEditModal | Modal 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 stringClinical Compliance
Data Captured for Regulatory Compliance
| Field | Purpose | Standard Reference |
|---|---|---|
lotNumber | Vaccine traceability for batch recalls | CDC, WHO, U-WIN |
expirationDate | Verify non-expired vaccine administered | Standard of care |
manufacturer | Adverse event reporting (PvPI/VAERS) | PVPI (India), VAERS (US) |
administeredBy | Clinical accountability and audit | HIPAA, NABH |
doseNumber | Multi-dose series completion tracking | Immunization schedules |
administrationRoute | Clinical documentation standard | Immunization practice |
administrationSite | Clinical documentation standard | Immunization practice |
adverseReaction | AEFI (Adverse Event Following Immunization) reporting | PvPI, 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)
Related Documentation
- Types Reference — Complete TypeScript type definitions
- Vaccination Schedule — Indian UIP schedule with age calculations
- Encounter Architecture — Parent encounter page structure
- FHIR Observations — Backend FHIR-compliant observation storage
Troubleshooting
"Load Schedule requires birth date"
The patient's birth_date is not available in the encounter context. Ensure:
- The patient record has
birth_datepopulated in the backend - The patient has been fetched via
useClinicalPatient(patientId)
Vaccination not saving
The batch save only persists when:
vaccinations.length > 0, ORgeneralNotesis 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.