agdar/aba/models.py
Marwan Alwali 2f1681b18c update
2025-11-11 13:44:48 +03:00

495 lines
15 KiB
Python

"""
ABA (Applied Behavior Analysis) models for the Tenhal Multidisciplinary Healthcare Platform.
This module handles ABA consultations and behavior tracking based on ABA-F-1 form.
"""
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 ABAConsult(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin, ClinicallySignableMixin):
"""
ABA consultation form (ABA-F-1).
Functional behavior assessment and intervention planning.
"""
class ReasonOfReferral(models.TextChoices):
DEVELOPMENTAL_DELAY = 'DEVELOPMENTAL_DELAY', _('Developmental Delay')
AUTISM_SPECTRUM = 'AUTISM_SPECTRUM', _('Autism Spectrum Disorder')
BEHAVIORAL_ISSUES = 'BEHAVIORAL_ISSUES', _('Behavioral Issues')
LEARNING_DIFFICULTIES = 'LEARNING_DIFFICULTIES', _('Learning Difficulties')
SOCIAL_SKILLS = 'SOCIAL_SKILLS', _('Social Skills Deficits')
COMMUNICATION = 'COMMUNICATION', _('Communication Difficulties')
SELF_CARE = 'SELF_CARE', _('Self-Care Skills')
OTHER = 'OTHER', _('Other')
# Core Relationships
patient = models.ForeignKey(
'core.Patient',
on_delete=models.CASCADE,
related_name='aba_consults',
verbose_name=_("Patient")
)
appointment = models.ForeignKey(
'appointments.Appointment',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='aba_consults',
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='aba_consults_provided',
verbose_name=_("Provider")
)
# Referral Information
reason_of_referral = models.CharField(
max_length=50,
choices=ReasonOfReferral.choices,
verbose_name=_("Reason of Referral")
)
parental_concern = models.TextField(
blank=True,
verbose_name=_("Parental Concern")
)
school_concern = models.TextField(
blank=True,
verbose_name=_("School Concern")
)
# Interview Details
respondents = models.CharField(
max_length=200,
blank=True,
help_text=_("Who provided the information (e.g., mother, father, teacher)"),
verbose_name=_("Respondents")
)
interviewer = models.CharField(
max_length=200,
blank=True,
verbose_name=_("Interviewer")
)
# Diagnosis & Interaction
diagnosed_condition = models.CharField(
max_length=200,
blank=True,
verbose_name=_("Diagnosed Condition")
)
interaction_hours_per_day = models.PositiveIntegerField(
null=True,
blank=True,
help_text=_("Hours of interaction per day"),
verbose_name=_("Interaction Hours/Day")
)
# Physiological Factors (TextField for user-friendly input)
physiological_factors = models.TextField(
blank=True,
help_text=_("Physiological factors affecting behavior (e.g., sleep, appetite, medical conditions)"),
verbose_name=_("Physiological Factors")
)
# Medical Factors (TextField for user-friendly input)
medical_factors = models.TextField(
blank=True,
help_text=_("Medical factors and conditions"),
verbose_name=_("Medical Factors")
)
# Recommendations
recommendations = models.TextField(
blank=True,
verbose_name=_("Recommendations")
)
history = HistoricalRecords()
class Meta:
verbose_name = _("ABA Consultation")
verbose_name_plural = _("ABA 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"ABA Consultation - {self.patient} - {self.consultation_date}"
class ABABehavior(UUIDPrimaryKeyMixin, TimeStampedMixin):
"""
Specific behavior details for ABA consultation.
Tracks frequency, intensity, and functional analysis.
"""
class Frequency(models.TextChoices):
HOURLY = 'HOURLY', _('Hourly')
DAILY = 'DAILY', _('Daily')
WEEKLY = 'WEEKLY', _('Weekly')
LESS_THAN_WEEKLY = 'LESS_THAN_WEEKLY', _('Less than Weekly')
class Intensity(models.TextChoices):
MILD = 'MILD', _('Mild')
MODERATE = 'MODERATE', _('Moderate')
SEVERE = 'SEVERE', _('Severe')
consult = models.ForeignKey(
ABAConsult,
on_delete=models.CASCADE,
related_name='behaviors',
verbose_name=_("Consultation")
)
behavior_description = models.TextField(
verbose_name=_("Behavior Description")
)
frequency = models.CharField(
max_length=20,
choices=Frequency.choices,
verbose_name=_("Frequency")
)
duration = models.CharField(
max_length=100,
blank=True,
help_text=_("How long the behavior typically lasts"),
verbose_name=_("Duration")
)
intensity = models.CharField(
max_length=20,
choices=Intensity.choices,
verbose_name=_("Intensity")
)
# Functional Analysis
antecedents_likely = models.TextField(
blank=True,
help_text=_("Contexts where behavior is most likely to occur"),
verbose_name=_("Antecedents (Most Likely)")
)
antecedents_least_likely = models.TextField(
blank=True,
help_text=_("Contexts where behavior is least likely to occur"),
verbose_name=_("Antecedents (Least Likely)")
)
consequences = models.TextField(
blank=True,
help_text=_("What happens after the behavior occurs"),
verbose_name=_("Consequences")
)
# Ordering
order = models.PositiveIntegerField(
default=0,
help_text=_("Order of behaviors in the list"),
verbose_name=_("Order")
)
class Meta:
verbose_name = _("ABA Behavior")
verbose_name_plural = _("ABA Behaviors")
ordering = ['consult', 'order', 'created_at']
def __str__(self):
return f"{self.behavior_description[:50]} - {self.get_frequency_display()} - {self.get_intensity_display()}"
class ABAGoal(UUIDPrimaryKeyMixin, TimeStampedMixin):
"""
Treatment goals for ABA intervention.
Tracks progress towards behavioral objectives.
"""
class Status(models.TextChoices):
NOT_STARTED = 'NOT_STARTED', _('Not Started')
IN_PROGRESS = 'IN_PROGRESS', _('In Progress')
ACHIEVED = 'ACHIEVED', _('Achieved')
DISCONTINUED = 'DISCONTINUED', _('Discontinued')
consult = models.ForeignKey(
ABAConsult,
on_delete=models.CASCADE,
related_name='goals',
verbose_name=_("Consultation")
)
goal_description = models.TextField(
verbose_name=_("Goal Description")
)
target_date = models.DateField(
null=True,
blank=True,
verbose_name=_("Target Date")
)
status = models.CharField(
max_length=20,
choices=Status.choices,
default=Status.NOT_STARTED,
verbose_name=_("Status")
)
progress_notes = models.TextField(
blank=True,
verbose_name=_("Progress Notes")
)
achieved_date = models.DateField(
null=True,
blank=True,
verbose_name=_("Achieved Date")
)
class Meta:
verbose_name = _("ABA Goal")
verbose_name_plural = _("ABA Goals")
ordering = ['consult', 'target_date']
def __str__(self):
return f"{self.goal_description[:50]} - {self.get_status_display()}"
class ABASession(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin, ClinicallySignableMixin):
"""
ABA session notes and progress tracking.
Documents individual therapy sessions and behavioral interventions.
"""
class SessionType(models.TextChoices):
INDIVIDUAL = 'INDIVIDUAL', _('Individual Session')
GROUP = 'GROUP', _('Group Session')
PARENT_TRAINING = 'PARENT_TRAINING', _('Parent Training')
OBSERVATION = 'OBSERVATION', _('Observation')
# Core Relationships
patient = models.ForeignKey(
'core.Patient',
on_delete=models.CASCADE,
related_name='aba_sessions',
verbose_name=_("Patient")
)
appointment = models.ForeignKey(
'appointments.Appointment',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='aba_sessions',
verbose_name=_("Appointment")
)
# NEW: Link to centralized session (for scheduling/capacity management)
session = models.ForeignKey(
'appointments.Session',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='aba_notes',
verbose_name=_("Session"),
help_text=_("Link to centralized session for scheduling")
)
# NEW: Link to specific participant (for group sessions)
session_participant = models.ForeignKey(
'appointments.SessionParticipant',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='aba_notes',
verbose_name=_("Session Participant"),
help_text=_("For group sessions: which participant these notes are for")
)
session_date = models.DateField(
verbose_name=_("Session Date")
)
provider = models.ForeignKey(
'core.User',
on_delete=models.SET_NULL,
null=True,
related_name='aba_sessions_provided',
verbose_name=_("Provider")
)
# Session Details
session_type = models.CharField(
max_length=20,
choices=SessionType.choices,
verbose_name=_("Session Type")
)
duration_minutes = models.PositiveIntegerField(
null=True,
blank=True,
help_text=_("Session duration in minutes"),
verbose_name=_("Duration (minutes)")
)
# Behavioral Observations
engagement_level = models.PositiveSmallIntegerField(
null=True,
blank=True,
help_text=_("Engagement level (1-5 scale)"),
verbose_name=_("Engagement Level (1-5)")
)
cooperation_level = models.PositiveSmallIntegerField(
null=True,
blank=True,
help_text=_("Cooperation level (1-5 scale)"),
verbose_name=_("Cooperation Level (1-5)")
)
# Session Content
target_behaviors = models.TextField(
blank=True,
help_text=_("Target behaviors addressed in this session"),
verbose_name=_("Target Behaviors")
)
interventions_used = models.TextField(
blank=True,
help_text=_("Interventions and strategies used"),
verbose_name=_("Interventions Used")
)
activities_performed = models.TextField(
blank=True,
verbose_name=_("Activities Performed")
)
# Progress Notes
behavioral_observations = models.TextField(
blank=True,
verbose_name=_("Behavioral Observations")
)
progress_notes = models.TextField(
blank=True,
verbose_name=_("Progress Notes")
)
# Recommendations
recommendations = models.TextField(
blank=True,
verbose_name=_("Recommendations")
)
home_program = models.TextField(
blank=True,
help_text=_("Home program recommendations for parents"),
verbose_name=_("Home Program")
)
history = HistoricalRecords()
class Meta:
verbose_name = _("ABA Session")
verbose_name_plural = _("ABA Sessions")
ordering = ['-session_date', '-created_at']
indexes = [
models.Index(fields=['patient', 'session_date']),
models.Index(fields=['provider', 'session_date']),
models.Index(fields=['tenant', 'session_date']),
]
def __str__(self):
return f"ABA Session - {self.patient} - {self.session_date}"
@property
def engagement_level_display(self):
"""Return engagement level with description."""
levels = {
1: _("Very Low"),
2: _("Low"),
3: _("Moderate"),
4: _("Good"),
5: _("Excellent")
}
return levels.get(self.engagement_level, "")
@property
def cooperation_level_display(self):
"""Return cooperation level with description."""
levels = {
1: _("Very Low"),
2: _("Low"),
3: _("Moderate"),
4: _("Good"),
5: _("Excellent")
}
return levels.get(self.cooperation_level, "")
class ABASkillTarget(UUIDPrimaryKeyMixin):
"""
Skill targets with progress tracking for ABA sessions.
Tracks mastery of specific behavioral and developmental skills.
"""
class MasteryLevel(models.TextChoices):
NOT_STARTED = 'NOT_STARTED', _('Not Started')
EMERGING = 'EMERGING', _('Emerging (0-25%)')
DEVELOPING = 'DEVELOPING', _('Developing (26-50%)')
PROGRESSING = 'PROGRESSING', _('Progressing (51-75%)')
MASTERED = 'MASTERED', _('Mastered (76-100%)')
session = models.ForeignKey(
ABASession,
on_delete=models.CASCADE,
related_name='skill_targets',
verbose_name=_("Session")
)
skill_name = models.CharField(
max_length=200,
verbose_name=_("Skill Name")
)
mastery_level = models.CharField(
max_length=20,
choices=MasteryLevel.choices,
default=MasteryLevel.NOT_STARTED,
verbose_name=_("Mastery Level")
)
trials_correct = models.PositiveIntegerField(
null=True,
blank=True,
help_text=_("Number of correct trials"),
verbose_name=_("Trials Correct")
)
trials_total = models.PositiveIntegerField(
null=True,
blank=True,
help_text=_("Total number of trials"),
verbose_name=_("Trials Total")
)
notes = models.TextField(
blank=True,
verbose_name=_("Notes")
)
order = models.PositiveIntegerField(
default=0,
help_text=_("Order of skills in the list"),
verbose_name=_("Order")
)
class Meta:
verbose_name = _("ABA Skill Target")
verbose_name_plural = _("ABA Skill Targets")
ordering = ['session', 'order']
def __str__(self):
return f"{self.skill_name} - {self.get_mastery_level_display()}"
@property
def success_rate(self):
"""Calculate success rate percentage."""
if self.trials_total and self.trials_total > 0:
return round((self.trials_correct / self.trials_total) * 100, 2)
return 0