| """ |
| med_tracker.py — Medication CRUD with persistent JSON storage. |
| Each record is scoped to a care profile via profile_id. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import json |
| import os |
| import uuid |
| from datetime import datetime |
| from typing import List, Dict, Any, Optional |
|
|
| from config import MEDICATIONS_JSON |
|
|
|
|
| def _load() -> List[Dict[str, Any]]: |
| if MEDICATIONS_JSON.exists(): |
| try: |
| with open(MEDICATIONS_JSON, "r", encoding="utf-8") as f: |
| return json.load(f) |
| except Exception: |
| return [] |
| return [] |
|
|
|
|
| def _save(meds: List[Dict[str, Any]]): |
| MEDICATIONS_JSON.parent.mkdir(parents=True, exist_ok=True) |
| with open(MEDICATIONS_JSON, "w", encoding="utf-8") as f: |
| json.dump(meds, f, indent=2, ensure_ascii=False) |
| f.flush() |
| os.fsync(f.fileno()) |
|
|
|
|
| def _new_id() -> str: |
| return str(uuid.uuid4()) |
|
|
|
|
| def _now() -> str: |
| return datetime.now().isoformat(timespec="seconds") |
|
|
|
|
| def _for_profile(meds: List[Dict[str, Any]], profile_id: Optional[str]) -> List[Dict[str, Any]]: |
| if not profile_id: |
| return [] |
| return [m for m in meds if m.get("profile_id") == profile_id] |
|
|
|
|
| def add_medication( |
| name: str, |
| dosage: str = "", |
| frequency: str = "", |
| status: str = "current", |
| side_effects: str = "", |
| personal_notes: str = "", |
| category: str = "", |
| profile_id: str = "", |
| ) -> Dict[str, Any]: |
| """Add a new medication for profile_id. status: current | past.""" |
| if not profile_id: |
| raise ValueError("No active care profile.") |
| if not name.strip(): |
| raise ValueError("Medication name is required.") |
|
|
| status = status if status in ("current", "past") else "current" |
|
|
| med = { |
| "id": _new_id(), |
| "profile_id": profile_id, |
| "name": name.strip(), |
| "dosage": dosage.strip(), |
| "frequency": frequency.strip(), |
| "status": status, |
| "side_effects": side_effects.strip(), |
| "personal_notes": personal_notes.strip(), |
| "category": category.strip(), |
| "created_at": _now(), |
| "updated_at": _now(), |
| } |
|
|
| meds = _load() |
| meds.append(med) |
| _save(meds) |
| return med |
|
|
|
|
| def edit_medication( |
| med_id: str, |
| name: Optional[str] = None, |
| dosage: Optional[str] = None, |
| frequency: Optional[str] = None, |
| status: Optional[str] = None, |
| side_effects: Optional[str] = None, |
| personal_notes: Optional[str] = None, |
| category: Optional[str] = None, |
| ) -> Optional[Dict[str, Any]]: |
| meds = _load() |
| for med in meds: |
| if med["id"] == med_id: |
| if name is not None: |
| med["name"] = name.strip() |
| if dosage is not None: |
| med["dosage"] = dosage.strip() |
| if frequency is not None: |
| med["frequency"] = frequency.strip() |
| if status is not None and status in ("current", "past"): |
| med["status"] = status |
| if side_effects is not None: |
| med["side_effects"] = side_effects.strip() |
| if personal_notes is not None: |
| med["personal_notes"] = personal_notes.strip() |
| if category is not None: |
| med["category"] = category.strip() |
| med["updated_at"] = _now() |
| _save(meds) |
| return med |
| return None |
|
|
|
|
| def delete_medication(med_id: str) -> bool: |
| meds = _load() |
| new_meds = [m for m in meds if m["id"] != med_id] |
| if len(new_meds) == len(meds): |
| return False |
| _save(new_meds) |
| return True |
|
|
|
|
| def get_medications(filter: str = "all", profile_id: Optional[str] = None) -> List[Dict[str, Any]]: |
| meds = _for_profile(_load(), profile_id) |
| if filter in ("current", "past"): |
| meds = [m for m in meds if m.get("status") == filter] |
| return sorted(meds, key=lambda m: m.get("name", "").lower()) |
|
|
|
|
| def get_medication_by_id(med_id: str) -> Optional[Dict[str, Any]]: |
| for m in _load(): |
| if m["id"] == med_id: |
| return m |
| return None |
|
|
|
|
| def delete_for_profile(profile_id: str) -> int: |
| meds = _load() |
| kept = [m for m in meds if m.get("profile_id") != profile_id] |
| deleted = len(meds) - len(kept) |
| if deleted: |
| _save(kept) |
| return deleted |
|
|