Files
takeyourmeds/server/slots.py
T
Frank Schwenk eaa019087e debug line test
2026-06-09 21:21:52 +02:00

119 lines
3.7 KiB
Python

from __future__ import annotations
from datetime import datetime, timedelta
from typing import Any
from zoneinfo import ZoneInfo
import aiosqlite
from server.config_loader import MedsConfig, Slot, load_meds_config, slot_datetime, today_str
from server.db import utc_now_iso
async def get_log_for_day(db: aiosqlite.Connection, day: str) -> dict[str, dict[str, Any]]:
cur = await db.execute(
"SELECT slot_id, status, logged_at, source FROM intake_log WHERE day = ? ORDER BY logged_at DESC",
(day,),
)
rows = await cur.fetchall()
await cur.close()
result: dict[str, dict[str, Any]] = {}
for slot_id, status, logged_at, source in rows:
if slot_id not in result:
result[slot_id] = {"status": status, "logged_at": logged_at, "source": source}
return result
async def get_snooze_for_day(db: aiosqlite.Connection, day: str) -> dict[str, str]:
cur = await db.execute("SELECT slot_id, snooze_until FROM snooze WHERE day = ?", (day,))
rows = await cur.fetchall()
await cur.close()
return {slot_id: snooze_until for slot_id, snooze_until in rows}
def compute_status(
slot: Slot,
now: datetime,
tz: ZoneInfo,
log_entry: dict[str, Any] | None,
snooze_until: str | None,
) -> str:
if log_entry:
return log_entry["status"]
slot_dt = slot_datetime(now, slot, tz)
window_end = slot_dt + timedelta(minutes=slot.reminder_window_minutes)
if snooze_until:
try:
snooze_dt = datetime.fromisoformat(snooze_until)
if now < snooze_dt:
return "snoozed"
except ValueError:
pass
if now < slot_dt:
return "upcoming"
if now <= window_end:
return "pending"
return "overdue"
async def build_today(db: aiosqlite.Connection, config: MedsConfig | None = None) -> dict[str, Any]:
config = config or load_meds_config()
tz = config.timezone
now = datetime.now(tz)
day = today_str(tz)
logs = await get_log_for_day(db, day)
snoozes = await get_snooze_for_day(db, day)
slots_out: list[dict[str, Any]] = []
for slot in config.slots:
log_entry = logs.get(slot.id)
snooze_until = snoozes.get(slot.id)
status = compute_status(slot, now, tz, log_entry, snooze_until)
slot_dt = slot_datetime(now, slot, tz)
window_end = slot_dt + timedelta(minutes=slot.reminder_window_minutes)
slots_out.append(
{
"id": slot.id,
"time": slot.time.strftime("%H:%M"),
"label": slot.label,
"meds": [{"name": m.name, "dose": m.dose} for m in slot.meds],
"status": status,
"logged_at": log_entry["logged_at"] if log_entry else None,
"window_end": window_end.isoformat(),
"snooze_until": snooze_until,
}
)
return {"day": day, "timezone": str(tz), "now": now.isoformat(), "slots": slots_out}
async def mark_missed_for_overdue(db: aiosqlite.Connection, config: MedsConfig | None = None) -> list[str]:
from server.db import new_id
config = config or load_meds_config()
tz = config.timezone
now = datetime.now(tz)
day = today_str(tz)
logs = await get_log_for_day(db, day)
marked: list[str] = []
for slot in config.slots:
if slot.id in logs:
continue
status = compute_status(slot, now, tz, None, None)
if status == "overdue":
await db.execute(
"INSERT INTO intake_log (id, slot_id, day, status, logged_at, source) VALUES (?, ?, ?, ?, ?, ?)",
(new_id(), slot.id, day, "missed", utc_now_iso(), "system"),
)
marked.append(slot.id)
if marked:
await db.commit()
return marked