File size: 13,015 Bytes
682caaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76201c6
 
 
 
 
 
 
 
 
 
 
 
682caaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1424ea9
682caaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b648f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682caaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
from pydantic import BaseModel, EmailStr
from typing import Optional, List, Literal, Union, Any
from datetime import date, time, datetime
from enum import Enum

# --- USER SCHEMAS ---
class SignupForm(BaseModel):
    email: EmailStr
    password: str
    full_name: str
    # Patients can only register through signup, role is automatically set to 'patient'

class TokenResponse(BaseModel):
    access_token: str
    token_type: str

# --- DOCTOR CREATION SCHEMA (Admin use only) ---
class DoctorCreate(BaseModel):
    license_number: str  # Changed from matricule to license_number for consistency
    email: EmailStr
    password: str
    full_name: str
    specialty: str
    roles: List[Literal['admin', 'doctor', 'patient']] = ['doctor']  # Can have multiple roles

# --- ADMIN CREATION SCHEMA (Admin use only) ---
class AdminCreate(BaseModel):
    email: EmailStr
    password: str
    full_name: str
    roles: List[Literal['admin', 'doctor', 'patient']] = ['admin']  # Can have multiple roles

# --- ADMIN USER MANAGEMENT ---
class AdminUserUpdate(BaseModel):
    full_name: Optional[str] = None
    roles: Optional[List[Literal['admin', 'doctor', 'patient']]] = None
    specialty: Optional[str] = None
    license_number: Optional[str] = None

class AdminPasswordReset(BaseModel):
    new_password: str

# --- PROFILE UPDATE SCHEMA ---
class ProfileUpdate(BaseModel):
    full_name: Optional[str] = None
    phone: Optional[str] = None
    address: Optional[str] = None
    date_of_birth: Optional[str] = None
    gender: Optional[str] = None
    specialty: Optional[str] = None
    license_number: Optional[str] = None

# --- PASSWORD CHANGE SCHEMA ---
class PasswordChange(BaseModel):
    current_password: str
    new_password: str

# --- PASSWORD RESET SCHEMAS ---
class PasswordResetRequest(BaseModel):
    email: EmailStr

class PasswordResetVerify(BaseModel):
    email: EmailStr
    verification_code: str

class PasswordResetConfirm(BaseModel):
    email: EmailStr
    verification_code: str
    new_password: str

# --- CONTACT INFO ---
class ContactInfo(BaseModel):
    email: Optional[EmailStr] = None
    phone: Optional[str] = None

# --- PATIENT SOURCE TYPES ---
class PatientSource(str, Enum):
    EHR = "ehr"                    # Patients from external EHR system
    DIRECT_REGISTRATION = "direct"  # Patients registered directly in the system
    IMPORT = "import"               # Patients imported from other systems
    MANUAL = "manual"               # Manually entered patients
    SYNTHEA = "synthea"            # Patients imported from Synthea
    HAPI_FHIR = "hapi_fhir"        # Patients imported from HAPI FHIR Test Server

