| """ |
| دستیار بالینی پروتکل تشخیص دوگانه — اپ Gradio |
| اسپیس هاشینگفیس. رابط چت + API قابل صدا زدن + جمعآوری بازخورد. |
| اجرا: python app.py (یا بهصورت Space روی هاشینگفیس بالا میآید) |
| """ |
| from __future__ import annotations |
| import logging |
| import os |
|
|
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") |
| log = logging.getLogger("app") |
|
|
| import config |
| from rag import retriever, pipeline, store |
| from monitoring.risk_engine import assess as assess_risk |
| from integrations import firebase_store |
|
|
| |
| def ensure_index(): |
| if store.exists(): |
| log.info("ایندکس آماده پیدا شد.") |
| return |
| log.info("ایندکس موجود نیست؛ در حال ساخت...") |
| from scripts import build_index |
| build_index.main() |
|
|
|
|
| ensure_index() |
|
|
|
|
| |
| def answer(question: str): |
| """سؤال بالینی را پاسخ میدهد. این تابع بهصورت API هم در دسترس است. |
| |
| Args: |
| question: سؤال کاربر به فارسی. |
| Returns: |
| (پاسخ، منابع، متادیتا) |
| """ |
| question = (question or "").strip() |
| if not question: |
| return "لطفاً سؤال خود را بنویسید.", "", {} |
| res = pipeline.answer(question) |
| src_lines = "\n".join(f"- {s['source']} (نوع: {s['type']}، شباهت: {s['score']})" |
| for s in res["sources"]) |
| meta = { |
| "method": res["method"], |
| "num_sources": len(res["sources"]), |
| "latency": res["latency"], |
| } |
| return res["answer"], src_lines, meta |
|
|
|
|
| def save_feedback(question: str, answer_text: str, rating: int, comment: str): |
| path = pipeline.add_feedback(question or "", answer_text or "", rating, comment or "") |
| return f"✅ بازخورد ذخیره شد (فایل: {os.path.basename(path)}). از شما ممنونیم!" |
|
|
|
|
| def risk_api(patient_ref, psychosis, suicide, violence, withdrawal, substance_use, sleep_loss, nonadherence): |
| """پایش خطر روزانه/هفتگی؛ patient_ref پیش از ذخیره هش میشود.""" |
| result = assess_risk(dict(psychosis=psychosis, suicide=suicide, violence=violence, |
| withdrawal=withdrawal, substance_use=substance_use, |
| sleep_loss=sleep_loss, nonadherence=nonadherence)) |
| if firebase_store.enabled() and (patient_ref or "").strip(): |
| try: |
| result["firebaseAssessmentId"] = firebase_store.save_assessment(patient_ref.strip(), result) |
| result["storage"] = "firestore" |
| except Exception as exc: |
| log.exception("خطای ذخیره Firebase") |
| result["storageWarning"] = str(exc) |
| else: |
| result["storage"] = "disabled" |
| return result |
|
|
|
|
| |
| import gradio as gr |
|
|
| CSS = """ |
| .gradio-container {max-width: 920px !important; font-family: 'Vazirmatn', Tahoma, sans-serif;} |
| .title {text-align:center;} |
| .source-box {background:#ecf6f6; border:1px solid #d8e6e6; border-radius:10px; padding:10px;} |
| """ |
|
|
| with gr.Blocks(title="دستیار پروتکل تشخیص دوگانه", css=CSS, theme=gr.themes.Soft()) as demo: |
| gr.Markdown( |
| "# ⚕️ دستیار بالینی پروتکل تشخیص دوگانه\n" |
| "سایکوز / اسکیزوفرنی + اختلال مصرف مواد + BPD ± ADHD — مبتنی بر شواهد علمی " |
| "NICE · APA · WFSBP.\n\n" |
| "⚠️ این ابزار جایگزین مشاوره پزشک نیست؛ تصمیم نهایی درمان با پزشک معالج است.", |
| elem_classes=["title"]) |
|
|
| with gr.Tab("💬 پرسش و پاسخ"): |
| q_in = gr.Textbox(label="سؤال خود را بنویسید", lines=2, |
| placeholder="مثلاً: کلوزاپین چه زمانی تجویز میشود؟") |
| with gr.Row(): |
| btn = gr.Button("🩺 پاسخ بده", variant="primary") |
| ans_out = gr.Markdown(label="پاسخ") |
| src_out = gr.Markdown(label="منابع بازیابیشده", visible=True) |
| meta_out = gr.JSON(label="متادیتا (روش/تأخیر)") |
| btn.click(answer, inputs=q_in, outputs=[ans_out, src_out, meta_out]) |
| q_in.submit(answer, inputs=q_in, outputs=[ans_out, src_out, meta_out]) |
|
|
| gr.Markdown("#### 📝 ثبت بازخورد (برای آموزش بهتر مدل)") |
| with gr.Row(): |
| rating = gr.Slider(1, 5, value=4, step=1, label="امتیاز پاسخ") |
| comment = gr.Textbox(label="توضیح", lines=1) |
| fb_btn = gr.Button("ثبت بازخورد") |
| fb_out = gr.Markdown() |
| fb_btn.click(save_feedback, inputs=[q_in, ans_out, rating, comment], outputs=fb_out) |
|
|
| with gr.Tab("📈 پایش خطر"): |
| gr.Markdown("### پایش روزانه یا هفتگی\nهر شاخص را از ۰ (ندارد) تا ۴ (شدید) ثبت کنید. این ابزار **دارو را خودکار تغییر نمیدهد**.") |
| patient_ref = gr.Textbox(label="کد داخلی بیمار (نام و کد ملی وارد نشود)", type="password") |
| labels = ["روانپریشی", "خودکشی", "خشونت", "ترک ماده", "مصرف ماده", "کمخوابی", "عدم پایبندی"] |
| sliders = [gr.Slider(0, 4, 0, step=1, label=x) for x in labels] |
| risk_btn = gr.Button("محاسبه و پیشنهاد سطح اقدام", variant="primary") |
| risk_out = gr.JSON(label="گزارش خطر") |
| risk_btn.click(risk_api, inputs=[patient_ref, *sliders], outputs=risk_out, api_name="risk") |
|
|
| with gr.Tab("📚 درباره پایگاه دانش"): |
| gr.Markdown( |
| "این سیستم از روی **پروتکل درمان تشخیص دوگانه**، **مقالات علمی**، " |
| "**سابقه بیمار** و **بازخورد کاربران** پاسخ میسازد.\n\n" |
| "- فایلهای مقاله را در `data/articles/` \n" |
| "- سابقه بیمار را در `data/patient_history/` \n" |
| "- بازخوردها در `data/feedback/` قرار دهید و سپس ایندکس را بازسازی کنید.\n\n" |
| "مدل امبدینگ: چندزبانه (MiniLM). مدل مولد: سبک روی CPU رایگان. " |
| "اگر مدل مولد در دسترس نباشد، سیستم بهصورت استخراجی از متن مرجع پاسخ میدهد." |
| ) |
|
|
| with gr.Tab("🔌 API"): |
| gr.Markdown( |
| "### نحوهی استفاده از API\n" |
| "این اپ بهصورت خودکار یک REST API ارائه میدهد. مثال پایتون:\n" |
| "```python\n" |
| "from gradio_client import Client\n" |
| "c = Client(\"<این-اسپیس>\")\n" |
| "ans, sources, meta = c.predict(question=\"کلوزاپین چه زمانی؟\", api_name=\"/answer\")\n" |
| "print(ans)\n" |
| "```\n" |
| "همچنین میتوانید بهصورت POST به `/call/answer` درخواست بزنید." |
| ) |
|
|
|
|
| |
| if __name__ == "__main__": |
| demo.queue().launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")), |
| share=os.getenv("GRADIO_SHARE", "0") == "1") |
|
|