MaternAlert / prisma /schema.prisma
Auspicious14's picture
feat(ocr, risk): add patient OCR and risk assessment
27e706d
Raw
History Blame Contribute Delete
21 kB
/**
* 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")
}