261 lines
7.5 KiB
Python
261 lines
7.5 KiB
Python
"""
|
|
Referrals models for the Tenhal Multidisciplinary Healthcare Platform.
|
|
|
|
This module handles inter-discipline and external referrals.
|
|
"""
|
|
|
|
from django.db import models
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from core.models import (
|
|
UUIDPrimaryKeyMixin,
|
|
TimeStampedMixin,
|
|
TenantOwnedMixin,
|
|
)
|
|
|
|
|
|
class Referral(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin):
|
|
"""
|
|
Inter-discipline and external referrals.
|
|
Tracks referrals between clinics and to external providers.
|
|
"""
|
|
|
|
class Discipline(models.TextChoices):
|
|
MEDICAL = 'MEDICAL', _('Medical')
|
|
NURSING = 'NURSING', _('Nursing')
|
|
OT = 'OT', _('Occupational Therapy')
|
|
SLP = 'SLP', _('Speech-Language Pathology')
|
|
ABA = 'ABA', _('Applied Behavior Analysis')
|
|
EXTERNAL = 'EXTERNAL', _('External Provider')
|
|
|
|
class Urgency(models.TextChoices):
|
|
ROUTINE = 'ROUTINE', _('Routine')
|
|
URGENT = 'URGENT', _('Urgent')
|
|
EMERGENCY = 'EMERGENCY', _('Emergency')
|
|
|
|
class Status(models.TextChoices):
|
|
PENDING = 'PENDING', _('Pending')
|
|
ACCEPTED = 'ACCEPTED', _('Accepted')
|
|
REJECTED = 'REJECTED', _('Rejected')
|
|
COMPLETED = 'COMPLETED', _('Completed')
|
|
CANCELLED = 'CANCELLED', _('Cancelled')
|
|
|
|
# Core Relationships
|
|
patient = models.ForeignKey(
|
|
'core.Patient',
|
|
on_delete=models.CASCADE,
|
|
related_name='referrals',
|
|
verbose_name=_("Patient")
|
|
)
|
|
|
|
# From (Source)
|
|
from_clinic = models.ForeignKey(
|
|
'core.Clinic',
|
|
on_delete=models.CASCADE,
|
|
related_name='referrals_from',
|
|
verbose_name=_("From Clinic")
|
|
)
|
|
from_discipline = models.CharField(
|
|
max_length=20,
|
|
choices=Discipline.choices,
|
|
verbose_name=_("From Discipline")
|
|
)
|
|
from_provider = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
related_name='referrals_made',
|
|
verbose_name=_("From Provider")
|
|
)
|
|
|
|
# To (Destination) - Internal or External
|
|
to_clinic = models.ForeignKey(
|
|
'core.Clinic',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='referrals_to',
|
|
verbose_name=_("To Clinic")
|
|
)
|
|
to_discipline = models.CharField(
|
|
max_length=20,
|
|
choices=Discipline.choices,
|
|
blank=True,
|
|
verbose_name=_("To Discipline")
|
|
)
|
|
to_provider = models.ForeignKey(
|
|
'core.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='referrals_received',
|
|
verbose_name=_("To Provider")
|
|
)
|
|
external_provider_name = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
help_text=_("Name of external provider if referral is external"),
|
|
verbose_name=_("External Provider Name")
|
|
)
|
|
external_provider_contact = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
verbose_name=_("External Provider Contact")
|
|
)
|
|
|
|
# Referral Details
|
|
reason = models.TextField(
|
|
verbose_name=_("Reason for Referral")
|
|
)
|
|
urgency = models.CharField(
|
|
max_length=20,
|
|
choices=Urgency.choices,
|
|
default=Urgency.ROUTINE,
|
|
verbose_name=_("Urgency")
|
|
)
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=Status.choices,
|
|
default=Status.PENDING,
|
|
verbose_name=_("Status")
|
|
)
|
|
|
|
# Timestamps
|
|
responded_at = models.DateTimeField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_("Responded At")
|
|
)
|
|
completed_at = models.DateTimeField(
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_("Completed At")
|
|
)
|
|
|
|
# Additional Information
|
|
notes = models.TextField(
|
|
blank=True,
|
|
verbose_name=_("Notes")
|
|
)
|
|
response_notes = models.TextField(
|
|
blank=True,
|
|
verbose_name=_("Response Notes")
|
|
)
|
|
|
|
# Attachments (clinical documents, reports, etc.)
|
|
clinical_summary = models.TextField(
|
|
blank=True,
|
|
help_text=_("Summary of clinical findings to share with receiving provider"),
|
|
verbose_name=_("Clinical Summary")
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _("Referral")
|
|
verbose_name_plural = _("Referrals")
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['patient', 'status']),
|
|
models.Index(fields=['from_clinic', 'created_at']),
|
|
models.Index(fields=['to_clinic', 'status']),
|
|
models.Index(fields=['status', 'urgency']),
|
|
models.Index(fields=['tenant', 'created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
if self.to_clinic:
|
|
return f"Referral: {self.from_clinic.name_en} → {self.to_clinic.name_en} - {self.patient}"
|
|
else:
|
|
return f"Referral: {self.from_clinic.name_en} → {self.external_provider_name} - {self.patient}"
|
|
|
|
@property
|
|
def is_internal(self):
|
|
"""Check if referral is internal (within the same facility)."""
|
|
return self.to_clinic is not None
|
|
|
|
@property
|
|
def is_external(self):
|
|
"""Check if referral is external."""
|
|
return self.to_clinic is None and bool(self.external_provider_name)
|
|
|
|
@property
|
|
def is_pending(self):
|
|
"""Check if referral is pending."""
|
|
return self.status == self.Status.PENDING
|
|
|
|
@property
|
|
def response_time(self):
|
|
"""Calculate response time in hours."""
|
|
if self.responded_at:
|
|
delta = self.responded_at - self.created_at
|
|
return round(delta.total_seconds() / 3600, 2)
|
|
return None
|
|
|
|
|
|
class ReferralAutoRule(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin):
|
|
"""
|
|
Automatic referral rules based on diagnoses or conditions.
|
|
E.g., ASD diagnosis → auto-suggest referral to Pediatrician
|
|
"""
|
|
|
|
name = models.CharField(
|
|
max_length=200,
|
|
verbose_name=_("Rule Name")
|
|
)
|
|
description = models.TextField(
|
|
blank=True,
|
|
verbose_name=_("Description")
|
|
)
|
|
|
|
# Trigger Conditions
|
|
trigger_clinic = models.ForeignKey(
|
|
'core.Clinic',
|
|
on_delete=models.CASCADE,
|
|
related_name='referral_auto_rules',
|
|
verbose_name=_("Trigger Clinic")
|
|
)
|
|
trigger_keywords = models.JSONField(
|
|
default=list,
|
|
help_text=_("Keywords in diagnosis/assessment that trigger this rule"),
|
|
verbose_name=_("Trigger Keywords")
|
|
)
|
|
|
|
# Target
|
|
target_clinic = models.ForeignKey(
|
|
'core.Clinic',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='referral_auto_rules_target',
|
|
verbose_name=_("Target Clinic")
|
|
)
|
|
target_external_provider = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
verbose_name=_("Target External Provider")
|
|
)
|
|
|
|
# Rule Configuration
|
|
urgency = models.CharField(
|
|
max_length=20,
|
|
choices=Referral.Urgency.choices,
|
|
default=Referral.Urgency.ROUTINE,
|
|
verbose_name=_("Default Urgency")
|
|
)
|
|
auto_create = models.BooleanField(
|
|
default=False,
|
|
help_text=_("Automatically create referral (vs. just suggest)"),
|
|
verbose_name=_("Auto Create")
|
|
)
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
verbose_name=_("Is Active")
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _("Referral Auto Rule")
|
|
verbose_name_plural = _("Referral Auto Rules")
|
|
ordering = ['trigger_clinic', 'name']
|
|
|
|
def __str__(self):
|
|
return f"{self.name} - {self.trigger_clinic.name_en}"
|