495 lines
15 KiB
Python
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
|