feat: add recap page
This commit is contained in:
+63
-1
@@ -2,7 +2,7 @@ from datetime import date as date_type
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from django.db.models import Avg, Max, Min
|
||||
from django.db.models import Avg, Count, Max, Min
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
@@ -81,6 +81,17 @@ class DayOut(Schema):
|
||||
note: Optional[DailyNoteOut]
|
||||
|
||||
|
||||
class RecapDayOut(Schema):
|
||||
"""One line of the recap: did every dose go out that day, and what was the pulse."""
|
||||
|
||||
date: date_type
|
||||
taken: int
|
||||
expected: int
|
||||
complete: bool
|
||||
bpm: Optional[int]
|
||||
note: str
|
||||
|
||||
|
||||
class DoseLogIn(Schema):
|
||||
scheduled_dose_id: int
|
||||
date: Optional[date_type] = None
|
||||
@@ -147,6 +158,51 @@ def build_day(day: date_type) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def build_recap(days: int) -> list[dict]:
|
||||
"""One line per day, newest first: dose completeness, pulse and note.
|
||||
|
||||
"Complete" is measured against the doses that are active now, not the plan
|
||||
as it stood on that day — the point is spotting gaps, not auditing history.
|
||||
"""
|
||||
today = timezone.localdate()
|
||||
since = today - timedelta(days=days - 1)
|
||||
|
||||
expected = ScheduledDose.objects.filter(
|
||||
is_active=True, medication__is_active=True
|
||||
).count()
|
||||
|
||||
taken_by_day = {
|
||||
row["date"]: row["taken"]
|
||||
for row in DoseLog.objects.filter(
|
||||
date__gte=since,
|
||||
taken=True,
|
||||
scheduled_dose__is_active=True,
|
||||
scheduled_dose__medication__is_active=True,
|
||||
)
|
||||
.values("date")
|
||||
.annotate(taken=Count("id"))
|
||||
}
|
||||
pulse_by_day = {
|
||||
reading.date: reading.bpm
|
||||
for reading in PulseReading.objects.filter(date__gte=since)
|
||||
}
|
||||
note_by_day = {
|
||||
note.date: note.body for note in DailyNote.objects.filter(date__gte=since)
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
"date": day,
|
||||
"taken": taken_by_day.get(day, 0),
|
||||
"expected": expected,
|
||||
"complete": expected > 0 and taken_by_day.get(day, 0) == expected,
|
||||
"bpm": pulse_by_day.get(day),
|
||||
"note": note_by_day.get(day, ""),
|
||||
}
|
||||
for day in (today - timedelta(days=offset) for offset in range(days))
|
||||
]
|
||||
|
||||
|
||||
def pulse_trend(readings: list[PulseReading]) -> Optional[float]:
|
||||
"""Least-squares slope in bpm/day: ~0 means stable, positive means climbing."""
|
||||
if len(readings) < 2:
|
||||
@@ -202,6 +258,12 @@ def get_day(request: HttpRequest, date: Optional[date_type] = None):
|
||||
return build_day(date or timezone.localdate())
|
||||
|
||||
|
||||
@router.get("/recap", response=list[RecapDayOut])
|
||||
def get_recap(request: HttpRequest, days: int = 14):
|
||||
"""Day-by-day recap, newest first: were all doses given, pulse and note."""
|
||||
return build_recap(days)
|
||||
|
||||
|
||||
@router.post("/doses", response=DayOut)
|
||||
def log_dose(request: HttpRequest, payload: DoseLogIn):
|
||||
"""Tick a dose off (or untick it). Idempotent per (dose, day)."""
|
||||
|
||||
Reference in New Issue
Block a user