pillpal / server.py
vivek gangadharan
first
b9aa4e9
Raw
History Blame Contribute Delete
3.98 kB
from __future__ import annotations
import base64
import io
import os
from datetime import date
from gradio import Server
from fastapi.responses import HTMLResponse
import logic
import storage
import vision
app = Server()
HERE = os.path.dirname(os.path.abspath(__file__))
def _fmt_qty(q):
if q is None:
return None
return int(q) if float(q).is_integer() else q
def _payload() -> dict:
meds = storage.load_meds()
rows = []
for i, m in enumerate(meds):
rows.append({
"i": i, "name": m.drug_name, "dose": m.dose, "instruction": m.frequency_text,
"quantity": _fmt_qty(m.quantity),
"times": [logic.format_time_12h(t) for t in (m.schedule_times or [])],
"run_out": m.run_out_date, "refill_or_expiry": m.refill_or_expiry_date,
"warning": m.refill_warning, "on_track": not m.refill_warning,
})
timed, as_needed = logic.build_daily_agenda(meds)
schedule = [{"time": logic.format_time_12h(t), "items": items} for t, items in timed]
today = date.today().strftime("%A, %B %d, %Y").replace(" 0", " ")
return {"meds": rows, "schedule": schedule, "as_needed": as_needed, "today": today}
def _decode(data_url: str):
if not data_url or "," not in data_url:
return None
try:
raw = base64.b64decode(data_url.split(",", 1)[1])
from PIL import Image
return Image.open(io.BytesIO(raw)).convert("RGB")
except Exception:
return None
@app.api(name="state")
def state() -> dict:
return _payload()
@app.api(name="read_label")
def read_label(image_data: str = "") -> dict:
img = _decode(image_data)
if img is None:
return {"ok": False, "message": "Upload a photo of the label first."}
try:
raw = vision.extract_label(img)
data = logic.safe_parse(raw)
except Exception as exc:
return {"ok": False, "message": f"Couldn't read the label ({exc}). Enter the details below."}
if not data:
return {"ok": False, "message": "Couldn't read the label clearly — try a sharper, well-lit photo, or enter it below."}
return {"ok": True, "fields": {
"drug_name": data.get("drug_name") or "", "dose": data.get("dose") or "",
"frequency_text": data.get("frequency_text") or "",
"quantity": _fmt_qty(data.get("quantity")) or "",
"refill_or_expiry_date": data.get("refill_or_expiry_date") or ""}}
@app.api(name="save_med")
def save_med(drug_name: str = "", dose: str = "", frequency_text: str = "",
quantity: str = "", refill_or_expiry_date: str = "", index: int = -1) -> dict:
if not (drug_name or "").strip():
return {"ok": False, "message": "A medication name is required.", **_payload()}
data = {"drug_name": drug_name.strip(), "dose": (dose or "").strip() or None,
"frequency_text": (frequency_text or "").strip() or None,
"quantity": logic._coerce_quantity(quantity),
"refill_or_expiry_date": (refill_or_expiry_date or "").strip() or None}
med = logic.make_medication(data)
meds = storage.load_meds()
if 0 <= int(index) < len(meds):
meds[int(index)] = med
else:
meds.append(med)
storage.save_meds(meds)
return {"ok": True, **_payload()}
@app.api(name="delete_med")
def delete_med(index: int = -1) -> dict:
meds = storage.load_meds()
if 0 <= int(index) < len(meds):
meds.pop(int(index)); storage.save_meds(meds)
return {"ok": True, **_payload()}
@app.api(name="clear_all")
def clear_all() -> dict:
storage.save_meds([])
return {"ok": True, **_payload()}
@app.get("/", response_class=HTMLResponse)
async def homepage():
with open(os.path.join(HERE, "index.html"), "r", encoding="utf-8") as f:
return f.read()
if __name__ == "__main__":
os.environ.setdefault("GRADIO_SERVER_NAME", "0.0.0.0")
os.environ.setdefault("GRADIO_SERVER_PORT", "7860")
app.launch(show_error=True)