# --- PATIENT STATUS ---
class PatientStatus(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    ARCHIVED = "archived"
    PENDING = "pending"

# --- PATIENT SCHEMA ---
class PatientCreate(BaseModel):
    full_name: str
    date_of_birth: date
    gender: str
    notes: Optional[str] = ""
    address: Optional[str] = None
    national_id: Optional[str] = None
    blood_type: Optional[str] = None
    allergies: Optional[List[str]] = []
    chronic_conditions: Optional[List[str]] = []
    medications: Optional[List[str]] = []
    emergency_contact_name: Optional[str] = None
    emergency_contact_phone: Optional[str] = None
    insurance_provider: Optional[str] = None
    insurance_policy_number: Optional[str] = None
    contact: Optional[ContactInfo] = None
    # New fields for patient source management
    source: PatientSource = PatientSource.DIRECT_REGISTRATION
    ehr_id: Optional[str] = None  # External EHR system ID
    ehr_system: Optional[str] = None  # Name of the EHR system
    status: PatientStatus = PatientStatus.ACTIVE
    assigned_doctor_id: Optional[str] = None  # Primary doctor assignment
    registration_date: Optional[datetime] = None
    last_visit_date: Optional[datetime] = None
    next_appointment_date: Optional[datetime] = None

class PatientUpdate(BaseModel):
    full_name: Optional[str] = None
    date_of_birth: Optional[date] = None
    gender: Optional[str] = None
    notes: Optional[str] = None
    address: Optional[str] = None
    national_id: Optional[str] = None
    blood_type: Optional[str] = None
    allergies: Optional[List[str]] = None
    chronic_conditions: Optional[List[str]] = None
    medications: Optional[List[str]] = None
    emergency_contact_name: Optional[str] = None
    emergency_contact_phone: Optional[str] = None
    insurance_provider: Optional[str] = None
    insurance_policy_number: Optional[str] = None
    contact: Optional[ContactInfo] = None
    status: Optional[PatientStatus] = None
    assigned_doctor_id: Optional[str] = None
    last_visit_date: Optional[datetime] = None
    next_appointment_date: Optional[datetime] = None

class PatientResponse(BaseModel):
    id: str
    full_name: str
    date_of_birth: Optional[date] = None
    gender: str
    notes: Optional[Union[str, List[dict]]] = []  # Can be string or list of dicts
    address: Optional[str] = None
    national_id: Optional[str] = None
    blood_type: Optional[str] = None
    allergies: List[str] = []
    chronic_conditions: List[str] = []
    medications: Union[List[str], List[dict]] = []  # Can be list of strings or list of dicts
    emergency_contact_name: Optional[str] = None
    emergency_contact_phone: Optional[str] = None
    insurance_provider: Optional[str] = None
    insurance_policy_number: Optional[str] = None
    contact: Optional[ContactInfo] = None
    source: PatientSource
    ehr_id: Optional[str] = None
    ehr_system: Optional[str] = None
    status: PatientStatus
    assigned_doctor_id: Optional[str] = None
    assigned_doctor_name: Optional[str] = None
    registration_date: Optional[datetime] = None
    last_visit_date: Optional[datetime] = None
    next_appointment_date: Optional[datetime] = None
    created_at: datetime
    updated_at: datetime

class PatientListResponse(BaseModel):
    patients: List[PatientResponse]
    total: int
    page: int
    limit: int
    source_filter: Optional[PatientSource] = None
    status_filter: Optional[PatientStatus] = None

# --- APPOINTMENT STATUS ENUM ---
class AppointmentStatus(str, Enum):
    PENDING = "pending"
    CONFIRMED = "confirmed"
    CANCELLED = "cancelled"
    COMPLETED = "completed"
    NO_SHOW = "no_show"

# --- APPOINTMENT TYPE ENUM ---
class AppointmentType(str, Enum):
    CHECKUP = "checkup"
    CONSULTATION = "consultation"
    PROCEDURE = "procedure"
    FOLLOW_UP = "follow_up"
    EMERGENCY = "emergency"
    ROUTINE = "routine"

# --- APPOINTMENT SCHEMAS ---
class AppointmentCreate(BaseModel):
    patient_id: str  # MongoDB ObjectId as string
    doctor_id: str   # MongoDB ObjectId as string
    date: str  # Date as string in 'YYYY-MM-DD' format
    time: str  # Time as string in 'HH:MM:SS' format
    type: AppointmentType = AppointmentType.CONSULTATION
    reason: Optional[str] = None
    notes: Optional[str] = None
    duration: Optional[int] = 30  # Duration in minutes
    status: Optional[AppointmentStatus] = AppointmentStatus.PENDING
    
    # Enhanced patient data fields for comprehensive patient record
    # Personal Information
    patient_full_name: Optional[str] = None
    patient_date_of_birth: Optional[str] = None  # Format: 'YYYY-MM-DD'
    patient_gender: Optional[str] = None
    patient_address: Optional[str] = None
    patient_city: Optional[str] = None
    patient_state: Optional[str] = None
    patient_postal_code: Optional[str] = None
    patient_country: Optional[str] = None
    patient_national_id: Optional[str] = None
    patient_blood_type: Optional[str] = None
    
    # Medical Information
    patient_allergies: Optional[List[str]] = []
    patient_chronic_conditions: Optional[List[str]] = []
    patient_medications: Optional[List[str]] = []
    
    # Emergency Contact
    patient_emergency_contact_name: Optional[str] = None
    patient_emergency_contact_phone: Optional[str] = None
    
    # Insurance Information
    patient_insurance_provider: Optional[str] = None
    patient_insurance_policy_number: Optional[str] = None
    
    # Additional Notes and Encounters
    patient_medical_notes: Optional[str] = None
    patient_previous_encounters: Optional[List[dict]] = []
    patient_symptoms: Optional[List[str]] = []
    patient_vital_signs: Optional[dict] = None  # e.g., {"blood_pressure": "120/80", "temperature": "98.6"}
    patient_lab_results: Optional[List[dict]] = []
    patient_imaging_results: Optional[List[dict]] = []

class AppointmentUpdate(BaseModel):
    date: Optional[str] = None  # Date as string in 'YYYY-MM-DD' format
    time: Optional[str] = None  # Time as string in 'HH:MM:SS' format
    type: Optional[AppointmentType] = None
    reason: Optional[str] = None
    notes: Optional[str] = None
    status: Optional[AppointmentStatus] = None
    duration: Optional[int] = None

class AppointmentResponse(BaseModel):
    id: str
    patient_id: str
    doctor_id: str
    patient_name: str
    doctor_name: str
    date: date
    time: time
    type: AppointmentType
    status: AppointmentStatus
    reason: Optional[str] = None
    notes: Optional[str] = None
    duration: int
    created_at: datetime
    updated_at: datetime

class AppointmentListResponse(BaseModel):
    appointments: List[AppointmentResponse]
    total: int
    page: int
    limit: int

# --- DOCTOR AVAILABILITY SCHEMAS ---
class DoctorAvailabilityCreate(BaseModel):
    doctor_id: str
    day_of_week: int  # 0=Monday, 6=Sunday
    start_time: time
    end_time: time
    is_available: bool = True

class DoctorAvailabilityUpdate(BaseModel):
    start_time: Optional[time] = None
    end_time: Optional[time] = None
    is_available: Optional[bool] = None

class DoctorAvailabilityResponse(BaseModel):
    id: str
    doctor_id: str
    doctor_name: str
    day_of_week: int
    start_time: time
    end_time: time
    is_available: bool

# --- APPOINTMENT SLOT SCHEMAS ---
class AppointmentSlot(BaseModel):
    date: date
    time: time
    is_available: bool
    appointment_id: Optional[str] = None

class AvailableSlotsResponse(BaseModel):
    doctor_id: str
    doctor_name: str
    specialty: str
    date: date
    slots: List[AppointmentSlot]

# --- DOCTOR LIST RESPONSE ---
class DoctorListResponse(BaseModel):
    id: str
    full_name: str
    specialty: str
    license_number: str
    email: str
    phone: Optional[str] = None

# --- MESSAGING SCHEMAS ---
class MessageType(str, Enum):
    TEXT = "text"
    IMAGE = "image"
    FILE = "file"
    SYSTEM = "system"

class MessageStatus(str, Enum):
    SENT = "sent"
    DELIVERED = "delivered"
    READ = "read"
    FAILED = "failed"

class MessageCreate(BaseModel):
    recipient_id: str  # MongoDB ObjectId as string
    content: str
    message_type: MessageType = MessageType.TEXT
    attachment_url: Optional[str] = None
    reply_to_message_id: Optional[str] = None

class MessageUpdate(BaseModel):
    content: Optional[str] = None
    is_archived: Optional[bool] = None

class MessageResponse(BaseModel):
    id: str
    sender_id: str
    recipient_id: str
    sender_name: str
    recipient_name: str
    content: str
    message_type: MessageType
    attachment_url: Optional[str] = None
    reply_to_message_id: Optional[str] = None
    status: MessageStatus
    is_archived: bool = False
    created_at: datetime
    updated_at: datetime
    read_at: Optional[datetime] = None

class ConversationResponse(BaseModel):
    id: str
    participant_ids: List[str]
    participant_names: List[str]
    last_message: Optional[MessageResponse] = None
    unread_count: int = 0
    created_at: datetime
    updated_at: datetime

class ConversationListResponse(BaseModel):
    conversations: List[ConversationResponse]
    total: int
    page: int
    limit: int

class MessageListResponse(BaseModel):
    messages: List[MessageResponse]
    total: int
    page: int
    limit: int
    conversation_id: str

# --- NOTIFICATION SCHEMAS ---
class NotificationType(str, Enum):
    MESSAGE = "message"
    APPOINTMENT = "appointment"
    SYSTEM = "system"
    REMINDER = "reminder"

class NotificationPriority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    URGENT = "urgent"

class NotificationCreate(BaseModel):
    recipient_id: str
    title: str
    message: str
    notification_type: NotificationType
    priority: NotificationPriority = NotificationPriority.MEDIUM
    data: Optional[dict] = None  # Additional data for the notification

class NotificationResponse(BaseModel):
    id: str
    recipient_id: str
    recipient_name: str
    title: str
    message: str
    notification_type: NotificationType
    priority: NotificationPriority
    data: Optional[dict] = None
    is_read: bool = False
    created_at: datetime
    read_at: Optional[datetime] = None

class NotificationListResponse(BaseModel):
    notifications: List[NotificationResponse]
    total: int
    unread_count: int
    page: int
    limit: int