emr-types
Version:
Comprehensive TypeScript Types Library for Electronic Medical Record (EMR) Applications - Domain-Driven Design with Zod Validation
383 lines • 16.1 kB
JavaScript
import { z } from 'zod';
import { MedicalRecordStatus } from '../domains/medical-record';
// ============================================================================
// BASE SCHEMAS
// ============================================================================
export const MedicalRecordIdSchema = z.string().uuid().brand();
// ============================================================================
// ENUM SCHEMAS
// ============================================================================
export const MedicalRecordStatusSchema = z.nativeEnum(MedicalRecordStatus);
// ============================================================================
// VALUE OBJECT SCHEMAS
// ============================================================================
export const MedicalRecordIdValueObjectSchema = z.object({
value: MedicalRecordIdSchema,
format: z.enum(['uuid', 'auto-increment', 'custom']),
createdAt: z.date(),
metadata: z.record(z.unknown()).optional()
});
// ============================================================================
// ENTITY SCHEMAS
// ============================================================================
export const DiagnosisSchema = z.object({
id: z.string().uuid(),
condition: z.string().min(1).max(100),
icd10Code: z.string().max(20).optional(),
icd11Code: z.string().max(20).optional(),
severity: z.enum(['mild', 'moderate', 'severe']),
diagnosisDate: z.date(),
isPrimary: z.boolean(),
isActive: z.boolean(),
notes: z.string().max(500).optional(),
diagnosedBy: z.string().uuid().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export const PrescriptionSchema = z.object({
id: z.string().uuid(),
medication: z.string().min(1).max(100),
ndcCode: z.string().max(20).optional(),
rxNormCode: z.string().max(20).optional(),
dosage: z.string().max(50),
frequency: z.string().max(50),
route: z.enum(['oral', 'intravenous', 'intramuscular', 'subcutaneous', 'topical', 'inhalation']),
quantity: z.number().positive(),
refills: z.number().nonnegative(),
startDate: z.date(),
endDate: z.date().optional(),
isActive: z.boolean(),
instructions: z.string().max(500).optional(),
prescribedBy: z.string().uuid(),
prescribedAt: z.date(),
createdAt: z.date(),
updatedAt: z.date()
});
export const ProcedureSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
cptCode: z.string().max(20).optional(),
hcpcsCode: z.string().max(20).optional(),
description: z.string().max(500).optional(),
procedureDate: z.date(),
performedBy: z.string().uuid().optional(),
facility: z.string().max(100).optional(),
anesthesiaType: z.enum(['none', 'local', 'regional', 'general']).optional(),
complications: z.string().max(500).optional(),
notes: z.string().max(500).optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export const MedicalRecordSchema = z.object({
id: MedicalRecordIdSchema,
patientId: z.string().uuid(),
providerId: z.string().uuid(),
status: MedicalRecordStatusSchema,
visitDate: z.date(),
chiefComplaint: z.string().min(1).max(500),
historyOfPresentIllness: z.string().max(1000).optional(),
physicalExamination: z.object({
vitalSigns: z.object({
temperature: z.number().optional(),
bloodPressure: z.string().max(20).optional(),
heartRate: z.number().positive().optional(),
respiratoryRate: z.number().positive().optional(),
oxygenSaturation: z.number().min(0).max(100).optional(),
height: z.number().positive().optional(),
weight: z.number().positive().optional(),
bmi: z.number().positive().optional()
}).optional(),
generalAppearance: z.string().max(200).optional(),
cardiovascular: z.string().max(200).optional(),
respiratory: z.string().max(200).optional(),
gastrointestinal: z.string().max(200).optional(),
musculoskeletal: z.string().max(200).optional(),
neurological: z.string().max(200).optional(),
skin: z.string().max(200).optional(),
notes: z.string().max(1000).optional()
}).optional(),
assessment: z.string().max(1000).optional(),
plan: z.string().max(1000).optional(),
diagnoses: z.array(DiagnosisSchema).optional(),
prescriptions: z.array(PrescriptionSchema).optional(),
procedures: z.array(ProcedureSchema).optional(),
labResults: z.array(z.object({
id: z.string().uuid(),
testName: z.string().min(1).max(100),
loincCode: z.string().max(20).optional(),
value: z.string().max(100),
unit: z.string().max(20).optional(),
referenceRange: z.string().max(100).optional(),
isAbnormal: z.boolean(),
notes: z.string().max(500).optional(),
reportedAt: z.date()
})).optional(),
imagingResults: z.array(z.object({
id: z.string().uuid(),
studyName: z.string().min(1).max(100),
modality: z.enum(['x-ray', 'ct', 'mri', 'ultrasound', 'mammography', 'other']),
bodyPart: z.string().max(100),
report: z.string().max(1000),
images: z.array(z.string().url()),
radiologist: z.string().max(100).optional(),
reportedAt: z.date()
})).optional(),
attachments: z.array(z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
type: z.enum(['document', 'image', 'video', 'audio']),
url: z.string().url(),
size: z.number().positive(),
mimeType: z.string(),
uploadedBy: z.string().uuid(),
uploadedAt: z.date()
})).optional(),
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string().uuid().optional(),
updatedBy: z.string().uuid().optional()
});
// ============================================================================
// REQUEST/INPUT SCHEMAS
// ============================================================================
export const CreateMedicalRecordRequestSchema = z.object({
patientId: z.string().uuid(),
providerId: z.string().uuid(),
visitDate: z.date(),
chiefComplaint: z.string().min(1).max(500),
historyOfPresentIllness: z.string().max(1000).optional(),
physicalExamination: z.object({
vitalSigns: z.object({
temperature: z.number().optional(),
bloodPressure: z.string().max(20).optional(),
heartRate: z.number().positive().optional(),
respiratoryRate: z.number().positive().optional(),
oxygenSaturation: z.number().min(0).max(100).optional(),
height: z.number().positive().optional(),
weight: z.number().positive().optional()
}).optional(),
generalAppearance: z.string().max(200).optional(),
cardiovascular: z.string().max(200).optional(),
respiratory: z.string().max(200).optional(),
gastrointestinal: z.string().max(200).optional(),
musculoskeletal: z.string().max(200).optional(),
neurological: z.string().max(200).optional(),
skin: z.string().max(200).optional(),
notes: z.string().max(1000).optional()
}).optional(),
assessment: z.string().max(1000).optional(),
plan: z.string().max(1000).optional()
});
export const UpdateMedicalRecordRequestSchema = z.object({
status: MedicalRecordStatusSchema.optional(),
chiefComplaint: z.string().min(1).max(500).optional(),
historyOfPresentIllness: z.string().max(1000).optional(),
physicalExamination: z.object({
vitalSigns: z.object({
temperature: z.number().optional(),
bloodPressure: z.string().max(20).optional(),
heartRate: z.number().positive().optional(),
respiratoryRate: z.number().positive().optional(),
oxygenSaturation: z.number().min(0).max(100).optional(),
height: z.number().positive().optional(),
weight: z.number().positive().optional()
}).optional(),
generalAppearance: z.string().max(200).optional(),
cardiovascular: z.string().max(200).optional(),
respiratory: z.string().max(200).optional(),
gastrointestinal: z.string().max(200).optional(),
musculoskeletal: z.string().max(200).optional(),
neurological: z.string().max(200).optional(),
skin: z.string().max(200).optional(),
notes: z.string().max(1000).optional()
}).optional(),
assessment: z.string().max(1000).optional(),
plan: z.string().max(1000).optional()
});
export const AddDiagnosisRequestSchema = z.object({
condition: z.string().min(1).max(100),
icd10Code: z.string().max(20).optional(),
icd11Code: z.string().max(20).optional(),
severity: z.enum(['mild', 'moderate', 'severe']),
diagnosisDate: z.date(),
isPrimary: z.boolean().optional(),
notes: z.string().max(500).optional()
});
export const AddPrescriptionRequestSchema = z.object({
medication: z.string().min(1).max(100),
ndcCode: z.string().max(20).optional(),
rxNormCode: z.string().max(20).optional(),
dosage: z.string().max(50),
frequency: z.string().max(50),
route: z.enum(['oral', 'intravenous', 'intramuscular', 'subcutaneous', 'topical', 'inhalation']),
quantity: z.number().positive(),
refills: z.number().nonnegative(),
startDate: z.date(),
endDate: z.date().optional(),
instructions: z.string().max(500).optional()
});
export const AddProcedureRequestSchema = z.object({
name: z.string().min(1).max(100),
cptCode: z.string().max(20).optional(),
hcpcsCode: z.string().max(20).optional(),
description: z.string().max(500).optional(),
procedureDate: z.date(),
facility: z.string().max(100).optional(),
anesthesiaType: z.enum(['none', 'local', 'regional', 'general']).optional(),
complications: z.string().max(500).optional(),
notes: z.string().max(500).optional()
});
// ============================================================================
// RESPONSE SCHEMAS
// ============================================================================
export const MedicalRecordResponseSchema = z.object({
id: MedicalRecordIdSchema,
patientId: z.string().uuid(),
providerId: z.string().uuid(),
status: MedicalRecordStatusSchema,
visitDate: z.date(),
chiefComplaint: z.string(),
historyOfPresentIllness: z.string().optional(),
assessment: z.string().optional(),
plan: z.string().optional(),
diagnoses: z.array(z.object({
condition: z.string(),
icd10Code: z.string().optional(),
severity: z.enum(['mild', 'moderate', 'severe']),
diagnosisDate: z.date(),
isActive: z.boolean()
})).optional(),
prescriptions: z.array(z.object({
medication: z.string(),
dosage: z.string(),
frequency: z.string(),
isActive: z.boolean()
})).optional(),
procedures: z.array(z.object({
name: z.string(),
cptCode: z.string().optional(),
procedureDate: z.date()
})).optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export const MedicalRecordListResponseSchema = z.object({
medicalRecords: z.array(MedicalRecordResponseSchema),
pagination: z.object({
page: z.number().positive(),
limit: z.number().positive(),
total: z.number().nonnegative(),
totalPages: z.number().nonnegative()
})
});
// ============================================================================
// FILTER/SEARCH SCHEMAS
// ============================================================================
export const MedicalRecordSearchFiltersSchema = z.object({
query: z.string().optional(),
patientId: z.string().uuid().optional(),
providerId: z.string().uuid().optional(),
status: MedicalRecordStatusSchema.optional(),
visitDateAfter: z.date().optional(),
visitDateBefore: z.date().optional(),
hasDiagnoses: z.boolean().optional(),
hasPrescriptions: z.boolean().optional(),
hasProcedures: z.boolean().optional(),
hasLabResults: z.boolean().optional(),
hasImagingResults: z.boolean().optional(),
createdAfter: z.date().optional(),
createdBefore: z.date().optional()
});
export const MedicalRecordSortOptionsSchema = z.enum([
'visitDate',
'createdAt',
'updatedAt',
'status',
'patientName',
'providerName'
]);
// ============================================================================
// VALIDATION UTILITIES
// ============================================================================
export const MedicalRecordValidationUtils = {
validateICD10Code: (code) => {
// Basic ICD-10 validation pattern
return /^[A-Z]\d{2}(\.\d{1,2})?$/.test(code);
},
validateICD11Code: (code) => {
// Basic ICD-11 validation pattern
return /^[A-Z]{2}\d{2}\.\d{1,2}$/.test(code);
},
validateCPTCode: (code) => {
// Basic CPT code validation pattern
return /^\d{5}$/.test(code);
},
validateNDCCode: (code) => {
// Basic NDC code validation pattern
return /^\d{4}-\d{4}-\d{2}$/.test(code);
},
validateRxNormCode: (code) => {
// Basic RxNorm code validation pattern
return /^\d+$/.test(code);
},
validateLOINCCode: (code) => {
// Basic LOINC code validation pattern
return /^\d{1,6}-\d$/.test(code);
},
validateMedicalRecordData: (data) => {
const errors = [];
if (data.visitDate && new Date(data.visitDate) > new Date()) {
errors.push('Visit date cannot be in the future');
}
if (data.physicalExamination?.vitalSigns) {
const vitals = data.physicalExamination.vitalSigns;
if (vitals.temperature && (vitals.temperature < 30 || vitals.temperature > 45)) {
errors.push('Temperature must be between 30 and 45 degrees Celsius');
}
if (vitals.heartRate && (vitals.heartRate < 30 || vitals.heartRate > 250)) {
errors.push('Heart rate must be between 30 and 250 bpm');
}
if (vitals.respiratoryRate && (vitals.respiratoryRate < 5 || vitals.respiratoryRate > 60)) {
errors.push('Respiratory rate must be between 5 and 60 breaths per minute');
}
if (vitals.oxygenSaturation && (vitals.oxygenSaturation < 70 || vitals.oxygenSaturation > 100)) {
errors.push('Oxygen saturation must be between 70 and 100 percent');
}
}
return {
isValid: errors.length === 0,
errors
};
}
};
// ============================================================================
// SCHEMA EXPORTS
// ============================================================================
export const MedicalRecordSchemas = {
// Base schemas
MedicalRecordId: MedicalRecordIdSchema,
// Enum schemas
MedicalRecordStatus: MedicalRecordStatusSchema,
// Value object schemas
MedicalRecordIdValueObject: MedicalRecordIdValueObjectSchema,
// Entity schemas
MedicalRecord: MedicalRecordSchema,
Diagnosis: DiagnosisSchema,
Prescription: PrescriptionSchema,
Procedure: ProcedureSchema,
// Request schemas
CreateMedicalRecordRequest: CreateMedicalRecordRequestSchema,
UpdateMedicalRecordRequest: UpdateMedicalRecordRequestSchema,
AddDiagnosisRequest: AddDiagnosisRequestSchema,
AddPrescriptionRequest: AddPrescriptionRequestSchema,
AddProcedureRequest: AddProcedureRequestSchema,
// Response schemas
MedicalRecordResponse: MedicalRecordResponseSchema,
MedicalRecordListResponse: MedicalRecordListResponseSchema,
// Filter schemas
MedicalRecordSearchFilters: MedicalRecordSearchFiltersSchema,
MedicalRecordSortOptions: MedicalRecordSortOptionsSchema,
// Utilities
ValidationUtils: MedicalRecordValidationUtils
};
//# sourceMappingURL=medical-record-schemas.js.map