""" Therapy Goal Tracking Models. This module handles therapy goals, goal progress tracking, and goal-based session documentation. """ 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, ) class TherapyGoal(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin): """ Therapy goals for patients. Tracks treatment objectives and target outcomes. """ class Status(models.TextChoices): ACTIVE = 'ACTIVE', _('Active') ACHIEVED = 'ACHIEVED', _('Achieved') IN_PROGRESS = 'IN_PROGRESS', _('In Progress') ON_HOLD = 'ON_HOLD', _('On Hold') DISCONTINUED = 'DISCONTINUED', _('Discontinued') class Priority(models.TextChoices): LOW = 'LOW', _('Low') MEDIUM = 'MEDIUM', _('Medium') HIGH = 'HIGH', _('High') URGENT = 'URGENT', _('Urgent') patient = models.ForeignKey( 'core.Patient', on_delete=models.CASCADE, related_name='therapy_goals', verbose_name=_("Patient") ) clinic = models.ForeignKey( 'core.Clinic', on_delete=models.CASCADE, related_name='therapy_goals', verbose_name=_("Clinic") ) assigned_therapist = models.ForeignKey( 'core.User', on_delete=models.SET_NULL, null=True, related_name='assigned_therapy_goals', verbose_name=_("Assigned Therapist") ) # Goal Details goal_text = models.TextField( verbose_name=_("Goal Description"), help_text=_("Clear, measurable description of the therapy goal") ) baseline = models.TextField( blank=True, verbose_name=_("Baseline"), help_text=_("Current level of functioning before intervention") ) target_criteria = models.TextField( verbose_name=_("Target Criteria"), help_text=_("Specific, measurable criteria for goal achievement") ) intervention_strategies = models.TextField( blank=True, verbose_name=_("Intervention Strategies"), help_text=_("Strategies and techniques to achieve this goal") ) # Timeline start_date = models.DateField( verbose_name=_("Start Date") ) target_date = models.DateField( verbose_name=_("Target Date"), help_text=_("Expected date to achieve this goal") ) achieved_date = models.DateField( null=True, blank=True, verbose_name=_("Achieved Date") ) # Status & Priority status = models.CharField( max_length=20, choices=Status.choices, default=Status.ACTIVE, verbose_name=_("Status") ) priority = models.CharField( max_length=10, choices=Priority.choices, default=Priority.MEDIUM, verbose_name=_("Priority") ) # Progress Tracking current_progress_percentage = models.PositiveIntegerField( default=0, verbose_name=_("Current Progress %"), help_text=_("Estimated progress toward goal achievement (0-100)") ) # Notes notes = models.TextField( blank=True, verbose_name=_("Notes") ) history = HistoricalRecords() class Meta: verbose_name = _("Therapy Goal") verbose_name_plural = _("Therapy Goals") ordering = ['-priority', '-start_date'] indexes = [ models.Index(fields=['patient', 'status']), models.Index(fields=['clinic', 'status']), models.Index(fields=['assigned_therapist', 'status']), models.Index(fields=['tenant', 'status']), models.Index(fields=['target_date', 'status']), ] def __str__(self): return f"{self.patient} - {self.goal_text[:50]}" @property def is_overdue(self): """Check if goal is past target date and not achieved.""" from datetime import date if self.status == self.Status.ACHIEVED: return False return date.today() > self.target_date @property def days_until_target(self): """Calculate days until target date.""" from datetime import date delta = self.target_date - date.today() return delta.days def mark_achieved(self): """Mark goal as achieved.""" from django.utils import timezone self.status = self.Status.ACHIEVED self.achieved_date = timezone.now().date() self.current_progress_percentage = 100 self.save() def update_progress(self, percentage, notes=""): """Update goal progress.""" self.current_progress_percentage = min(100, max(0, percentage)) if notes: self.notes = f"{self.notes}\n\n[{timezone.now().strftime('%Y-%m-%d')}] {notes}" if self.notes else notes # Auto-update status based on progress if self.current_progress_percentage == 100: self.status = self.Status.ACHIEVED if not self.achieved_date: self.achieved_date = timezone.now().date() elif self.current_progress_percentage > 0: if self.status == self.Status.ACTIVE: self.status = self.Status.IN_PROGRESS self.save() class GoalProgress(UUIDPrimaryKeyMixin, TimeStampedMixin): """ Progress updates for therapy goals. Tracks progress over time with session linkage. """ goal = models.ForeignKey( TherapyGoal, on_delete=models.CASCADE, related_name='progress_updates', verbose_name=_("Goal") ) session_date = models.DateField( verbose_name=_("Session Date") ) progress_percentage = models.PositiveIntegerField( verbose_name=_("Progress %"), help_text=_("Progress toward goal at this session (0-100)") ) observations = models.TextField( verbose_name=_("Observations"), help_text=_("Observations and notes about progress") ) recorded_by = models.ForeignKey( 'core.User', on_delete=models.SET_NULL, null=True, related_name='recorded_goal_progress', verbose_name=_("Recorded By") ) # Optional session linkage (if session models exist) # session_id = models.UUIDField(null=True, blank=True) class Meta: verbose_name = _("Goal Progress") verbose_name_plural = _("Goal Progress Updates") ordering = ['-session_date'] indexes = [ models.Index(fields=['goal', 'session_date']), models.Index(fields=['session_date']), ] def __str__(self): return f"{self.goal.goal_text[:30]} - {self.session_date} ({self.progress_percentage}%)" def save(self, *args, **kwargs): """Update parent goal progress when saving.""" super().save(*args, **kwargs) # Update goal's current progress self.goal.current_progress_percentage = self.progress_percentage self.goal.save(update_fields=['current_progress_percentage']) class PatientProgressMetric(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin): """ Quantitative metrics for patient progress tracking. Used for visual progress charts and analytics. """ class MetricType(models.TextChoices): BEHAVIORAL = 'BEHAVIORAL', _('Behavioral') COMMUNICATION = 'COMMUNICATION', _('Communication') MOTOR_SKILLS = 'MOTOR_SKILLS', _('Motor Skills') SOCIAL_SKILLS = 'SOCIAL_SKILLS', _('Social Skills') ACADEMIC = 'ACADEMIC', _('Academic') DAILY_LIVING = 'DAILY_LIVING', _('Daily Living Skills') SENSORY = 'SENSORY', _('Sensory Processing') COGNITIVE = 'COGNITIVE', _('Cognitive') EMOTIONAL = 'EMOTIONAL', _('Emotional Regulation') CUSTOM = 'CUSTOM', _('Custom Metric') patient = models.ForeignKey( 'core.Patient', on_delete=models.CASCADE, related_name='progress_metrics', verbose_name=_("Patient") ) clinic = models.ForeignKey( 'core.Clinic', on_delete=models.CASCADE, related_name='progress_metrics', verbose_name=_("Clinic") ) goal = models.ForeignKey( TherapyGoal, on_delete=models.SET_NULL, null=True, blank=True, related_name='metrics', verbose_name=_("Related Goal") ) # Metric Details metric_type = models.CharField( max_length=20, choices=MetricType.choices, verbose_name=_("Metric Type") ) metric_name = models.CharField( max_length=200, verbose_name=_("Metric Name"), help_text=_("Name of the metric being tracked") ) metric_value = models.DecimalField( max_digits=10, decimal_places=2, verbose_name=_("Metric Value"), help_text=_("Numerical value of the metric") ) unit_of_measurement = models.CharField( max_length=50, blank=True, verbose_name=_("Unit"), help_text=_("Unit of measurement (e.g., %, score, count)") ) # Context measurement_date = models.DateField( verbose_name=_("Measurement Date") ) measured_by = models.ForeignKey( 'core.User', on_delete=models.SET_NULL, null=True, related_name='measured_metrics', verbose_name=_("Measured By") ) notes = models.TextField( blank=True, verbose_name=_("Notes") ) class Meta: verbose_name = _("Patient Progress Metric") verbose_name_plural = _("Patient Progress Metrics") ordering = ['-measurement_date'] indexes = [ models.Index(fields=['patient', 'metric_type', 'measurement_date']), models.Index(fields=['clinic', 'measurement_date']), models.Index(fields=['goal', 'measurement_date']), models.Index(fields=['tenant', 'measurement_date']), ] def __str__(self): return f"{self.patient} - {self.metric_name}: {self.metric_value} ({self.measurement_date})"