Upload folder using huggingface_hub
Browse files- app.py +27 -7
- whatchanged/ui/log_tab.py +47 -17
- whatchanged/ui/report_tab.py +1 -1
- whatchanged/ui/trends_tab.py +1 -1
app.py
CHANGED
|
@@ -44,8 +44,7 @@ def make_backend():
|
|
| 44 |
|
| 45 |
def maybe_seed(conn) -> None:
|
| 46 |
"""On the demo Space (WC_SEED_DEMO=1) only, populate an empty DB with a realistic
|
| 47 |
-
60-day decline arc so
|
| 48 |
-
users (no env var) always start with a clean log."""
|
| 49 |
if os.environ.get("WC_SEED_DEMO") != "1":
|
| 50 |
return
|
| 51 |
if conn.execute("SELECT COUNT(*) FROM entries").fetchone()[0] > 0:
|
|
@@ -58,15 +57,36 @@ def maybe_seed(conn) -> None:
|
|
| 58 |
print(f"[seed] skipped: {e}")
|
| 59 |
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
def build_app():
|
| 62 |
conn = init_db(DB_PATH)
|
| 63 |
maybe_seed(conn)
|
| 64 |
backend = make_backend()
|
| 65 |
-
with gr.Blocks(title="What Changed") as demo:
|
| 66 |
-
gr.
|
| 67 |
-
"# What Changed\n"
|
| 68 |
-
"*A private, local tracker to show the doctor what's been happening at home. "
|
| 69 |
-
"Not medical advice.*")
|
| 70 |
build_log_tab(conn, backend)
|
| 71 |
build_trends_tab(conn)
|
| 72 |
build_report_tab(conn, backend)
|
|
|
|
| 44 |
|
| 45 |
def maybe_seed(conn) -> None:
|
| 46 |
"""On the demo Space (WC_SEED_DEMO=1) only, populate an empty DB with a realistic
|
| 47 |
+
60-day decline arc so Trends/Report are non-empty on first open."""
|
|
|
|
| 48 |
if os.environ.get("WC_SEED_DEMO") != "1":
|
| 49 |
return
|
| 50 |
if conn.execute("SELECT COUNT(*) FROM entries").fetchone()[0] > 0:
|
|
|
|
| 57 |
print(f"[seed] skipped: {e}")
|
| 58 |
|
| 59 |
|
| 60 |
+
# --- Off-Brand custom look: calm health palette, Inter, branded header, no Gradio footer ---
|
| 61 |
+
THEME = gr.themes.Soft(
|
| 62 |
+
primary_hue="teal", secondary_hue="cyan", neutral_hue="slate",
|
| 63 |
+
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
| 64 |
+
)
|
| 65 |
+
CSS = """
|
| 66 |
+
.gradio-container {max-width: 880px !important; margin: 0 auto !important;}
|
| 67 |
+
#wc-header {background: linear-gradient(135deg,#0d9488,#0e7490); color:#fff;
|
| 68 |
+
padding:22px 26px; border-radius:16px; margin-bottom:6px;}
|
| 69 |
+
#wc-header h1 {margin:0; font-size:26px; font-weight:700; color:#fff;}
|
| 70 |
+
#wc-header p {margin:6px 0 0; opacity:.92; font-size:14px; color:#fff;}
|
| 71 |
+
#wc-header .wc-pill {display:inline-block; background:rgba(255,255,255,.18);
|
| 72 |
+
padding:3px 11px; border-radius:999px; font-size:12px; margin-top:11px;}
|
| 73 |
+
footer {display:none !important;}
|
| 74 |
+
"""
|
| 75 |
+
HEADER = (
|
| 76 |
+
'<div id="wc-header"><h1>📋 What Changed</h1>'
|
| 77 |
+
"<p>A private, on-device Parkinson's diary — describe the day, and a local fine-tuned 1B "
|
| 78 |
+
"structures it into a doctor-ready summary.</p>"
|
| 79 |
+
'<span class="wc-pill">Runs 100% locally · fine-tuned 1B · llama.cpp · not medical advice</span>'
|
| 80 |
+
"</div>"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
def build_app():
|
| 85 |
conn = init_db(DB_PATH)
|
| 86 |
maybe_seed(conn)
|
| 87 |
backend = make_backend()
|
| 88 |
+
with gr.Blocks(title="What Changed", theme=THEME, css=CSS) as demo:
|
| 89 |
+
gr.HTML(HEADER)
|
|
|
|
|
|
|
|
|
|
| 90 |
build_log_tab(conn, backend)
|
| 91 |
build_trends_tab(conn)
|
| 92 |
build_report_tab(conn, backend)
|
whatchanged/ui/log_tab.py
CHANGED
|
@@ -2,27 +2,53 @@ from __future__ import annotations
|
|
| 2 |
import datetime
|
| 3 |
import gradio as gr
|
| 4 |
from whatchanged.models import Entry, DOMAINS, EVENTS
|
| 5 |
-
from whatchanged.db import upsert_entry
|
| 6 |
from whatchanged.inference import extract_note
|
| 7 |
from whatchanged.trends import DOMAIN_LABELS, EVENT_LABELS
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def build_log_tab(conn, backend):
|
| 11 |
-
with gr.Tab("Log Today"):
|
| 12 |
-
gr.Markdown(
|
|
|
|
|
|
|
| 13 |
date = gr.Textbox(label="Date", value=str(datetime.date.today()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
sliders = {}
|
| 15 |
for d in DOMAINS:
|
| 16 |
-
sliders[d] = gr.Slider(1, 5, step=1, value=3, label=DOMAIN_LABELS[d]
|
|
|
|
|
|
|
|
|
|
| 17 |
counters = {}
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
| 24 |
save_btn = gr.Button("Save today", variant="primary")
|
| 25 |
-
|
| 26 |
|
| 27 |
def do_extract(note_text, *slider_vals):
|
| 28 |
data = extract_note(note_text, backend)
|
|
@@ -31,20 +57,24 @@ def build_log_tab(conn, backend):
|
|
| 31 |
if d in data:
|
| 32 |
new[i] = data[d]
|
| 33 |
counter_updates = [data.get(ev, gr.update()) for ev in EVENTS]
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
extract_btn.click(
|
| 38 |
do_extract,
|
| 39 |
inputs=[note] + [sliders[d] for d in DOMAINS],
|
| 40 |
outputs=[sliders[d] for d in DOMAINS] + [counters[ev] for ev in EVENTS]
|
| 41 |
-
+ [
|
| 42 |
)
|
| 43 |
|
| 44 |
def do_save(date_val, note_text, *vals):
|
| 45 |
n = len(DOMAINS)
|
| 46 |
-
domain_vals = vals[:n]
|
| 47 |
-
event_vals = vals[n:]
|
| 48 |
|
| 49 |
def _to_int(v):
|
| 50 |
return int(v) if v not in (None, "") else 0
|
|
@@ -59,5 +89,5 @@ def build_log_tab(conn, backend):
|
|
| 59 |
do_save,
|
| 60 |
inputs=[date, note] + [sliders[d] for d in DOMAINS]
|
| 61 |
+ [counters[ev] for ev in EVENTS],
|
| 62 |
-
outputs=[
|
| 63 |
)
|
|
|
|
| 2 |
import datetime
|
| 3 |
import gradio as gr
|
| 4 |
from whatchanged.models import Entry, DOMAINS, EVENTS
|
| 5 |
+
from whatchanged.db import upsert_entry
|
| 6 |
from whatchanged.inference import extract_note
|
| 7 |
from whatchanged.trends import DOMAIN_LABELS, EVENT_LABELS
|
| 8 |
|
| 9 |
+
# Plain-language anchors so a caregiver knows what each 1-5 rating means (5 = best). These
|
| 10 |
+
# rate the *quality* of the day, not a number of hours/etc.
|
| 11 |
+
ANCHORS = {
|
| 12 |
+
"mobility": "1 = could barely move · 5 = moved freely & steadily",
|
| 13 |
+
"tremor": "1 = severe tremor · 5 = almost no tremor",
|
| 14 |
+
"stiffness": "1 = very stiff & rigid · 5 = loose, not stiff",
|
| 15 |
+
"mood": "1 = very low · 5 = bright & cheerful",
|
| 16 |
+
"sleep": "1 = barely slept · 5 = slept very well",
|
| 17 |
+
"alertness": "1 = very confused & foggy · 5 = sharp & clear",
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
|
| 21 |
def build_log_tab(conn, backend):
|
| 22 |
+
with gr.Tab("📝 Log Today"):
|
| 23 |
+
gr.Markdown(
|
| 24 |
+
"### Just describe the day — the on-device AI fills the form, you review.\n"
|
| 25 |
+
"Type a sentence or two about how your parent did today, then tap **Auto-fill**.")
|
| 26 |
date = gr.Textbox(label="Date", value=str(datetime.date.today()))
|
| 27 |
+
note = gr.Textbox(
|
| 28 |
+
label="Today's note", lines=3, autofocus=True,
|
| 29 |
+
placeholder='e.g. "Rough day — Dad froze in the doorway twice, his meds wore off '
|
| 30 |
+
'before lunch, and he barely slept."')
|
| 31 |
+
extract_btn = gr.Button("✨ Auto-fill from note (local AI)",
|
| 32 |
+
variant="primary", size="lg")
|
| 33 |
+
extract_status = gr.Markdown()
|
| 34 |
+
|
| 35 |
+
gr.Markdown("#### Review & adjust")
|
| 36 |
sliders = {}
|
| 37 |
for d in DOMAINS:
|
| 38 |
+
sliders[d] = gr.Slider(1, 5, step=1, value=3, label=DOMAIN_LABELS[d],
|
| 39 |
+
info=ANCHORS[d])
|
| 40 |
+
|
| 41 |
+
gr.Markdown("**Events today** — how many times each happened")
|
| 42 |
counters = {}
|
| 43 |
+
with gr.Row():
|
| 44 |
+
for ev in EVENTS[:3]:
|
| 45 |
+
counters[ev] = gr.Number(value=0, precision=0, label=EVENT_LABELS[ev], minimum=0)
|
| 46 |
+
with gr.Row():
|
| 47 |
+
for ev in EVENTS[3:]:
|
| 48 |
+
counters[ev] = gr.Number(value=0, precision=0, label=EVENT_LABELS[ev], minimum=0)
|
| 49 |
+
|
| 50 |
save_btn = gr.Button("Save today", variant="primary")
|
| 51 |
+
save_status = gr.Markdown()
|
| 52 |
|
| 53 |
def do_extract(note_text, *slider_vals):
|
| 54 |
data = extract_note(note_text, backend)
|
|
|
|
| 57 |
if d in data:
|
| 58 |
new[i] = data[d]
|
| 59 |
counter_updates = [data.get(ev, gr.update()) for ev in EVENTS]
|
| 60 |
+
if backend is None:
|
| 61 |
+
msg = "⚠️ The local model isn't loaded right now — enter values manually below."
|
| 62 |
+
elif not note_text.strip():
|
| 63 |
+
msg = "Write a note above first, then tap Auto-fill."
|
| 64 |
+
else:
|
| 65 |
+
msg = f"✓ Filled **{len(data)}** field(s) from your note — please review below."
|
| 66 |
+
return (*new, *counter_updates, msg)
|
| 67 |
|
| 68 |
extract_btn.click(
|
| 69 |
do_extract,
|
| 70 |
inputs=[note] + [sliders[d] for d in DOMAINS],
|
| 71 |
outputs=[sliders[d] for d in DOMAINS] + [counters[ev] for ev in EVENTS]
|
| 72 |
+
+ [extract_status],
|
| 73 |
)
|
| 74 |
|
| 75 |
def do_save(date_val, note_text, *vals):
|
| 76 |
n = len(DOMAINS)
|
| 77 |
+
domain_vals, event_vals = vals[:n], vals[n:]
|
|
|
|
| 78 |
|
| 79 |
def _to_int(v):
|
| 80 |
return int(v) if v not in (None, "") else 0
|
|
|
|
| 89 |
do_save,
|
| 90 |
inputs=[date, note] + [sliders[d] for d in DOMAINS]
|
| 91 |
+ [counters[ev] for ev in EVENTS],
|
| 92 |
+
outputs=[save_status],
|
| 93 |
)
|
whatchanged/ui/report_tab.py
CHANGED
|
@@ -6,7 +6,7 @@ from whatchanged.report import build_report, render_report_html
|
|
| 6 |
|
| 7 |
|
| 8 |
def build_report_tab(conn, backend):
|
| 9 |
-
with gr.Tab("Doctor Report"):
|
| 10 |
gr.Markdown("### Generate a one-page summary to share with the doctor.")
|
| 11 |
with gr.Row():
|
| 12 |
start = gr.Textbox(
|
|
|
|
| 6 |
|
| 7 |
|
| 8 |
def build_report_tab(conn, backend):
|
| 9 |
+
with gr.Tab("🩺 Doctor Report"):
|
| 10 |
gr.Markdown("### Generate a one-page summary to share with the doctor.")
|
| 11 |
with gr.Row():
|
| 12 |
start = gr.Textbox(
|
whatchanged/ui/trends_tab.py
CHANGED
|
@@ -7,7 +7,7 @@ from whatchanged.trends import detect_findings
|
|
| 7 |
|
| 8 |
|
| 9 |
def build_trends_tab(conn):
|
| 10 |
-
with gr.Tab("Trends"):
|
| 11 |
window = gr.Radio([7, 30, 90], value=30, label="Window (days)")
|
| 12 |
refresh = gr.Button("Refresh", variant="primary")
|
| 13 |
plot = gr.Plot()
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
def build_trends_tab(conn):
|
| 10 |
+
with gr.Tab("📈 Trends"):
|
| 11 |
window = gr.Radio([7, 30, 90], value=30, label="Window (days)")
|
| 12 |
refresh = gr.Button("Refresh", variant="primary")
|
| 13 |
plot = gr.Plot()
|