"""Araras-Gemma4 — HF Space (Gradio + ZeroGPU) Clinical decision support copilot for Brazilian rare diseases. For licensed healthcare professionals. Submission: Gemma 4 Good Hackathon (Kaggle, May 2026). """ import os, re, unicodedata, json import gradio as gr import spaces from llama_cpp import Llama from huggingface_hub import hf_hub_download MODEL_REPO = "Raras-AI/araras-gemma4-e4b-v4-gguf" MODEL_FILE = "araras-gemma4-e4b-v4-Q4_K_M.gguf" # ─── Canonical ORPHA resolver (subset, 26 CEAF diseases) ─────────────── CANONICAL = [ (["esclerose multipla","multiple sclerosis","neuromielite optica","mogad","desmielinizante"], "ORPHA:802", "esclerose-multipla", "Esclerose Múltipla", ["betainterferona-1a","natalizumabe","fingolimode","ocrelizumabe"]), (["doenca falciforme","anemia falciforme","hbss","sickle cell","drepanocitose","falciforme"], "ORPHA:232", "doenca-falciforme", "Doença Falciforme", ["hidroxiureia","deferasirox","L-glutamina"]), (["ame tipo 1","sma tipo 1","spinal muscular atrophy type 1","werdnig","ame tipo i"], "ORPHA:70", "atrofia-muscular-espinhal-5q", "AME Tipo 1 (Werdnig-Hoffmann)", ["nusinersena","onasemnogeno-abeparvoveque"]), (["atrofia muscular espinhal","ame 5q","sma 5q","spinal muscular atrophy"], "ORPHA:83330", "atrofia-muscular-espinhal-5q", "Atrofia Muscular Espinhal 5q", ["nusinersena","risdiplam","onasemnogeno-abeparvoveque"]), (["hipertensao arterial pulmonar","hipertensao pulmonar","pulmonary hypertension"], "ORPHA:182090", "hipertensao-pulmonar", "Hipertensão Arterial Pulmonar", ["sildenafila","bosentana","ambrisentana","macitentana"]), (["fibrose cistica","cystic fibrosis","mucoviscidose","cftr"], "ORPHA:586", "fibrose-cistica", "Fibrose Cística", ["dornase-alfa","tobramicina inalatória","ivacaftor","lumacaftor"]), (["fenilcetonuria","pku","phenylketonuria","fenilalaninemia"], "ORPHA:716", "fenilcetonuria", "Fenilcetonúria", ["fórmula metabólica isenta de fenilalanina","sapropterina"]), (["doenca de wilson","wilson disease","kayser"], "ORPHA:905", "doenca-de-wilson", "Doença de Wilson", ["d-penicilamina","trientina","zinco-acetato"]), (["mucopolissacaridose tipo i","mps i","mps 1","hurler","iduronidase"], "ORPHA:579", "mucopolissacaridose-tipo-i", "MPS I (Hurler)", ["laronidase"]), (["mucopolissacaridose tipo ii","mps ii","mps 2","hunter","iduronato"], "ORPHA:580", "mucopolissacaridose-tipo-ii", "MPS II (Hunter)", ["idursulfase-alfa"]), (["mucopolissacaridose tipo vi","mps vi","mps 6","maroteaux","arilsulfatase b"], "ORPHA:583", "mucopolissacaridose-tipo-vi", "MPS VI (Maroteaux-Lamy)", ["galsulfase"]), (["scid","imunodeficiencia combinada","severe combined immunodeficiency"], "ORPHA:183660", None, "SCID", ["TCTH","IVIg"]), (["doenca de gaucher","gaucher disease","glucocerebrosidase"], "ORPHA:355", "doenca-de-gaucher", "Doença de Gaucher", ["imiglucerase","alfa-velaglicerase","alfa-taliglicerase"]), (["doenca de pompe","pompe disease","alfa-glicosidase"], "ORPHA:365", "doenca-de-pompe", "Doença de Pompe", ["alfa-alglicosidase"]), (["doenca de fabry","fabry disease","alfa-galactosidase"], "ORPHA:324", "doenca-de-fabry", "Doença de Fabry", ["alfa-agalsidase","beta-agalsidase","migalastate"]), (["hemoglobinuria paroxistica noturna","hpn","paroxysmal nocturnal"], "ORPHA:447", "hemoglobinuria-paroxistica-noturna", "Hemoglobinúria Paroxística Noturna", ["eculizumabe","ravulizumabe"]), (["esclerose lateral amiotrofica","ela","amyotrophic lateral sclerosis"], "ORPHA:803", "esclerose-lateral-amiotrofica", "Esclerose Lateral Amiotrófica", ["riluzol","edaravone"]), ] def norm(s): s = (s or "").lower() s = unicodedata.normalize("NFD", s) return "".join(c for c in s if unicodedata.category(c) != "Mn") def resolve(text): """Find disease mentions in model output, return list of dicts.""" text_n = norm(text) found, seen = [], set() # Per-line parse first for line in text.split("\n"): line_n = norm(line) for kws, orpha, slug, name, drugs in CANONICAL: if orpha in seen: continue if any(k in line_n for k in kws): found.append({"orpha": orpha, "name_pt": name, "pcdt_slug": slug, "ceaf_drugs": drugs}) seen.add(orpha); break # Fallback whole-text if not found: for kws, orpha, slug, name, drugs in CANONICAL: if orpha in seen: continue if any(k in text_n for k in kws): found.append({"orpha": orpha, "name_pt": name, "pcdt_slug": slug, "ceaf_drugs": drugs}) seen.add(orpha) return found[:3] _llm = None def get_llm(): global _llm if _llm is None: path = hf_hub_download(MODEL_REPO, MODEL_FILE) _llm = Llama(model_path=path, n_ctx=8192, n_gpu_layers=-1, verbose=False) return _llm SYS = """Você é ARARAS, copiloto de apoio à decisão clínica em doenças raras para profissionais de saúde no Brasil. NÃO substitui diagnóstico — apoia o(a) médico(a) na consulta. FORMATO OBRIGATÓRIO: - TEXTO SIMPLES. PROIBIDO LaTeX/$\\mathbf$/fórmulas matemáticas. - Respostas CURTAS — até 8 linhas por turno. - Lista numerada com até 3 hipóteses (nome PT-BR + 1 linha justificativa). - NUNCA invente código ORPHA ou Portaria — apenas escreva o nome canônico em PT-BR. - Máximo 2 emojis por resposta. - Para perguntas de exames/encaminhamento/CEAF: responda direto. DOENÇAS RARAS COMUNS NO BRASIL (priorizar): Doença de Gaucher, Pompe, Fabry, Mucopolissacaridoses I/II/VI, Doença Falciforme, AME 5q/tipo 1, Esclerose Múltipla, Hipertensão Pulmonar, Fibrose Cística, PKU, Wilson, SCID, HPN, ELA, ESPECIALMENTE com sinais clássicos: - Hepatoesplenomegalia + citopenias + Erlenmeyer = Gaucher - Fácies grosseira + hepatosplenomegalia = MPS - Hipotonia profunda bebê + arreflexia = AME tipo 1 - Surtos remitentes + Lhermitte = EM - Crises álgicas + HbSS = Falciforme""" @spaces.GPU(duration=90) def araras_infer(case_text, history): """Main inference — case_text is the latest user turn; history is conversation history.""" if not case_text.strip(): return "", history, "", "" llm = get_llm() # Build messages from history + new messages = [{"role": "system", "content": SYS}] for h in history: if isinstance(h, dict): messages.append({"role": h["role"], "content": h["content"]}) messages.append({"role": "user", "content": case_text}) resp = llm.create_chat_completion( messages=messages, temperature=0.5, top_p=0.95, top_k=64, repeat_penalty=1.15, max_tokens=700, ) raw = resp["choices"][0]["message"]["content"] # Resolve ORPHAs orphas = resolve(raw) orpha_md = "" sus_md = "" if orphas: orpha_md = "**🧬 Diagnósticos reconhecidos** (top-3):\n\n" + "\n".join( f"- **{o['name_pt']}** (`{o['orpha']}`)" for o in orphas ) top = orphas[0] if top.get("pcdt_slug"): pcdt_url = f"https://www.gov.br/conitec/pt-br/midias/protocolos/{top['pcdt_slug']}.pdf" sus_md = (f"### 🇧🇷 Conduta SUS sugerida ({top['name_pt']})\n\n" f"**📜 PCDT do MS**: [`{top['pcdt_slug']}`]({pcdt_url})\n\n" f"**💊 CEAF dispensa**: {', '.join(top['ceaf_drugs'])}\n\n" f"**🏥 Próximos passos**: solicitar exames confirmatórios; encaminhar ao centro de referência regional.") # Append to history (Gradio chat format) new_history = history + [ {"role": "user", "content": case_text}, {"role": "assistant", "content": raw}, ] return "", new_history, orpha_md, sus_md # ─── Pre-loaded clinical examples ────────────────────────────────────── EXAMPLES = [ "Lactente feminino, 4 meses, natural de Petrolina (PE), pais primos. Quadro de hipotonia profunda generalizada desde o nascimento, ausência de sustentação cefálica, choro fraco, dificuldade para mamar, e abolição dos reflexos osteotendíneos profundos. SpO2 95% em ar ambiente.", "Adolescente masculino, 15 anos, da zona rural de Conceição do Coité (BA), pardo. Internado há 4 horas com dor torácica intensa e dispneia. História: triagem neonatal positiva HbSS, crises álgicas vaso-oclusivas frequentes desde os 5 anos. Hemograma: Hb 6.8, retic 12%, leuco 18k. Saturação 89%.", "Mulher 28 anos, UBS Cruzeiro do Sul (AC). Fraqueza no braço esquerdo há 3 semanas + episódio prévio há 2 anos de diplopia que resolveu sozinha. Exame: paresia leve MSE, hiperreflexia, sinal de Lhermitte positivo. Nega febre.", "Recém-nascido masculino, 12 dias de vida, Manaus (AM). Triagem neonatal (teste do pezinho) com fenilalanina sérica 18 mg/dL (normal <2). Pais consanguíneos, primeira filha. Sem sintomas no momento.", "Menino de 8 anos, hepatoesplenomegalia progressiva, opacidade corneana bilateral, atraso cognitivo importante, fácies grosseira, irmã mais velha com mesmo quadro (falecida aos 12 anos).", ] # ─── UI ──────────────────────────────────────────────────────────────── with gr.Blocks(title="Araras-Gemma4 — Copiloto Clínico de Doenças Raras", theme=gr.themes.Soft()) as demo: gr.Markdown("""# 🇧🇷 Araras-Gemma4 ### Copiloto offline de apoio à decisão clínica em doenças raras — para profissionais de saúde no Brasil **Para médicos, enfermeiros, agentes comunitários** atendendo nas UBSs e ambulatórios do SUS. NÃO é diagnóstico — é apoio à decisão. 🤖 Gemma 4 E4B (4.5B params, quantizado Q4) · 🇧🇷 PT-BR · 🛜 roda offline no celular · Apache 2.0 🔗 [Model](https://huggingface.co/Raras-AI/araras-gemma4-e4b-v4-gguf) · [Code](https://github.com/rarasAI/araras-gemma4) · [RareBench-BR](https://github.com/rarasAI/rarebench-br) · [raras.org](https://raras.org) """) with gr.Row(): with gr.Column(scale=3): chatbot = gr.Chatbot( label="Conversa com o(a) profissional de saúde", type="messages", height=500, avatar_images=("👨‍⚕️", "🤖"), ) msg = gr.Textbox( label="Descreva o caso ou pergunte sobre conduta, exames, CEAF…", placeholder="Ex: Lactente 4 meses com hipotonia profunda + arreflexia em UBS sem 4G…", lines=3, ) with gr.Row(): submit = gr.Button("Enviar", variant="primary", scale=2) clear = gr.Button("Nova consulta", scale=1) with gr.Column(scale=2): orpha_out = gr.Markdown(label="ORPHAs reconhecidos") sus_out = gr.Markdown(label="Conduta SUS") gr.Examples( examples=EXAMPLES, inputs=msg, label="📋 Casos de exemplo (clique pra testar)", ) submit.click(araras_infer, inputs=[msg, chatbot], outputs=[msg, chatbot, orpha_out, sus_out]) msg.submit(araras_infer, inputs=[msg, chatbot], outputs=[msg, chatbot, orpha_out, sus_out]) clear.click(lambda: ("", [], "", ""), outputs=[msg, chatbot, orpha_out, sus_out]) gr.Markdown("""--- ### ⚠️ Aviso clínico Araras é **software de apoio à decisão clínica** (SaMD per ANVISA Res. 657/2022 e 830/2023), destinado a **profissionais de saúde habilitados**. Não realiza diagnósticos nem prescreve. Toda recomendação é informativa e ancorada em fontes públicas verificáveis (gov.br/conitec, bvsms, PubMed). O profissional de saúde é sempre quem decide. ### 🎯 Submissão Gemma 4 Good Hackathon · Google DeepMind / Kaggle · Maio 2026 · Tracks: Main + Health & Sciences + Digital Equity + Ollama + llama.cpp — Dimas, fundador da [Raras.org](https://raras.org), paciente raro (distonia mioclônica, ORPHA:36899) """) demo.queue().launch()