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

324 lines
10 KiB
Python

"""
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})"