agdar/medical/models.py
2025-11-02 14:35:35 +03:00

543 lines
16 KiB
Python

"""
Medical models for the Tenhal Multidisciplinary Healthcare Platform.
This module handles medical consultations and follow-ups based on
MD-F-1 and MD-F-2 forms.
"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords
from core.models import (
UUIDPrimaryKeyMixin,
TimeStampedMixin,
TenantOwnedMixin,
ClinicallySignableMixin,
)
class MedicalConsultation(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin, ClinicallySignableMixin):
"""
Medical consultation form (MD-F-1).
Comprehensive medical history and assessment.
"""
# Core Relationships
patient = models.ForeignKey(
'core.Patient',
on_delete=models.CASCADE,
related_name='medical_consultations',
verbose_name=_("Patient")
)
appointment = models.ForeignKey(
'appointments.Appointment',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='medical_consultations',
verbose_name=_("Appointment")
)
consultation_date = models.DateField(
verbose_name=_("Consultation Date")
)
provider = models.ForeignKey(
'core.User',
on_delete=models.SET_NULL,
null=True,
related_name='medical_consultations_provided',
verbose_name=_("Provider")
)
# History Sections
chief_complaint = models.TextField(
blank=True,
verbose_name=_("Chief Complaint")
)
present_illness_history = models.TextField(
blank=True,
verbose_name=_("History of Present Illness")
)
past_medical_history = models.TextField(
blank=True,
verbose_name=_("Past Medical History")
)
vaccination_status = models.TextField(
blank=True,
verbose_name=_("Vaccination Status")
)
family_history = models.TextField(
blank=True,
verbose_name=_("Family History")
)
social_history = models.TextField(
blank=True,
verbose_name=_("Social History")
)
pregnancy_history = models.TextField(
blank=True,
verbose_name=_("Pregnancy History")
)
neonatal_history = models.TextField(
blank=True,
verbose_name=_("Neonatal History")
)
# Developmental Milestones
developmental_motor_milestones = models.TextField(
blank=True,
verbose_name=_("Developmental Motor Milestones")
)
developmental_language_milestones = models.TextField(
blank=True,
verbose_name=_("Developmental Language Milestones")
)
developmental_social_milestones = models.TextField(
blank=True,
verbose_name=_("Developmental Social Milestones")
)
developmental_cognitive_milestones = models.TextField(
blank=True,
verbose_name=_("Developmental Cognitive Milestones")
)
# Behavioral Symptoms (JSONField - checklist)
behavioral_symptoms = models.JSONField(
default=dict,
blank=True,
help_text=_("Behavioral symptoms checklist with yes/no/notes"),
verbose_name=_("Behavioral Symptoms")
)
# Physical Exam (JSONField - structured)
physical_exam = models.JSONField(
default=dict,
blank=True,
help_text=_("Structured physical examination findings"),
verbose_name=_("Physical Exam")
)
# Assessment & Plan
clinical_summary = models.TextField(
blank=True,
verbose_name=_("Clinical Summary")
)
recommendations = models.TextField(
blank=True,
verbose_name=_("Recommendations")
)
referrals_needed = models.TextField(
blank=True,
verbose_name=_("Referrals Needed")
)
# Medications (JSONField - like target skills)
medications = models.JSONField(
default=list,
blank=True,
help_text=_("List of current medications with compliance and effectiveness"),
verbose_name=_("Medications")
)
# Lab & Radiology Orders
lab_orders = models.JSONField(
default=list,
blank=True,
help_text=_("List of lab tests ordered"),
verbose_name=_("Lab Orders")
)
radiology_orders = models.JSONField(
default=list,
blank=True,
help_text=_("List of radiology studies ordered"),
verbose_name=_("Radiology Orders")
)
history = HistoricalRecords()
@property
def medications_json(self):
"""Return medications as JSON string for form field."""
import json
return json.dumps(self.medications) if self.medications else '[]'
class Meta:
verbose_name = _("Medical Consultation")
verbose_name_plural = _("Medical Consultations")
ordering = ['-consultation_date', '-created_at']
indexes = [
models.Index(fields=['patient', 'consultation_date']),
models.Index(fields=['provider', 'consultation_date']),
models.Index(fields=['tenant', 'consultation_date']),
]
def __str__(self):
return f"Medical Consultation - {self.patient} - {self.consultation_date}"
class MedicationPlan(UUIDPrimaryKeyMixin):
"""
Medication details for a medical consultation.
Tracks current medications, compliance, and effectiveness.
"""
class Frequency(models.TextChoices):
DAILY = 'DAILY', _('Daily')
BID = 'BID', _('BID (Twice daily)')
TID = 'TID', _('TID (Three times daily)')
QID = 'QID', _('QID (Four times daily)')
PRN = 'PRN', _('PRN (As needed)')
OTHER = 'OTHER', _('Other')
class Compliance(models.TextChoices):
GOOD = 'GOOD', _('Good')
PARTIAL = 'PARTIAL', _('Partial')
BAD = 'BAD', _('Bad')
consultation = models.ForeignKey(
MedicalConsultation,
on_delete=models.CASCADE,
related_name='medication_plans_old',
verbose_name=_("Consultation")
)
drug_name = models.CharField(
max_length=200,
verbose_name=_("Drug Name")
)
dose = models.CharField(
max_length=100,
verbose_name=_("Dose")
)
frequency = models.CharField(
max_length=20,
choices=Frequency.choices,
verbose_name=_("Frequency")
)
frequency_other = models.CharField(
max_length=100,
blank=True,
verbose_name=_("Frequency (Other)")
)
compliance = models.CharField(
max_length=20,
choices=Compliance.choices,
blank=True,
verbose_name=_("Compliance")
)
gains = models.TextField(
blank=True,
verbose_name=_("Gains/Benefits")
)
side_effects = models.TextField(
blank=True,
verbose_name=_("Side Effects")
)
target_behavior = models.TextField(
blank=True,
verbose_name=_("Target Behavior")
)
improved = models.BooleanField(
default=False,
verbose_name=_("Improved")
)
class Meta:
verbose_name = _("Medication Plan")
verbose_name_plural = _("Medication Plans")
ordering = ['consultation', 'drug_name']
def __str__(self):
return f"{self.drug_name} - {self.dose} {self.get_frequency_display()}"
class ConsultationResponse(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin):
"""
Response to medical consultation from other disciplines.
Allows OT, SLP, ABA, etc. to respond to medical consultations.
"""
class ResponseType(models.TextChoices):
OT = 'OT', _('Occupational Therapy')
SLP = 'SLP', _('Speech-Language Pathology')
ABA = 'ABA', _('ABA Therapy')
NURSING = 'NURSING', _('Nursing')
OTHER = 'OTHER', _('Other')
consultation = models.ForeignKey(
MedicalConsultation,
on_delete=models.CASCADE,
related_name='responses',
verbose_name=_("Consultation")
)
response_type = models.CharField(
max_length=20,
choices=ResponseType.choices,
verbose_name=_("Response Type")
)
responder = models.ForeignKey(
'core.User',
on_delete=models.SET_NULL,
null=True,
related_name='consultation_responses',
verbose_name=_("Responder")
)
response_date = models.DateField(
verbose_name=_("Response Date")
)
assessment = models.TextField(
verbose_name=_("Assessment")
)
recommendations = models.TextField(
blank=True,
verbose_name=_("Recommendations")
)
follow_up_needed = models.BooleanField(
default=False,
verbose_name=_("Follow-up Needed")
)
notes = models.TextField(
blank=True,
verbose_name=_("Additional Notes")
)
history = HistoricalRecords()
class Meta:
verbose_name = _("Consultation Response")
verbose_name_plural = _("Consultation Responses")
ordering = ['-response_date', '-created_at']
indexes = [
models.Index(fields=['consultation', 'response_date']),
models.Index(fields=['responder', 'response_date']),
]
def __str__(self):
return f"{self.get_response_type_display()} Response to {self.consultation}"
class ConsultationFeedback(UUIDPrimaryKeyMixin, TimeStampedMixin):
"""
Feedback on medical consultation.
Can be from family, caregivers, or interdisciplinary team.
"""
class FeedbackType(models.TextChoices):
FAMILY = 'FAMILY', _('Family/Caregiver')
TEAM = 'TEAM', _('Interdisciplinary Team')
PEER = 'PEER', _('Peer Review')
SUPERVISOR = 'SUPERVISOR', _('Supervisor')
class SatisfactionRating(models.IntegerChoices):
VERY_DISSATISFIED = 1, _('Very Dissatisfied')
DISSATISFIED = 2, _('Dissatisfied')
NEUTRAL = 3, _('Neutral')
SATISFIED = 4, _('Satisfied')
VERY_SATISFIED = 5, _('Very Satisfied')
consultation = models.ForeignKey(
MedicalConsultation,
on_delete=models.CASCADE,
related_name='feedback',
verbose_name=_("Consultation")
)
feedback_type = models.CharField(
max_length=20,
choices=FeedbackType.choices,
verbose_name=_("Feedback Type")
)
submitted_by = models.ForeignKey(
'core.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='consultation_feedback_submitted',
verbose_name=_("Submitted By")
)
submitted_by_name = models.CharField(
max_length=200,
blank=True,
verbose_name=_("Submitted By Name"),
help_text=_("For family/caregiver feedback")
)
feedback_date = models.DateField(
verbose_name=_("Feedback Date")
)
satisfaction_rating = models.IntegerField(
choices=SatisfactionRating.choices,
null=True,
blank=True,
verbose_name=_("Satisfaction Rating")
)
communication_rating = models.IntegerField(
choices=SatisfactionRating.choices,
null=True,
blank=True,
verbose_name=_("Communication Rating")
)
care_quality_rating = models.IntegerField(
choices=SatisfactionRating.choices,
null=True,
blank=True,
verbose_name=_("Care Quality Rating")
)
comments = models.TextField(
verbose_name=_("Comments")
)
concerns = models.TextField(
blank=True,
verbose_name=_("Concerns/Issues")
)
suggestions = models.TextField(
blank=True,
verbose_name=_("Suggestions for Improvement")
)
history = HistoricalRecords()
class Meta:
verbose_name = _("Consultation Feedback")
verbose_name_plural = _("Consultation Feedback")
ordering = ['-feedback_date', '-created_at']
indexes = [
models.Index(fields=['consultation', 'feedback_date']),
models.Index(fields=['feedback_type']),
]
def __str__(self):
return f"{self.get_feedback_type_display()} Feedback for {self.consultation}"
@property
def average_rating(self):
"""Calculate average rating across all rating fields."""
ratings = [
r for r in [
self.satisfaction_rating,
self.communication_rating,
self.care_quality_rating
] if r is not None
]
return sum(ratings) / len(ratings) if ratings else None
class MedicalFollowUp(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin, ClinicallySignableMixin):
"""
Medical follow-up form (MD-F-2).
Tracks progress since previous consultation.
"""
class FamilySatisfaction(models.TextChoices):
ZERO = '0', _('0%')
FIFTY = '50', _('50%')
HUNDRED = '100', _('100%')
# Core Relationships
patient = models.ForeignKey(
'core.Patient',
on_delete=models.CASCADE,
related_name='medical_followups',
verbose_name=_("Patient")
)
appointment = models.ForeignKey(
'appointments.Appointment',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='medical_followups',
verbose_name=_("Appointment")
)
previous_consultation = models.ForeignKey(
MedicalConsultation,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='followups',
verbose_name=_("Previous Consultation")
)
followup_date = models.DateField(
verbose_name=_("Follow-up Date")
)
provider = models.ForeignKey(
'core.User',
on_delete=models.SET_NULL,
null=True,
related_name='medical_followups_provided',
verbose_name=_("Provider")
)
# Previous Complaints Status
previous_complaints_status = models.JSONField(
default=dict,
blank=True,
help_text=_("Status of previous complaints: complaint → RESOLVED/STATIC/WORSE"),
verbose_name=_("Previous Complaints Status")
)
new_complaints = models.TextField(
blank=True,
verbose_name=_("New Complaints")
)
# Link to Nursing Vitals
nursing_vitals = models.ForeignKey(
'nursing.NursingEncounter',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='medical_followups',
verbose_name=_("Nursing Vitals")
)
# Assessment
assessment = models.TextField(
blank=True,
verbose_name=_("Assessment")
)
recommendations = models.TextField(
blank=True,
verbose_name=_("Recommendations")
)
family_satisfaction = models.CharField(
max_length=10,
choices=FamilySatisfaction.choices,
blank=True,
verbose_name=_("Family Satisfaction")
)
# Medication Snapshot
medication_snapshot = models.JSONField(
default=list,
blank=True,
help_text=_("Snapshot of current medications at time of follow-up"),
verbose_name=_("Medication Snapshot")
)
history = HistoricalRecords()
class Meta:
verbose_name = _("Medical Follow-up")
verbose_name_plural = _("Medical Follow-ups")
ordering = ['-followup_date', '-created_at']
indexes = [
models.Index(fields=['patient', 'followup_date']),
models.Index(fields=['previous_consultation']),
models.Index(fields=['provider', 'followup_date']),
models.Index(fields=['tenant', 'followup_date']),
]
def __str__(self):
return f"Medical Follow-up - {self.patient} - {self.followup_date}"
def save(self, *args, **kwargs):
# Auto-populate medication snapshot from previous consultation if not set
if self.previous_consultation and not self.medication_snapshot:
self.medication_snapshot = [
{
'drug_name': med.drug_name,
'dose': med.dose,
'frequency': med.get_frequency_display(),
'compliance': med.get_compliance_display() if med.compliance else '',
'improved': med.improved,
}
for med in self.previous_consultation.medication_plans_old.all()
]
super().save(*args, **kwargs)