127 lines
3.8 KiB
Python
127 lines
3.8 KiB
Python
"""
|
|
Analytics models - KPIs, metrics, and dashboards
|
|
|
|
This module implements analytics capabilities:
|
|
- KPI definitions and tracking
|
|
- Metric aggregation
|
|
- Dashboard data
|
|
- Trend analysis
|
|
"""
|
|
|
|
from django.db import models
|
|
|
|
from apps.core.models import TimeStampedModel, UUIDModel
|
|
|
|
|
|
class KPI(UUIDModel, TimeStampedModel):
|
|
"""
|
|
KPI (Key Performance Indicator) definition.
|
|
|
|
Defines metrics to track across the system.
|
|
"""
|
|
|
|
name = models.CharField(max_length=200, unique=True)
|
|
name_ar = models.CharField(max_length=200, blank=True)
|
|
description = models.TextField(blank=True)
|
|
|
|
# Category
|
|
category = models.CharField(
|
|
max_length=100,
|
|
choices=[
|
|
("patient_satisfaction", "Patient Satisfaction"),
|
|
("complaint_management", "Complaint Management"),
|
|
("action_management", "Action Management"),
|
|
("sla_compliance", "SLA Compliance"),
|
|
("survey_response", "Survey Response"),
|
|
("operational", "Operational"),
|
|
],
|
|
db_index=True,
|
|
)
|
|
|
|
# Measurement
|
|
unit = models.CharField(max_length=50, help_text="Unit of measurement (%, count, hours, etc.)")
|
|
calculation_method = models.TextField(help_text="Description of how this KPI is calculated")
|
|
|
|
# Thresholds
|
|
target_value = models.DecimalField(
|
|
max_digits=10, decimal_places=2, null=True, blank=True, help_text="Target value for this KPI"
|
|
)
|
|
warning_threshold = models.DecimalField(
|
|
max_digits=10, decimal_places=2, null=True, blank=True, help_text="Warning threshold"
|
|
)
|
|
critical_threshold = models.DecimalField(
|
|
max_digits=10, decimal_places=2, null=True, blank=True, help_text="Critical threshold"
|
|
)
|
|
|
|
# Configuration
|
|
is_active = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
ordering = ["category", "name"]
|
|
verbose_name = "KPI"
|
|
verbose_name_plural = "KPIs"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class KPIValue(UUIDModel, TimeStampedModel):
|
|
"""
|
|
KPI value - actual measurement at a point in time.
|
|
"""
|
|
|
|
kpi = models.ForeignKey(KPI, on_delete=models.CASCADE, related_name="values")
|
|
|
|
# Scope
|
|
hospital = models.ForeignKey(
|
|
"organizations.Hospital", on_delete=models.CASCADE, null=True, blank=True, related_name="kpi_values"
|
|
)
|
|
department = models.ForeignKey(
|
|
"organizations.Department", on_delete=models.CASCADE, null=True, blank=True, related_name="kpi_values"
|
|
)
|
|
|
|
# Value
|
|
value = models.DecimalField(max_digits=10, decimal_places=2)
|
|
|
|
# Time period
|
|
period_start = models.DateTimeField(db_index=True)
|
|
period_end = models.DateTimeField(db_index=True)
|
|
period_type = models.CharField(
|
|
max_length=20,
|
|
choices=[
|
|
("hourly", "Hourly"),
|
|
("daily", "Daily"),
|
|
("weekly", "Weekly"),
|
|
("monthly", "Monthly"),
|
|
("quarterly", "Quarterly"),
|
|
("yearly", "Yearly"),
|
|
],
|
|
default="daily",
|
|
)
|
|
|
|
# Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=[
|
|
("on_target", "On Target"),
|
|
("warning", "Warning"),
|
|
("critical", "Critical"),
|
|
],
|
|
db_index=True,
|
|
)
|
|
|
|
# Metadata
|
|
metadata = models.JSONField(default=dict, blank=True, help_text="Additional calculation details")
|
|
|
|
class Meta:
|
|
ordering = ["-period_end"]
|
|
indexes = [
|
|
models.Index(fields=["kpi", "-period_end"]),
|
|
models.Index(fields=["hospital", "kpi", "-period_end"]),
|
|
models.Index(fields=["hospital", "department", "-period_end"]),
|
|
]
|
|
|
|
def __str__(self):
|
|
scope = self.hospital.name if self.hospital else "Global"
|
|
return f"{self.kpi.name} - {scope} - {self.period_end.strftime('%Y-%m-%d')}: {self.value}"
|