Vital / ui /dashboard.py
eddyejembi's picture
Upload folder using huggingface_hub
4e88df3 verified
Raw
History Blame Contribute Delete
8.85 kB
"""Home tab — greeting, checklist, metrics, and daily check-in."""
from datetime import date, datetime
import gradio as gr
from db import queries
from vital_types.db import DailyLogSchemaField, MedicationLogEntry
def _period_label(now: datetime) -> str:
"""Return morning, afternoon, or evening for the current hour."""
if now.hour < 12:
return "morning"
if now.hour < 17:
return "afternoon"
return "evening"
def _greeting_text() -> str:
"""Build the dashboard greeting line."""
profile = queries.get_profile()
name = profile.name if profile and profile.name.strip() else "there"
today = date.today().strftime("%A, %B %d, %Y")
period = _period_label(datetime.now())
return f"## Good {period}, {name}\n{today}"
def _briefing_text() -> str:
"""Return the cached morning briefing or a short fallback."""
cached = queries.get_morning_briefing_cache()
if cached:
return cached
profile = queries.get_profile()
if profile is None:
return "Your coach briefing will appear here after the morning greeting."
return (
f"Welcome back, {profile.name}. Use today's check-in below to log how you're feeling."
)
def _metrics_markdown() -> str:
"""Build the three-column metrics summary as markdown."""
today = date.today()
logs = {entry.field_id: entry.value for entry in queries.get_daily_logs_for_date(today)}
medications = queries.get_medications_for_date(today)
taken_count = sum(1 for med in medications if med.taken)
total_meds = len(medications)
primary_metric = "—"
schema = queries.get_daily_log_schema()
if schema:
first_field = schema[0]
primary_metric = logs.get(first_field.field_id, "—")
water_value = logs.get("water_cups", "0")
return (
f"| Health metric | Water (cups) | Meds taken |\n"
f"|---|---|---|\n"
f"| **{primary_metric}** | **{water_value}** | **{taken_count}/{total_meds}** |"
)
def _schedule_markdown() -> str:
"""Build today's LLM-generated schedule summary."""
plan = queries.get_daily_plan(date.today())
if plan is None:
return "_Today's schedule will appear after your wake time._"
lines = [f"_{plan.summary}_\n"]
for job in plan.jobs:
extra = ""
if job.volume_ml:
extra = f" ({job.volume_ml}ml)"
if job.exercise_type:
extra = f" — {job.exercise_type}, {job.duration_minutes} min"
lines.append(f"- **{job.time}** [{job.type}] {job.message}{extra}")
return "\n".join(lines)
def _medication_choice_label(medication: MedicationLogEntry) -> str:
"""Format one medication row for the interactive checklist."""
return (
f"{medication.scheduled_time} — "
f"{medication.medication_name} ({medication.dose})"
)
def empty_medication_checklist_update() -> dict[str, object]:
"""Return a safe CheckboxGroup update when the dashboard is hidden."""
return {
"choices": [],
"value": [],
"label": "Medications today",
"info": "",
}
def medication_checklist_update() -> dict[str, object]:
"""Build a gr.update payload for today's medication checklist."""
medications = queries.get_medications_for_date(date.today())
if not medications:
return {
"choices": [],
"value": [],
"label": "Medications today",
"info": "No medications scheduled today.",
}
choices = [_medication_choice_label(med) for med in medications]
selected = [
label for med, label in zip(medications, choices, strict=True) if med.taken
]
return {
"choices": choices,
"value": selected,
"label": "Medications today",
"info": "Check each dose when you take it.",
}
def get_dashboard_updates() -> tuple[str, str, str, dict[str, object]]:
"""Return fresh dashboard content from the database."""
return (
_greeting_text(),
_briefing_text(),
_metrics_markdown(),
medication_checklist_update(),
)
def _render_checkin_field(field: DailyLogSchemaField) -> gr.components.Component:
"""Render one dynamic check-in field from schema."""
if field.type == "scale_1_10":
return gr.Slider(
minimum=1,
maximum=10,
step=1,
value=5,
label=field.label,
info=field.reason,
)
if field.type == "number":
return gr.Number(label=field.label, value=0, precision=0, info=field.reason)
if field.type == "select":
return gr.Radio(
choices=field.options,
label=field.label,
info=field.reason,
)
if field.type == "boolean":
return gr.Checkbox(label=field.label, value=False, info=field.reason)
return gr.Textbox(label=field.label, info=field.reason)
def build_dashboard_tab(initial_onboarded: bool = False) -> dict[str, object]:
"""Build the home dashboard tab components."""
greeting_text, briefing_text, metrics_text, checklist_payload = (
get_dashboard_updates() if initial_onboarded else ("", "", "", {})
)
greeting = gr.Markdown(greeting_text)
briefing = gr.Markdown(briefing_text, elem_classes=["coach-bubble"])
gr.Markdown("### Today's schedule")
schedule = gr.Markdown(_schedule_markdown() if initial_onboarded else "")
metrics = gr.Markdown(metrics_text)
gr.Markdown("### Medication checklist")
med_checklist = gr.CheckboxGroup(
choices=checklist_payload.get("choices", []),
value=checklist_payload.get("value", []),
label=str(checklist_payload.get("label", "Medications today")),
info=str(checklist_payload.get("info", "")),
)
med_status = gr.Markdown("")
gr.Markdown("### Daily check-in")
schema = queries.get_daily_log_schema() if initial_onboarded else []
checkin_fields: list[gr.components.Component] = []
checkin_field_ids: list[str] = []
with gr.Row():
for field in schema:
checkin_fields.append(_render_checkin_field(field))
checkin_field_ids.append(field.field_id)
save_status = gr.Markdown("")
save_btn = gr.Button("Save check-in", variant="primary")
refresh_btn = gr.Button("Refresh")
def sync_medications(selected_labels: list[str]) -> tuple[str, str]:
"""Persist medication checklist checkbox changes to the database."""
today = date.today()
medications = queries.get_medications_for_date(today)
selected_set = set(selected_labels)
for medication in medications:
label = _medication_choice_label(medication)
should_be_taken = label in selected_set
if medication.taken != should_be_taken:
queries.set_medication_taken_status(
today,
medication.medication_name,
medication.scheduled_time,
should_be_taken,
)
return _metrics_markdown(), "**Medication checklist updated.**"
def save_checkin(*values: object) -> str:
"""Persist dynamic check-in values to the database."""
today = date.today()
for index, field_id in enumerate(checkin_field_ids):
if index >= len(values):
continue
raw_value = values[index]
if isinstance(raw_value, bool):
text_value = "true" if raw_value else "false"
else:
text_value = str(raw_value)
queries.upsert_daily_log(today, field_id, text_value)
return "**Check-in saved.**"
def refresh_dashboard() -> tuple:
"""Reload dashboard summaries from the database."""
greeting_value, briefing_value, metrics_value, checklist_payload = (
get_dashboard_updates()
)
return (
gr.update(value=greeting_value),
gr.update(value=briefing_value),
gr.update(value=_schedule_markdown()),
gr.update(value=metrics_value),
gr.update(**checklist_payload),
"",
)
med_checklist.change(
sync_medications,
inputs=[med_checklist],
outputs=[metrics, med_status],
)
if checkin_fields:
save_btn.click(
save_checkin,
inputs=checkin_fields,
outputs=[save_status],
)
refresh_btn.click(
refresh_dashboard,
outputs=[greeting, briefing, schedule, metrics, med_checklist, med_status],
)
return {
"greeting": greeting,
"briefing": briefing,
"schedule": schedule,
"metrics": metrics,
"checklist": med_checklist,
"refresh_dashboard": refresh_dashboard,
}