324 lines
10 KiB
Python
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})"
|