/** * Prisma Schema for Maternal Health Support Backend * ORM CHOICE: Prisma * JUSTIFICATION: * - Type-safe database client with excellent TypeScript support * - Intuitive schema definition language * - Automatic migration generation * - Built-in connection pooling * - Great developer experience with Prisma Studio * - Strong PostgreSQL support * CLINICAL SAFETY PRINCIPLES: * - Data minimization enforced at schema level * - No free-text medical fields * - Enums for controlled vocabularies * - Timestamps for audit trails * - Strict validation rules */ generator client { provider = "prisma-client-js" engineType = "library" } datasource db { provider = "postgresql" } /** * User Role Enum */ enum UserRole { SUPER_ADMIN FACILITY_ADMIN FACILITY_STAFF PATIENT } /** * Health Worker Type Enum */ enum HealthWorkerType { DOCTOR NURSE MIDWIFE COMMUNITY_HEALTH_WORKER } enum PatientRiskLevel { NORMAL HIGH CRITICAL } /** * Organization Entity */ model Organization { id String @id @default(uuid()) name String description String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt facilities Facility[] @@index([isActive]) @@map("organization") } /** * Facility Status Enum */ enum FacilityStatus { PENDING ACTIVE SUSPENDED } /** * Facility Entity */ model Facility { id String @id @default(uuid()) organizationId String? name String address String? phone String? facilityCode String @unique isActive Boolean @default(true) status FacilityStatus @default(ACTIVE) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) healthcareWorkers HealthcareWorker[] patients Patient[] appointments Appointment[] alerts Alert[] referrals Referral[] ancVisits ANCVisit[] outgoingReferrals Referral[] @relation("OutgoingReferrals") incomingReferrals Referral[] @relation("IncomingReferrals") @@index([organizationId]) @@index([facilityCode]) @@index([isActive]) @@map("facility") } /** * Healthcare Worker Entity */ model HealthcareWorker { id String @id @default(uuid()) userId String @unique facilityId String type HealthWorkerType firstName String lastName String isSuspended Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) facility Facility @relation(fields: [facilityId], references: [id], onDelete: Cascade) ancVisits ANCVisit[] @@index([userId]) @@index([facilityId]) @@index([isSuspended]) @@map("healthcare_worker") } /** * Blood Group Enum */ enum BloodGroup { A_POSITIVE A_NEGATIVE B_POSITIVE B_NEGATIVE O_POSITIVE O_NEGATIVE AB_POSITIVE AB_NEGATIVE } /** * Genotype Enum */ enum Genotype { AA AS AC SS SC CC } /** * Smoking Status Enum */ enum SmokingStatus { NEVER CURRENT FORMER } /** * Alcohol Use Enum */ enum AlcoholUse { NEVER OCCASIONAL REGULAR } /** * Substance Use Enum */ enum SubstanceUse { NEVER CURRENT FORMER } /** * Education Level Enum */ enum EducationLevel { NONE PRIMARY SECONDARY TERTIARY } /** * Booking Status Enum */ enum BookingStatus { BOOKED UNBOOKED } /** * Patient Entity */ model Patient { id String @id @default(uuid()) userId String @unique facilityId String patientId String @unique firstName String lastName String phone String age Int address String? gestationalAge Int estimatedDeliveryDate DateTime? qrCode String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) facility Facility? @relation(fields: [facilityId], references: [id], onDelete: Cascade) appointments Appointment[] alerts Alert[] referrals Referral[] ancVisits ANCVisit[] status PatientStatus @default(ACTIVE) riskLevel PatientRiskLevel @default(NORMAL) riskReason String? latestRiskLevel RiskLevel @default(LOW) lastVisitDate DateTime? // --- Patient Information --- bloodGroup BloodGroup? genotype Genotype? emergencyContactName String? emergencyContactPhone String? emergencyContactRelation String? // --- Obstetric History --- gravidity Int? // G parity Int? // P livingChildren Int? previousMiscarriages Int? previousAbortions Int? previousStillbirths Int? previousNeonatalDeaths Int? previousCaesareans Int? previousMultiplePregnancy Boolean? // --- Current Pregnancy --- lastMenstrualPeriod DateTime? bookingStatus BookingStatus? plannedPlaceOfDelivery String? // --- Previous Pregnancy Complications --- previousPreeclampsia Boolean? previousEclampsia Boolean? gestationalHypertension Boolean? chronicHypertensionBeforePregnancy Boolean? previousGestationalDiabetes Boolean? previousPostpartumHemorrhage Boolean? previousPretermBirth Boolean? previousLowBirthWeight Boolean? previousPlacentalAbruption Boolean? previousPlacentaPrevia Boolean? previousIufd Boolean? // Intrauterine Fetal Death // --- Medical History --- chronicHypertension Boolean? diabetesMellitus Boolean? kidneyDisease Boolean? heartDisease Boolean? asthma Boolean? sickleCellDisease Boolean? epilepsy Boolean? thyroidDisease Boolean? hivStatus Boolean? hepatitisBStatus Boolean? // --- Family History --- familyHypertension Boolean? familyPreeclampsia Boolean? familyDiabetes Boolean? familyKidneyDisease Boolean? familyMultiplePregnancy Boolean? // --- Lifestyle & Social History --- smokingStatus SmokingStatus? alcoholUse AlcoholUse? substanceUse SubstanceUse? occupation String? educationLevel EducationLevel? distanceToFacility Float? // in km emergencyTransport Boolean? supportPersonAvailable Boolean? // Current Pregnancy Risk Factors bmi Float? // Risk History riskHistory RiskHistory[] @@index([userId]) @@index([facilityId]) @@index([patientId]) @@index([isActive]) @@index([status]) @@map("patient") } /** * Alert Entity */ model Alert { id String @id @default(uuid()) facilityId String patientId String type String message String isResolved Boolean @default(false) resolvedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt facility Facility @relation(fields: [facilityId], references: [id], onDelete: Cascade) patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade) @@index([facilityId]) @@index([patientId]) @@index([isResolved]) @@map("alert") } /** * Referral Entity */ enum ReferralStatus { PENDING ACCEPTED COMPLETED CANCELLED } model Referral { id String @id @default(uuid()) patientId String fromFacilityId String toFacilityId String? reason String notes String? status ReferralStatus @default(PENDING) referredAt DateTime @default(now()) acceptedAt DateTime? completedAt DateTime? patient Patient @relation(fields: [patientId], references: [id]) fromFacility Facility @relation("OutgoingReferrals", fields: [fromFacilityId], references: [id]) toFacility Facility? @relation("IncomingReferrals", fields: [toFacilityId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt facility Facility? @relation(fields: [facilityId], references: [id]) facilityId String? @@index([patientId]) @@index([fromFacilityId]) @@index([toFacilityId]) @@index([status]) } /** * User Status Enum */ enum UserStatus { PENDING ACTIVE SUSPENDED } /** * UserAuth Entity * CLINICAL SAFETY CONSTRAINTS: * - Minimal PII only (email OR phone, not both required) * - No health data in this table * - No logging of credentials * - Passwords are hashed (never stored in plain text) * DATA MINIMIZATION: * - Only authentication-related fields * - No names, addresses, or demographic data * - Health data stored in separate UserProfile table */ model UserAuth { id String @id @default(uuid()) email String? @unique phone String? @unique passwordHash String? // bcrypt hashed password (null until setup) role UserRole @default(PATIENT) status UserStatus @default(PENDING) refreshToken String? // For JWT refresh token rotation pushToken String? // For Expo push notifications passwordSetupToken String? // For initial password setup passwordSetupExpiresAt DateTime? passwordResetToken String? // For forgot password passwordResetExpiresAt DateTime? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relation to UserProfile (optional - created after registration) profile UserProfile? // Relation to BloodPressureReadings bloodPressureReadings BloodPressureReading[] // Relation to SymptomRecords symptomRecords SymptomRecord[] // Relation to Notifications notifications Notification[] // Relation to MonitoringStateRecord monitoringStateRecord MonitoringStateRecord? // Relation to FollowUpTasks followUpTasks FollowUpTask[] // Relation to Appointments appointments Appointment[] // Relation to HealthcareWorker healthcareWorker HealthcareWorker? // Relation to Patient patient Patient? @@index([email]) @@index([phone]) @@index([role]) @@map("user_auth") } /** * Age Range Enum * DATA MINIMIZATION: * - Use age ranges instead of exact DOB * - Prevents unnecessary PII collection */ enum AgeRange { UNDER_18 AGE_18_34 AGE_35_PLUS } /** * Emergency Contact Relationship Enum * DATA MINIMIZATION: * - Relationship type only (no names stored) * - Supports midwife or closest relative */ enum EmergencyContactRelationship { MIDWIFE PARTNER FAMILY_MEMBER OTHER } /** * Known Conditions Enum * CLINICAL SAFETY: * - Enumerated list prevents free-text medical history * - Only high-risk pregnancy conditions relevant to hypertension * - No diagnostic information stored */ enum KnownCondition { CHRONIC_HYPERTENSION GESTATIONAL_DIABETES PREECLAMPSIA_HISTORY KIDNEY_DISEASE AUTOIMMUNE_DISORDER MULTIPLE_PREGNANCY NONE } /** * UserProfile Entity * DATA MINIMIZATION PRINCIPLES: * - No DOB (age range only) * - No address * - No free-text medical history * - No name or demographic data * - Only pregnancy-relevant information * CLINICAL SAFETY: * - All fields are enumerated or structured * - No diagnostic interpretations stored * - Links to UserAuth via userId */ model UserProfile { id String @id @default(uuid()) userId String @unique ageRange AgeRange pregnancyWeeks Int // Current week of pregnancy (0-42) firstPregnancy Boolean knownConditions KnownCondition[] // Array of enumerated conditions emergencyContactPhone String? // Phone number only (no name) emergencyContactEmail String? // Email address for emergency alerts emergencyContactRelationship EmergencyContactRelationship? // Relationship enum (e.g. MIDWIFE, PARTNER) // Clinic Information clinicName String? clinicAddress String? clinicPhone String? // Notification Preferences notifyCarePriority Boolean @default(true) notifyBpAlert Boolean @default(true) notifySymptomAlert Boolean @default(true) notifyReminders Boolean @default(true) reminderTime String? @default("09:00") // 24h format (HH:mm) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relation to UserAuth userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("user_profile") } /** * Blood Pressure Reading Entity * CLINICAL SAFETY CONSTRAINTS: * - Manual entry only (no device integration) * - Do NOT label readings as normal/abnormal * - Do NOT store interpretations * - Neutral data handling only * VALIDATION: * - Systolic: 60-260 mmHg (physiologically possible range) * - Diastolic: 40-160 mmHg (physiologically possible range) * - Timestamp for temporal tracking * PURPOSE: * - Store raw BP measurements * - Used by care priority engine (Phase 3) * - No diagnostic labels attached to data */ model BloodPressureReading { id String @id @default(uuid()) userId String systolic Int // mmHg (60-260) diastolic Int // mmHg (40-160) recordedAt DateTime @default(now()) createdAt DateTime @default(now()) // Relation to UserAuth userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([recordedAt]) @@map("blood_pressure_reading") } /** * Symptom Type Enum * CLINICAL SAFETY: * - Enumerated list of pregnancy hypertension warning signs * - Free-text is only allowed with OTHER for user-entered symptoms * - No severity levels * - Based on clinical guidelines for preeclampsia/eclampsia */ enum SymptomType { HEADACHE // Severe or persistent headache BLURRED_VISION // Visual disturbances SWELLING // Edema (face, hands, feet) VAGINAL_BLEEDING // Vaginal bleeding FEVER // Fever REDUCED_FETAL_MOVEMENT // Reduced fetal movement DIFFICULTY_BREATHING // Difficulty breathing CONVULSIONS // Convulsions UPPER_ABDOMINAL_PAIN // Right upper quadrant pain REDUCED_URINE // Decreased urine output NAUSEA_VOMITING // Persistent nausea/vomiting SHORTNESS_OF_BREATH // Difficulty breathing OTHER // User-entered symptom text stored separately } /** * Symptom Record Entity * CLINICAL SAFETY CONSTRAINTS: * - Atomic recording (one symptom per record) * - No severity levels * - No numeric scoring * - No interpretation or diagnosis * PURPOSE: * - Track presence of warning symptoms * - Used by care priority engine for escalation * - Each symptom is a separate, timestamped record */ model SymptomRecord { id String @id @default(uuid()) userId String symptomType SymptomType customSymptom String? recordedAt DateTime @default(now()) createdAt DateTime @default(now()) // Relation to UserAuth userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([recordedAt]) @@index([symptomType]) @@map("symptom_record") } /** * Notification Type Enum */ enum NotificationType { CARE_PRIORITY BP_ALERT SYMPTOM_ALERT REMINDER EDUCATIONAL } /** * Notification Priority Enum */ enum NotificationPriority { INFORMATIONAL EDUCATIONAL REMINDER ELEVATED_RISK CRITICAL } /** * Notification Category Enum */ enum NotificationCategory { PREECLAMPSIA_AWARENESS BP_MONITORING PREGNANCY_WARNING_SIGNS HYDRATION REST ANTENATAL_CARE SYMPTOM_AWARENESS TRIMESTER_GUIDANCE } /** * Notification Entity * PURPOSE: * - Store history of alerts and notifications * - Track read status */ model Notification { id String @id @default(uuid()) userId String type NotificationType priority NotificationPriority @default(REMINDER) category NotificationCategory? title String message String read Boolean @default(false) createdAt DateTime @default(now()) userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([createdAt]) @@map("notification") } /** * MonitoringState Enum * Defines the possible states of the monitoring system for a user. */ enum MonitoringState { NORMAL MONITOR URGENT EMERGENCY } /** * MonitoringStateRecord Entity * Stores the current monitoring state for each user. */ model MonitoringStateRecord { id String @id @default(uuid()) userId String @unique currentState MonitoringState lastUpdatedAt DateTime @default(now()) reason String? userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("monitoring_state_record") } /** * FollowUpTaskType Enum * Defines the types of follow-up tasks. */ enum FollowUpTaskType { RECHECK SEEK_CARE } /** * FollowUpTaskStatus Enum * Defines the status of follow-up tasks. */ enum FollowUpTaskStatus { PENDING COMPLETED MISSED } enum PatientStatus { ACTIVE DEFAULTER DELIVERED TRANSFERRED LOST_TO_FOLLOW_UP } /** * FollowUpTask Entity * Stores details about each follow-up task. */ model FollowUpTask { id String @id @default(uuid()) userId String type FollowUpTaskType dueAt DateTime status FollowUpTaskStatus @default(PENDING) relatedReadingId String? // Optional: Link to a specific BP reading that triggered the task userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([dueAt]) @@index([status]) @@map("follow_up_task") } enum AppointmentType { ANC LAB SCAN } enum AppointmentStatus { SCHEDULED COMPLETED MISSED } enum UrineProtein { NEGATIVE TRACE PLUS_1 PLUS_2 PLUS_3 } enum RiskLevel { LOW MODERATE HIGH EMERGENCY } model Appointment { id String @id @default(uuid()) userId String facilityId String? patientId String? type AppointmentType title String dateTime DateTime location String? notes String? status AppointmentStatus @default(SCHEDULED) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userAuth UserAuth @relation(fields: [userId], references: [id], onDelete: Cascade) facility Facility? @relation(fields: [facilityId], references: [id], onDelete: Cascade) patient Patient? @relation(fields: [patientId], references: [id], onDelete: Cascade) @@index([userId]) @@index([facilityId]) @@index([patientId]) @@index([dateTime]) @@map("appointment") } model ANCVisit { id String @id @default(uuid()) patientId String patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade) facilityId String facility Facility @relation(fields: [facilityId], references: [id], onDelete: Cascade) healthcareWorkerId String healthcareWorker HealthcareWorker @relation(fields: [healthcareWorkerId], references: [id], onDelete: Cascade) visitDate DateTime @default(now()) gestationalAge Int? weight Float? bloodPressureSystolic Int? bloodPressureDiastolic Int? urineProtein UrineProtein? fundalHeight Float? fetalHeartRate Int? fetalMovement Boolean? haemoglobin Float? temperature Float? remarks String? nextAppointmentDate DateTime? riskLevel RiskLevel @default(LOW) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([patientId]) @@index([facilityId]) @@index([healthcareWorkerId]) @@index([visitDate]) @@index([nextAppointmentDate]) @@index([riskLevel]) @@map("anc_visit") } model RiskHistory { id String @id @default(uuid()) patientId String patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade) riskLevel RiskLevel score Int reasons String[] contributingFactorIds String[] recommendations String[] isRedFlag Boolean redFlagReasons String[] calculatedAt DateTime @default(now()) recordedBy String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([patientId]) @@index([calculatedAt]) @@map("risk_history") }