maai / app.py
Csandal17
Add SOCRATES gap-map to clinician PDF
e4bc8cb
Raw
History Blame Contribute Delete
7.49 kB
"""
app.py — Maai's interface.
A minimal Gradio skin over the proven pipeline:
paste a symptom description in any language -> see the advocacy
record -> download the clinician-ready PDF.
Function today; theming (palette, Fraunces/Inter) is weekend work.
"""
import gradio as gr
from chain import build_record
from make_pdf import make_pdf
from speak import speak_record
from socrates import annotate_socrates
from contribute import make_contribution, save_contribution
from aggregate import aggregate
def run_maai(description: str):
"""Full pipeline: description -> record -> PDF. Returns display text + PDF path."""
if not description or not description.strip():
return "Please describe what you've been experiencing.", None, None
record = build_record(description)
lines = [
f"Language detected: {record['language_detected']}",
"",
"HER WORDS → CLINICAL TERMS",
"",
]
for item in record["items"]:
lines.append(f'"{item["verbatim"]}"')
lines.append(f" → {item['clinical']}")
lines.append("")
annotated = annotate_socrates(record)
lines.append("")
lines.append("CLINICAL FRAMEWORK (SOCRATES)")
lines.append("")
for item in annotated["items"]:
if item["dimensions"]:
lines.append(f'"{item["verbatim"]}" — {", ".join(item["dimensions"])}')
lines.append("")
lines.append("Not yet described — your doctor may ask about:")
for dim in annotated["not_yet_described"]:
lines.append(f" · {dim}")
pdf_path = make_pdf(record, annotated)
return "\n".join(lines), pdf_path, record
css = """
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600&display=swap');
.gradio-container {
background-color: #F7F4EF !important;
max-width: 780px !important;
margin: 0 auto !important;
}
#header { text-align: center; }
#header h1 {
font-family: 'Fraunces', serif !important;
font-size: 3.2em !important;
font-weight: 600 !important;
color: #2B2A26 !important;
margin-bottom: 0.1em !important;
}
.block, .form, textarea, input {
border-radius: 12px !important;
border-color: #DCCFBC !important;
}
button.primary {
font-family: 'Fraunces', serif !important;
font-size: 1.1em !important;
}
"""
theme = gr.themes.Soft(
primary_hue=gr.themes.Color(
c50="#F7F4EF", c100="#EFE9DF", c200="#DCCFBC", c300="#C6B29A",
c400="#A98F73", c500="#8C7355", c600="#7A6349", c700="#65523C",
c800="#514230", c900="#3D3124", c950="#2B2A26",
),
neutral_hue="stone",
font=gr.themes.GoogleFont("Inter"),
font_mono=gr.themes.GoogleFont("Inter"),
)
with gr.Blocks(title="Maai", theme=theme, css=css) as demo:
gr.Markdown(
"# Maai\n"
"*See what time reveals.*\n\n"
"**The AI health advocate for endometriosis**",
elem_id="header",
)
gr.Markdown(
"Maai means the meaningful interval between things — the space where "
"health patterns emerge.\n\n"
"Endometriosis takes an average of **9 years and 4 months** to diagnose "
"in the UK — and 11 years for women from ethnically diverse communities. "
"83% of women were told by a healthcare practitioner they were making "
"a fuss about nothing. Almost half visited their GP ten or more times "
"before anyone joined the dots.\n\n"
"Maai helps you be harder to dismiss. Describe what you've been "
"experiencing over recent weeks or months, in your own words, in any "
"language. Maai maps your words to clinical terms a doctor recognises — "
"never replacing them, never drawing conclusions. Bring a record to "
"every appointment; the pattern builds. The clinician interprets. "
"Maai helps you be heard.\n\n"
"**Maai is for patterns over time, not a medical emergency. If you have "
"severe or sudden symptoms right now, call 111 — or 999 if it's urgent.**"
)
description = gr.Textbox(
label="In your own words",
placeholder="e.g. For months I've had a deep dragging pain low in my belly, not just during my period, and I'm exhausted all the time...",
lines=5,
)
submit = gr.Button("Prepare my record")
record_display = gr.Textbox(label="Your advocacy record", lines=14)
pdf_file = gr.File(label="Download for your appointment")
record_state = gr.State()
listen = gr.Button("Hear my record read aloud")
audio_out = gr.Audio(label="Your record, read back", type="filepath")
submit.click(fn=run_maai, inputs=description,
outputs=[record_display, pdf_file, record_state])
def read_aloud(record):
if not record:
raise gr.Error("Prepare a record first, then listen.")
return speak_record(record)
listen.click(fn=read_aloud, inputs=record_state, outputs=audio_out)
gr.Markdown("---")
gr.Markdown("### Contribute to what women are revealing")
consent = gr.Checkbox(
label=(
"Contribute your pattern (anonymous) — adds your symptom pattern, "
"never your words, name, or any identifying detail, to a shared "
"dataset of women's heart-health experiences."
),
value=False,
)
age_band = gr.Dropdown(
choices=["not_given", "25-34", "35-44", "45-54", "55-64", "65-74", "75+"],
value="not_given",
label="Age band (optional)",
)
contribute_btn = gr.Button("Contribute my pattern")
feedback = gr.Markdown()
def contribute(record, consented, band):
if not record:
raise gr.Error("Prepare a record first.")
if not consented:
raise gr.Error("Tick the consent box if you'd like to contribute — it's entirely optional.")
total = save_contribution(make_contribution(record, age_band=band))
view = aggregate()
top = next(iter(view["symptom_prevalence"]))
return (
f"**Your pattern joins {total - 1} others.** Together they're showing "
f"how often *{top}* appears in women's patterns — years before "
f"diagnosis. Thank you for helping make it visible."
)
contribute_btn.click(fn=contribute, inputs=[record_state, consent, age_band], outputs=feedback)
gr.Markdown("---")
gr.Markdown("### What women are revealing")
gr.Markdown(
"*Seeded with representative synthetic data to show what the aggregate "
"view reveals at scale. Descriptive only — Maai counts and reveals, "
"never predicts.*"
)
def render_aggregate():
view = aggregate()
if view["total"] == 0:
return "No contributions yet."
lines = [f"**{view['total']} contributed patterns**\n"]
lines.append("| Symptom | Appears in |")
lines.append("|---|---|")
for symptom, pct in view["symptom_prevalence"].items():
lines.append(f"| {symptom} | {pct}% |")
langs = ", ".join(view["languages"].keys())
lines.append(f"\nContributed in **{len(view['languages'])} languages**: {langs}")
return "\n".join(lines)
aggregate_display = gr.Markdown(render_aggregate())
refresh = gr.Button("Refresh the collective picture")
refresh.click(fn=render_aggregate, outputs=aggregate_display)
if __name__ == "__main__":
demo.launch(ssr_mode=False)