""" 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)