gthor7 commited on
Commit
8b84007
Β·
verified Β·
1 Parent(s): db2f00d

Add Gradio app with all 4 pipeline modules

Browse files
Files changed (1) hide show
  1. app.py +784 -0
app.py ADDED
@@ -0,0 +1,784 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PCOS Health AI β€” Interactive Demo Space
3
+ ========================================
4
+ A Gradio-powered demo showcasing all components of the PCOS Health AI pipeline:
5
+ Tab 1: πŸ›‘οΈ Safety Guardrails β€” Test medical safety checks interactively
6
+ Tab 2: πŸ” RAG Pipeline β€” Query the PCOS medical knowledge base
7
+ Tab 3: πŸ“Š Dataset Generator β€” Preview synthetic PCOS training data
8
+ Tab 4: βš™οΈ Training Config β€” View LoRA fine-tuning presets
9
+
10
+ Repository: https://huggingface.co/gthor7/pcos-health-ai-pipeline
11
+ """
12
+
13
+ import re
14
+ import json
15
+ import random
16
+ import hashlib
17
+ import logging
18
+ import numpy as np
19
+ from typing import Dict, List, Optional, Tuple
20
+ from enum import Enum
21
+ from dataclasses import dataclass
22
+
23
+ import gradio as gr
24
+
25
+ logging.basicConfig(level=logging.INFO)
26
+ logger = logging.getLogger(__name__)
27
+
28
+ random.seed(42)
29
+ np.random.seed(42)
30
+
31
+
32
+ # ═════════════════════════════════════════════════════════════════════════════
33
+ # EMBEDDED: Safety Guardrails (from safety_guardrails.py)
34
+ # ═════════════════════════════════════════════════════════════════════════════
35
+
36
+ SYSTEM_PROMPT = """You are a compassionate, evidence-based PCOS health assistant.
37
+ STRICT BOUNDARIES: Never prescribe medications. Never diagnose. Always recommend consulting an endocrinologist or OB/GYN."""
38
+
39
+ PROHIBITED_MEDICATIONS = [
40
+ "metformin", "spironolactone", "oral contraceptive", "birth control pill",
41
+ "clomiphene", "clomid", "letrozole", "femara", "gonadotropin",
42
+ "progesterone", "provera", "medroxyprogesterone", "drospirenone",
43
+ "cyproterone", "diane-35", "yaz", "yasmin", "finasteride",
44
+ "eflornithine", "vaniqa", "pioglitazone", "rosiglitazone",
45
+ "inositol", "n-acetylcysteine", "nac", "glucophage",
46
+ ]
47
+
48
+ EMERGENCY_SYMPTOMS = [
49
+ "suicidal", "self-harm", "severe pain", "heavy bleeding",
50
+ "chest pain", "shortness of breath", "fainting", "seizure",
51
+ "sudden vision loss", "severe headache", "allergic reaction",
52
+ "anaphylaxis", "pregnancy complication", "ectopic",
53
+ "ovarian torsion", "severe abdominal pain",
54
+ ]
55
+
56
+ DIET_CULTURE_FLAGS = [
57
+ "keto cure", "carnivore cure", "detox", "cleanse", "juice fast",
58
+ "intermittent fasting cure", "supplement cure", "miracle",
59
+ "guaranteed weight loss", "rapid weight loss", "diet pill",
60
+ "fat burner", "metabolism booster",
61
+ ]
62
+
63
+
64
+ def check_safety(user_message: str) -> Tuple[str, str, List[str]]:
65
+ """Check user input through safety guardrails. Returns (level, response, rules)."""
66
+ triggered = []
67
+ level = "βœ… SAFE"
68
+ response = None
69
+ text = user_message.lower()
70
+
71
+ # Emergency check
72
+ for symptom in EMERGENCY_SYMPTOMS:
73
+ if symptom in text:
74
+ triggered.append(f"🚨 EMERGENCY: '{symptom}' detected")
75
+ level = "🚨 EMERGENCY"
76
+
77
+ if level == "🚨 EMERGENCY":
78
+ response = (
79
+ "🚨 **The symptoms you're describing need immediate medical attention.**\n\n"
80
+ "Please:\n"
81
+ "- Contact your healthcare provider or go to the nearest ER\n"
82
+ "- If having thoughts of self-harm, call **988 Suicide & Crisis Lifeline**\n"
83
+ "- Do not wait β€” your safety is the top priority\n\n"
84
+ "I'm here for you, but this needs professional help right away."
85
+ )
86
+ return level, response, triggered
87
+
88
+ # Medication/prescription check
89
+ prescription_words = ["prescribe", "prescription", "what dose", "how much should i take",
90
+ "can you prescribe", "increase my dose", "change my medication"]
91
+ if any(pw in text for pw in prescription_words):
92
+ triggered.append("πŸ’Š PRESCRIPTION_REQUEST detected")
93
+ level = "πŸ”€ REFERRAL NEEDED"
94
+
95
+ dosage_pattern = re.compile(r'\b(\d+\s*(?:mg|mcg|ml|units?|tablets?|pills?|capsules?))\b', re.IGNORECASE)
96
+ if dosage_pattern.search(user_message):
97
+ for med in PROHIBITED_MEDICATIONS:
98
+ if med in text:
99
+ triggered.append(f"πŸ’Š MEDICATION+DOSAGE: '{med}' with dosage")
100
+ level = "πŸ”€ REFERRAL NEEDED"
101
+
102
+ if level == "πŸ”€ REFERRAL NEEDED":
103
+ response = (
104
+ "βš•οΈ **This is a question for your healthcare team.**\n\n"
105
+ "I'm not able to prescribe, recommend specific medications, or suggest dosage changes. "
106
+ "Please discuss this with your **endocrinologist or OB/GYN** who knows your complete medical history.\n\n"
107
+ "I *can* help you understand PCOS symptoms, track patterns, and prepare questions for your next appointment."
108
+ )
109
+ return level, response, triggered
110
+
111
+ # Diet culture check
112
+ for term in DIET_CULTURE_FLAGS:
113
+ if term in text:
114
+ triggered.append(f"⚠️ DIET_CULTURE: '{term}' detected")
115
+ level = "⚠️ WARNING"
116
+
117
+ if level == "⚠️ WARNING":
118
+ response = (
119
+ "ℹ️ **Let me share what the evidence actually says...**\n\n"
120
+ "No single diet or supplement has been proven to 'cure' PCOS. What research consistently "
121
+ "supports is focusing on:\n"
122
+ "- **Blood sugar stability** through balanced meals (protein + fiber + healthy fats)\n"
123
+ "- **Regular physical activity** (150 min/week moderate exercise)\n"
124
+ "- **Adequate sleep** (7-9 hours)\n"
125
+ "- **Stress management**\n\n"
126
+ "The goal is **metabolic health**, not following a specific named diet.\n\n"
127
+ "βš•οΈ A registered dietitian specializing in PCOS can create a sustainable, personalized plan."
128
+ )
129
+ return level, response, triggered
130
+
131
+ # Safe β€” provide educational response
132
+ if not triggered:
133
+ triggered.append("βœ… No safety rules triggered")
134
+ response = (
135
+ "βœ… **This query is safe to process.**\n\n"
136
+ "In a full deployment, this would be passed to the PCOS Health LLM + RAG pipeline "
137
+ "to generate an empathetic, evidence-based response grounded in clinical guidelines.\n\n"
138
+ "The model would:\n"
139
+ "1. Retrieve relevant medical documents from the knowledge base\n"
140
+ "2. Generate a response with source citations\n"
141
+ "3. Run output safety checks before delivering\n"
142
+ "4. Include a medical disclaimer"
143
+ )
144
+ return level, response, triggered
145
+
146
+
147
+ def safety_chat(message, history):
148
+ """Chatbot handler for safety guardrails tab."""
149
+ if not message.strip():
150
+ return "", history
151
+
152
+ level, response, rules = check_safety(message)
153
+
154
+ # Format rules as a details block
155
+ rules_text = "\n".join(f"- {r}" for r in rules)
156
+ full_response = (
157
+ f"**Safety Classification: {level}**\n\n"
158
+ f"---\n\n"
159
+ f"{response}\n\n"
160
+ f"---\n"
161
+ f"<details><summary>πŸ” Triggered Rules ({len(rules)})</summary>\n\n"
162
+ f"{rules_text}\n"
163
+ f"</details>"
164
+ )
165
+
166
+ history.append({"role": "user", "content": message})
167
+ history.append({"role": "assistant", "content": full_response})
168
+ return "", history
169
+
170
+
171
+ def test_output_safety(model_output: str) -> str:
172
+ """Check if a model's output would pass safety guardrails."""
173
+ if not model_output.strip():
174
+ return "Enter a model output to check."
175
+
176
+ triggered = []
177
+ level = "βœ… SAFE"
178
+ text_lower = model_output.lower()
179
+
180
+ # Prescription check
181
+ prescription_pattern = re.compile(
182
+ r'\b(you\s+should\s+take|start\s+taking|increase\s+(?:your\s+)?dose|'
183
+ r'decrease\s+(?:your\s+)?dose|recommended?\s+dose|take\s+\d+\s*mg|stop\s+taking)\b',
184
+ re.IGNORECASE
185
+ )
186
+ dosage_pattern = re.compile(r'\b(\d+\s*(?:mg|mcg|ml|units?|tablets?|pills?))\b', re.IGNORECASE)
187
+
188
+ if prescription_pattern.search(model_output):
189
+ for med in PROHIBITED_MEDICATIONS:
190
+ if med in text_lower:
191
+ triggered.append(f"🚫 OUTPUT_PRESCRIPTION: model prescribed '{med}'")
192
+ level = "🚫 BLOCKED"
193
+
194
+ if dosage_pattern.search(model_output):
195
+ for med in PROHIBITED_MEDICATIONS:
196
+ if med in text_lower:
197
+ triggered.append(f"🚫 OUTPUT_DOSAGE: model specified dosage for '{med}'")
198
+ level = "🚫 BLOCKED"
199
+
200
+ # Diagnosis check
201
+ diag_patterns = [
202
+ r'\byou (?:have|definitely have|clearly have)\b.*\b(?:pcos|diabetes|syndrome)\b',
203
+ r'\bI (?:can )?diagnose\b', r'\byour diagnosis is\b',
204
+ ]
205
+ for pat in diag_patterns:
206
+ if re.search(pat, model_output, re.IGNORECASE):
207
+ triggered.append("⚠️ OUTPUT_DIAGNOSIS: definitive diagnosis language")
208
+ if level == "βœ… SAFE":
209
+ level = "⚠️ WARNING"
210
+
211
+ # Disclaimer check
212
+ has_disclaimer = any(m in text_lower for m in [
213
+ "healthcare provider", "consult your doctor", "medical advice",
214
+ "endocrinologist", "ob/gyn", "healthcare team", "βš•οΈ", "specialist"
215
+ ])
216
+ if not has_disclaimer:
217
+ triggered.append("ℹ️ OUTPUT_NO_DISCLAIMER: missing safety disclaimer")
218
+
219
+ if not triggered:
220
+ triggered.append("βœ… All checks passed")
221
+
222
+ rules_text = "\n".join(f"- {r}" for r in triggered)
223
+
224
+ if level == "🚫 BLOCKED":
225
+ replacement = (
226
+ "**🚫 This output would be BLOCKED and replaced with:**\n\n"
227
+ "> I appreciate your question, but medication decisions β€” including starting, stopping, "
228
+ "or adjusting dosages β€” must be made by your healthcare provider who knows your complete "
229
+ "medical history.\n>\n> βš•οΈ Please discuss this with your endocrinologist or OB/GYN."
230
+ )
231
+ return f"## Output Safety: {level}\n\n{replacement}\n\n### Rules Triggered:\n{rules_text}"
232
+
233
+ return f"## Output Safety: {level}\n\n{'βœ… Output would be delivered to user.' if level == 'βœ… SAFE' else '⚠️ Output would be delivered with added disclaimers.'}\n\n{'βš•οΈ *Missing disclaimer β€” one would be auto-appended.*' if not has_disclaimer else ''}\n\n### Rules Triggered:\n{rules_text}"
234
+
235
+
236
+ # ═════════════════════════════════════════════════════════════════════════════
237
+ # EMBEDDED: RAG Pipeline (from rag_pipeline.py β€” lightweight, no ChromaDB)
238
+ # ═════════════════════════════════════════════════════════════════════════════
239
+
240
+ KNOWLEDGE_BASE = {
241
+ "diagnosis": {
242
+ "title": "PCOS Diagnosis β€” Endocrine Society Guidelines",
243
+ "source": "Endocrine Society Clinical Practice Guidelines, 2013 (reaffirmed 2023)",
244
+ "content": (
245
+ "Diagnosis requires β‰₯2 of 3 Rotterdam criteria: (1) Oligo-anovulation (cycles >35 days, "
246
+ "<8 cycles/year, or amenorrhea >3 months), (2) Hyperandrogenism β€” clinical (hirsutism F-G β‰₯4-6, "
247
+ "acne, alopecia) or biochemical (elevated testosterone, DHEA-S), (3) Polycystic ovarian morphology "
248
+ "(β‰₯12 follicles 2-9mm per ovary OR volume >10 mL). Must exclude thyroid dysfunction, CAH, "
249
+ "Cushing's, hyperprolactinemia. Four phenotypes: A (full classic), B (no PCO), C (ovulatory), D (non-hyperandrogenic)."
250
+ ),
251
+ },
252
+ "management": {
253
+ "title": "PCOS Management β€” ACOG Practice Bulletin No. 194",
254
+ "source": "ACOG, Practice Bulletin No. 194 (2018)",
255
+ "content": (
256
+ "First-line: lifestyle modification for ALL patients (150 min/week exercise, balanced nutrition, "
257
+ "behavioral support). Menstrual regulation: combined hormonal contraceptives (first-line). "
258
+ "Hyperandrogenism: CHCs first-line, anti-androgen add-on. Insulin resistance: Metformin (second-line, "
259
+ "for metabolic indications). Fertility: Letrozole (first-line, higher live birth than clomiphene 27.5% vs 19.1%), "
260
+ "Clomiphene (second-line), Gonadotropins (third-line), IVF (treatment-resistant). "
261
+ "Screen: OGTT at diagnosis then every 1-3 years, lipids, BP, depression/anxiety."
262
+ ),
263
+ },
264
+ "insulin_resistance": {
265
+ "title": "Insulin Resistance in PCOS β€” Pathophysiology",
266
+ "source": "Diamanti-Kandarakis & Dunaif, Endocrine Reviews 2012",
267
+ "content": (
268
+ "50-70% of PCOS patients have insulin resistance independent of BMI. Selective insulin resistance: "
269
+ "metabolic pathways resistant but steroidogenic pathways remain sensitive β†’ hyperinsulinemia drives "
270
+ "ovarian androgen production. Consequences: impaired glucose tolerance (30-40%), T2D risk 5-10x higher, "
271
+ "NAFLD (30-40%), CVD risk factors. Assessment: HOMA-IR, OGTT (gold standard), HbA1c. "
272
+ "Management: lifestyle improves sensitivity 30-40%, exercise (aerobic + resistance), metformin, inositol (emerging)."
273
+ ),
274
+ },
275
+ "hyperandrogenism": {
276
+ "title": "Hyperandrogenism in PCOS",
277
+ "source": "Endocrine Society Guidelines; Azziz R et al., JCEM 2009",
278
+ "content": (
279
+ "Hirsutism (60-70%): excess terminal hair, Ferriman-Gallwey score β‰₯4-6. Acne (15-30%): adult-onset, "
280
+ "cystic, jawline distribution. Alopecia (5-10%): diffuse thinning, Ludwig scale. "
281
+ "Biochemical: elevated total/free testosterone (most sensitive), DHEA-S, low SHBG. "
282
+ "Pathophysiology: ovarian theca cell overproduction driven by LH hypersecretion + hyperinsulinemia, "
283
+ "reduced SHBG β†’ more bioavailable testosterone. Adrenal contribution ~20-30%."
284
+ ),
285
+ },
286
+ "mental_health": {
287
+ "title": "Mental Health and PCOS",
288
+ "source": "Cooney LG et al., Fertility & Sterility 2017",
289
+ "content": (
290
+ "Depression: 28-64% (3-8x general population). Anxiety: 34-57%. Eating disorders: 2-3x higher. "
291
+ "Mechanisms: androgen fluctuations affect serotonin/GABA, insulin resistance impacts brain glucose, "
292
+ "chronic symptom burden, visible symptoms, fertility concerns. Screen with PHQ-9, GAD-7 at diagnosis. "
293
+ "Management: CBT (strongest evidence), lifestyle intervention, pharmacotherapy for moderate-severe, "
294
+ "PCOS support groups. Mental health directly impacts treatment adherence and metabolic outcomes."
295
+ ),
296
+ },
297
+ "fertility": {
298
+ "title": "Fertility and PCOS",
299
+ "source": "ACOG; Legro RS et al., NEJM 2014",
300
+ "content": (
301
+ "PCOS is most common cause of anovulatory infertility (70-80%). Most can conceive with management. "
302
+ "Treatment ladder: (1) Lifestyle β€” 5-10% weight reduction restores ovulation in 30-50%, "
303
+ "(2) Letrozole β€” FIRST-LINE, higher live birth rates (NEJM 2014), (3) Clomiphene β€” 60-80% ovulation rate, "
304
+ "(4) Gonadotropins β€” low-dose step-up, OHSS risk, (5) Ovarian drilling, (6) IVF β€” GnRH antagonist preferred. "
305
+ "OPKs may give false positives in PCOS (elevated LH); BBT is more reliable for confirming ovulation."
306
+ ),
307
+ },
308
+ "lifestyle": {
309
+ "title": "Lifestyle Management in PCOS β€” Evidence Base",
310
+ "source": "Teede HJ et al., Int J Women's Health 2023",
311
+ "content": (
312
+ "Exercise: β‰₯150 min/week moderate OR 75 min vigorous. Resistance training 2-3x/week improves insulin "
313
+ "sensitivity 25-30%. Benefits occur independent of weight loss. Nutrition: no single diet proven superior. "
314
+ "Evidence supports: adequate protein (1.2-1.6 g/kg), fiber 25-30g/day, whole grains, omega-3s, "
315
+ "limiting added sugars. Mediterranean pattern has most evidence. Keto/IF: insufficient PCOS-specific evidence. "
316
+ "Sleep: 7-9 hours (sleep apnea risk elevated). Stress: chronic stress worsens insulin resistance. "
317
+ "Weight: 5-10% loss improves markers, but PCOS makes loss harder β€” focus on behaviors not scale."
318
+ ),
319
+ },
320
+ }
321
+
322
+
323
+ def rag_query(query: str) -> Tuple[str, str, str]:
324
+ """Simple keyword-based RAG retrieval (no vector DB needed for demo)."""
325
+ if not query.strip():
326
+ return "Enter a question about PCOS.", "", ""
327
+
328
+ query_lower = query.lower()
329
+
330
+ # Score each document by keyword overlap
331
+ scores = {}
332
+ for key, doc in KNOWLEDGE_BASE.items():
333
+ text = (doc["title"] + " " + doc["content"]).lower()
334
+ score = sum(1 for word in query_lower.split() if word in text and len(word) > 3)
335
+ # Boost for category keyword matches
336
+ category_keywords = {
337
+ "diagnosis": ["diagnos", "rotterdam", "criteria", "phenotype", "test"],
338
+ "management": ["treat", "manage", "medication", "therapy", "screen"],
339
+ "insulin_resistance": ["insulin", "resistance", "glucose", "diabetes", "metabolic", "a1c"],
340
+ "hyperandrogenism": ["androgen", "hirsutism", "acne", "hair", "testosterone", "shbg"],
341
+ "mental_health": ["depress", "anxiety", "mental", "mood", "emotion", "feel", "sad"],
342
+ "fertility": ["fertil", "pregnant", "conceive", "ovulat", "ivf", "letrozole", "baby"],
343
+ "lifestyle": ["diet", "exercise", "nutrition", "weight", "sleep", "keto", "food", "stress"],
344
+ }
345
+ for kw in category_keywords.get(key, []):
346
+ if kw in query_lower:
347
+ score += 3
348
+ scores[key] = score
349
+
350
+ # Get top 3
351
+ top_keys = sorted(scores, key=scores.get, reverse=True)[:3]
352
+ top_docs = [(KNOWLEDGE_BASE[k], scores[k]) for k in top_keys if scores[k] > 0]
353
+
354
+ if not top_docs:
355
+ top_docs = [(KNOWLEDGE_BASE["management"], 1)] # Fallback
356
+
357
+ # Format retrieved context
358
+ context_parts = []
359
+ for i, (doc, score) in enumerate(top_docs, 1):
360
+ context_parts.append(
361
+ f"**[{i}] {doc['title']}**\n"
362
+ f"*Source: {doc['source']}*\n\n"
363
+ f"{doc['content']}"
364
+ )
365
+ context = "\n\n---\n\n".join(context_parts)
366
+
367
+ # Format sources
368
+ sources = "\n".join(
369
+ f"- **{doc['title']}** ({doc['source']}) β€” relevance: {score}"
370
+ for doc, score in top_docs
371
+ )
372
+
373
+ # Format the augmented prompt that would go to the LLM
374
+ augmented_prompt = (
375
+ f"**System**: {SYSTEM_PROMPT}\n\n"
376
+ f"**Retrieved Context**:\n{context}\n\n"
377
+ f"**User Question**: {query}\n\n"
378
+ f"**Instructions**: Provide empathetic, evidence-based response. Cite sources. "
379
+ f"Never prescribe. Include safety disclaimer."
380
+ )
381
+
382
+ return context, sources, augmented_prompt
383
+
384
+
385
+ # ═════════════════════════════════════════════════════════════════════════════
386
+ # EMBEDDED: Dataset Generator (from dataset_pipeline.py β€” lightweight)
387
+ # ═════════════════════════════════════════════════════════════════════════════
388
+
389
+ PHENOTYPES = [
390
+ {"name": "Classic PCOS (Full)", "hyperandrogenism": True, "oligo_anovulation": True},
391
+ {"name": "Classic PCOS (No PCO)", "hyperandrogenism": True, "oligo_anovulation": True},
392
+ {"name": "Ovulatory PCOS", "hyperandrogenism": True, "oligo_anovulation": False},
393
+ {"name": "Non-hyperandrogenic PCOS", "hyperandrogenism": False, "oligo_anovulation": True},
394
+ ]
395
+
396
+ CLINICAL_QUESTIONS = [
397
+ "What exactly is PCOS and why does it happen?",
398
+ "How is PCOS diagnosed? What are the Rotterdam criteria?",
399
+ "Why are my periods so irregular with PCOS?",
400
+ "Can I get pregnant with PCOS?",
401
+ "What does insulin resistance have to do with PCOS?",
402
+ "What diet is best for PCOS?",
403
+ "I feel so frustrated dealing with PCOS every day.",
404
+ "Does PCOS affect mental health?",
405
+ "Should I cut out all carbs?",
406
+ "I hate how my body looks because of PCOS.",
407
+ ]
408
+
409
+ EMPATHETIC_SCENARIOS = [
410
+ ("I just got diagnosed with PCOS and feel overwhelmed.",
411
+ "First, take a breath. It's completely natural to feel overwhelmed right now. PCOS is manageable, and you're not alone β€” about 1 in 10 women have it. A good first step is making a list of symptoms and questions for your doctor. βš•οΈ Schedule a follow-up to create a personalized plan."),
412
+ ("I've been trying to conceive for over a year and losing hope.",
413
+ "I'm so sorry you're going through this. PCOS-related fertility challenges are among the most treatable causes of difficulty conceiving. Many people with PCOS have successful pregnancies. βš•οΈ I'd encourage speaking with a reproductive endocrinologist."),
414
+ ("The facial hair and acne make me feel so unfeminine.",
415
+ "What you're feeling is so understandable. These are medical symptoms caused by hormones, not reflections of who you are. A dermatologist experienced with PCOS can be an amazing ally. Your worth and femininity are not defined by these symptoms. πŸ’›"),
416
+ ("My doctor told me to just lose weight. I've tried everything.",
417
+ "That must have been frustrating. PCOS itself makes weight management significantly harder due to insulin resistance and hormonal factors β€” this is a medical reality, not a willpower issue. Focus on metabolic health markers, not the scale. βš•οΈ Consider a PCOS-specialized endocrinologist."),
418
+ ("I read online that keto will cure my PCOS.",
419
+ "No single diet has been proven to 'cure' PCOS. Some evidence supports reducing refined carbs for insulin resistance, but strict keto is often unsustainable and can worsen hormonal stress. Focus on blood sugar stability: adequate protein, fiber-rich foods, healthy fats. βš•οΈ See a PCOS-specialized dietitian."),
420
+ ]
421
+
422
+
423
+ def generate_sample_data(n_samples: int = 10) -> list:
424
+ """Generate sample PCOS training data for preview."""
425
+ samples = []
426
+
427
+ for i in range(min(n_samples, 30)):
428
+ category = random.choice(["tracking_log", "clinical_qa", "empathetic"])
429
+
430
+ if category == "tracking_log":
431
+ phenotype = random.choice(PHENOTYPES)
432
+ age = random.randint(18, 42)
433
+ bmi = round(random.triangular(18.5, 42.0, 28.0), 1)
434
+ cycle = random.choice([random.randint(35, 90), -1]) if phenotype["oligo_anovulation"] else random.randint(25, 35)
435
+ bbt = round(random.uniform(96.8, 98.2), 1)
436
+ symptoms = []
437
+ if phenotype["hyperandrogenism"]:
438
+ symptoms.append(random.choice(["Acne: moderate on jawline", "Hirsutism: chin (F-G: 12)", "Hair loss: diffuse thinning"]))
439
+ symptoms.append(random.choice(["Fatigue: moderate, afternoon crash", "Mood: anxious", "Bloating: mild, daily"]))
440
+
441
+ user_msg = (
442
+ f"Daily log: Age {age}, BMI {bmi}. "
443
+ f"Cycle: {'amenorrhea (>6mo)' if cycle < 0 else f'{cycle} days'}. "
444
+ f"BBT: {bbt}Β°F. Symptoms: {'; '.join(symptoms)}."
445
+ )
446
+ asst_msg = (
447
+ f"Thank you for tracking! πŸ’› Your BBT of {bbt}Β°F {'without clear thermal shift suggests anovulatory cycle β€” common in PCOS' if bbt < 97.5 else 'shows a pattern developing β€” keep tracking'}. "
448
+ f"{'Amenorrhea >6mo: discuss with your OB/GYN for endometrial health.' if cycle < 0 else f'Cycles of {cycle} days are common with PCOS.'} "
449
+ f"βš•οΈ Discuss changes with your healthcare team."
450
+ )
451
+ samples.append([f"🌑️ Tracking Log", phenotype["name"], user_msg[:200], asst_msg[:200]])
452
+
453
+ elif category == "clinical_qa":
454
+ q = random.choice(CLINICAL_QUESTIONS)
455
+ prefix = random.choice([f"I'm {random.randint(18, 42)} with PCOS. ", ""])
456
+ samples.append([
457
+ "πŸ“‹ Clinical Q&A",
458
+ "Guideline-grounded",
459
+ (prefix + q)[:200],
460
+ "Evidence-based response citing Endocrine Society / ACOG guidelines..."[:200],
461
+ ])
462
+
463
+ else:
464
+ scenario = random.choice(EMPATHETIC_SCENARIOS)
465
+ samples.append([
466
+ "πŸ’› Empathetic",
467
+ "Emotional support",
468
+ scenario[0][:200],
469
+ scenario[1][:200],
470
+ ])
471
+
472
+ return samples
473
+
474
+
475
+ # ═════════════════════════════════════════════════════════════════════════════
476
+ # EMBEDDED: Training Config (from lora_finetuning.py)
477
+ # ═════════════════════════════════════════════════════════════════════════════
478
+
479
+ def get_training_config(preset: str) -> str:
480
+ """Return training configuration as formatted markdown."""
481
+ configs = {
482
+ "Quick Test": {
483
+ "Base Model": "BioMistral/BioMistral-7B",
484
+ "LoRA Rank (r)": 8, "LoRA Alpha": 16, "LoRA Dropout": 0.05,
485
+ "Target Modules": "all-linear", "Epochs": 1,
486
+ "Batch Size": "1 Γ— 4 grad_accum = 4 effective",
487
+ "Learning Rate": "1e-4", "Scheduler": "cosine",
488
+ "Max Seq Length": 512, "Quantization": "4-bit NF4 (QLoRA)",
489
+ "Optimizer": "paged_adamw_32bit", "Hardware": "a10g-small (24GB GPU)",
490
+ "Estimated Time": "~30 minutes",
491
+ "Paper Reference": "BioMistral (arXiv:2402.10373)",
492
+ },
493
+ "Standard": {
494
+ "Base Model": "BioMistral/BioMistral-7B",
495
+ "LoRA Rank (r)": 16, "LoRA Alpha": 32, "LoRA Dropout": 0.05,
496
+ "Target Modules": "all-linear", "Epochs": 3,
497
+ "Batch Size": "2 Γ— 8 grad_accum = 16 effective",
498
+ "Learning Rate": "1e-4", "Scheduler": "cosine",
499
+ "Max Seq Length": 2048, "Quantization": "4-bit NF4 (QLoRA)",
500
+ "Optimizer": "paged_adamw_32bit", "Hardware": "a10g-large (24GB GPU)",
501
+ "Estimated Time": "~3-4 hours",
502
+ "Paper Reference": "Med42 (arXiv:2404.14779) + BioMistral (arXiv:2402.10373)",
503
+ },
504
+ "High Quality": {
505
+ "Base Model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
506
+ "LoRA Rank (r)": 64, "LoRA Alpha": 128, "LoRA Dropout": 0.05,
507
+ "Target Modules": "all-linear", "Epochs": 5,
508
+ "Batch Size": "2 Γ— 16 grad_accum = 32 effective",
509
+ "Learning Rate": "5e-5", "Scheduler": "cosine",
510
+ "Max Seq Length": 4096, "Quantization": "4-bit NF4 (QLoRA)",
511
+ "Optimizer": "paged_adamw_32bit", "Hardware": "a100-large (80GB GPU)",
512
+ "Estimated Time": "~6-8 hours",
513
+ "Paper Reference": "Med42 (arXiv:2404.14779)",
514
+ },
515
+ "Maximum (Full FT Parity)": {
516
+ "Base Model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
517
+ "LoRA Rank (r)": 256, "LoRA Alpha": 16, "LoRA Dropout": 0.05,
518
+ "Target Modules": "all-linear", "Epochs": 8,
519
+ "Batch Size": "2 Γ— 16 grad_accum = 32 effective",
520
+ "Learning Rate": "2e-4", "Scheduler": "cosine",
521
+ "Max Seq Length": 4096, "Quantization": "4-bit NF4 (QLoRA)",
522
+ "Optimizer": "paged_adamw_32bit", "Hardware": "a100-large (80GB GPU)",
523
+ "Estimated Time": "~12-16 hours",
524
+ "Paper Reference": "LoRA Without Regret (2025, Schulman et al.)",
525
+ },
526
+ }
527
+
528
+ cfg = configs.get(preset, configs["Standard"])
529
+
530
+ rows = "\n".join(f"| **{k}** | {v} |" for k, v in cfg.items())
531
+ md = (
532
+ f"## 🎯 Preset: {preset}\n\n"
533
+ f"| Parameter | Value |\n|---|---|\n{rows}\n\n"
534
+ f"### Key Design Decisions\n\n"
535
+ f"- **`target_modules='all-linear'`**: Per Med42 paper, applying LoRA to ALL linear layers "
536
+ f"(not just attention Q/K/V/O) significantly outperforms attention-only LoRA.\n"
537
+ f"- **Learning rate 10x higher than full FT**: Standard practice for LoRA β€” the adapter weights "
538
+ f"need stronger gradients since only a small fraction of parameters are updated.\n"
539
+ f"- **Cosine schedule**: Both Med42 and BioMistral validate cosine > linear for LoRA training.\n"
540
+ f"- **QLoRA (4-bit NF4)**: Reduces VRAM from ~14GB (fp16) to ~5GB for 7B models, enabling "
541
+ f"training on consumer GPUs.\n\n"
542
+ f"### Mobile Export\n\n"
543
+ f"After training:\n"
544
+ f"- **GGUF**: `convert-hf-to-gguf.py β†’ Q4_K_M` (~5GB for mobile)\n"
545
+ f"- **AWQ**: `group_size=128, w_bit=4` (~4.68GB for mobile)\n"
546
+ )
547
+ return md
548
+
549
+
550
+ # ═════════════════════════════════════════════════════════════════════════════
551
+ # GRADIO APP
552
+ # ═════════════════════════════════════════════════════════════════════════════
553
+
554
+ EXAMPLE_SAFE = [
555
+ "What are common symptoms of PCOS?",
556
+ "How does PCOS affect my menstrual cycle?",
557
+ "Can you explain what insulin resistance means?",
558
+ ]
559
+ EXAMPLE_REFERRAL = [
560
+ "What dose of metformin should I take?",
561
+ "Can you prescribe me birth control for PCOS?",
562
+ "Should I increase my spironolactone to 200mg?",
563
+ ]
564
+ EXAMPLE_EMERGENCY = [
565
+ "I'm having severe abdominal pain and heavy bleeding",
566
+ "I've been having suicidal thoughts because of my PCOS",
567
+ ]
568
+ EXAMPLE_DIET = [
569
+ "Will a keto cure my PCOS?",
570
+ "What detox should I do for hormonal balance?",
571
+ ]
572
+
573
+ CSS = """
574
+ .header-text { text-align: center; }
575
+ .disclaimer { background-color: #fff3cd; padding: 12px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 10px 0; }
576
+ """
577
+
578
+ with gr.Blocks(title="PCOS Health AI Demo") as demo:
579
+
580
+ gr.Markdown(
581
+ """
582
+ # 🩺 PCOS Health AI β€” Interactive Pipeline Demo
583
+
584
+ **A production-grade AI pipeline for hormonal health tracking focused on Polycystic Ovary Syndrome (PCOS).**
585
+
586
+ Explore all 4 components: safety guardrails, RAG knowledge retrieval, dataset generation, and training configuration.
587
+
588
+ πŸ“¦ [View Full Code Repository](https://huggingface.co/gthor7/pcos-health-ai-pipeline)
589
+ """,
590
+ elem_classes="header-text",
591
+ )
592
+
593
+ gr.Markdown(
594
+ "⚠️ **Disclaimer**: This is a research demo, NOT a medical device. Never use AI for medical decisions. "
595
+ "Always consult a licensed healthcare professional.",
596
+ elem_classes="disclaimer",
597
+ )
598
+
599
+ # ── Tab 1: Safety Guardrails ────────────────────────────────────────────
600
+ with gr.Tab("πŸ›‘οΈ Safety Guardrails"):
601
+ gr.Markdown(
602
+ "### Test the 3-layer medical safety system\n"
603
+ "Type any health question and see how the guardrails classify it. "
604
+ "Try medication requests, emergency symptoms, or diet culture claims."
605
+ )
606
+
607
+ with gr.Row():
608
+ with gr.Column(scale=3):
609
+ chatbot = gr.Chatbot(
610
+ height=450,
611
+ buttons=["copy", "copy_all"],
612
+ placeholder="Try asking about PCOS symptoms, medications, or emergencies...",
613
+ )
614
+ with gr.Row():
615
+ msg_input = gr.Textbox(
616
+ label="Your message",
617
+ placeholder="e.g., 'What dose of metformin should I take?'",
618
+ scale=5,
619
+ show_label=False,
620
+ )
621
+ send_btn = gr.Button("Send", variant="primary", scale=1)
622
+ clear_btn = gr.ClearButton([msg_input, chatbot], value="πŸ—‘οΈ Clear Chat")
623
+
624
+ with gr.Column(scale=2):
625
+ gr.Markdown("#### πŸ§ͺ Try These Examples")
626
+ gr.Markdown("**βœ… Safe queries:**")
627
+ for ex in EXAMPLE_SAFE:
628
+ gr.Button(ex, size="sm").click(
629
+ lambda x=ex: (x, ), outputs=[msg_input]
630
+ )
631
+ gr.Markdown("**πŸ”€ Medication (β†’ Referral):**")
632
+ for ex in EXAMPLE_REFERRAL:
633
+ gr.Button(ex, size="sm").click(
634
+ lambda x=ex: (x, ), outputs=[msg_input]
635
+ )
636
+ gr.Markdown("**🚨 Emergency:**")
637
+ for ex in EXAMPLE_EMERGENCY:
638
+ gr.Button(ex, size="sm").click(
639
+ lambda x=ex: (x, ), outputs=[msg_input]
640
+ )
641
+ gr.Markdown("**⚠️ Diet culture:**")
642
+ for ex in EXAMPLE_DIET:
643
+ gr.Button(ex, size="sm").click(
644
+ lambda x=ex: (x, ), outputs=[msg_input]
645
+ )
646
+
647
+ msg_input.submit(safety_chat, [msg_input, chatbot], [msg_input, chatbot])
648
+ send_btn.click(safety_chat, [msg_input, chatbot], [msg_input, chatbot])
649
+
650
+ gr.Markdown("---\n### πŸ” Output Safety Checker\nPaste a model's response to check if it would pass output guardrails:")
651
+ with gr.Row():
652
+ output_input = gr.Textbox(
653
+ label="Model output to check",
654
+ placeholder="e.g., 'You should take 500mg of metformin twice daily.'",
655
+ lines=3,
656
+ )
657
+ output_result = gr.Markdown()
658
+ output_btn = gr.Button("Check Output Safety", variant="secondary")
659
+ output_btn.click(test_output_safety, inputs=output_input, outputs=output_result)
660
+
661
+ # ── Tab 2: RAG Pipeline ─────────────────────────────────────────────────
662
+ with gr.Tab("πŸ” RAG Knowledge Base"):
663
+ gr.Markdown(
664
+ "### Query the PCOS Medical Knowledge Base\n"
665
+ "Ask any PCOS question and see which clinical guidelines are retrieved, "
666
+ "along with the augmented prompt that would be sent to the LLM."
667
+ )
668
+
669
+ rag_input = gr.Textbox(
670
+ label="Your PCOS question",
671
+ placeholder="e.g., 'What are the Rotterdam criteria for diagnosing PCOS?'",
672
+ lines=2,
673
+ )
674
+ rag_btn = gr.Button("πŸ” Retrieve & Build Prompt", variant="primary")
675
+
676
+ gr.Markdown("#### Example Queries")
677
+ with gr.Row():
678
+ for q in ["How is PCOS diagnosed?", "Does PCOS affect fertility?",
679
+ "What diet is best for PCOS?", "Why does PCOS cause depression?",
680
+ "What is insulin resistance in PCOS?"]:
681
+ gr.Button(q, size="sm").click(lambda x=q: x, outputs=[rag_input])
682
+
683
+ with gr.Row():
684
+ rag_context = gr.Markdown(label="Retrieved Documents")
685
+ with gr.Accordion("πŸ“‹ Sources & Relevance Scores", open=False):
686
+ rag_sources = gr.Markdown()
687
+ with gr.Accordion("πŸ“ Full Augmented Prompt (sent to LLM)", open=False):
688
+ rag_prompt = gr.Markdown()
689
+
690
+ rag_btn.click(rag_query, inputs=rag_input, outputs=[rag_context, rag_sources, rag_prompt])
691
+
692
+ # ── Tab 3: Dataset Generator ────────────────────────────────────────────
693
+ with gr.Tab("πŸ“Š Dataset Preview"):
694
+ gr.Markdown(
695
+ "### Synthetic PCOS Training Data\n"
696
+ "Preview the 3 types of synthetic training data generated by the pipeline:\n"
697
+ "- 🌑️ **Tracking Logs** β€” Daily menstrual/BBT/symptom logs with AI interpretations\n"
698
+ "- πŸ“‹ **Clinical Q&A** β€” Guideline-grounded questions and answers\n"
699
+ "- πŸ’› **Empathetic Conversations** β€” Emotional support exchanges\n\n"
700
+ "**Plus 5 HuggingFace datasets** (350K+ examples total) preprocessed into ChatML format."
701
+ )
702
+
703
+ with gr.Row():
704
+ n_slider = gr.Slider(minimum=5, maximum=30, value=10, step=1, label="Number of samples")
705
+ gen_btn = gr.Button("🎲 Generate Samples", variant="primary")
706
+
707
+ dataset_table = gr.Dataframe(
708
+ headers=["Type", "Subtype", "User Message (preview)", "Assistant Response (preview)"],
709
+ datatype=["str", "str", "str", "str"],
710
+ wrap=True,
711
+ interactive=False,
712
+ )
713
+ gen_btn.click(generate_sample_data, inputs=n_slider, outputs=dataset_table)
714
+
715
+ gr.Markdown(
716
+ "### πŸ“¦ Hub Datasets Used\n\n"
717
+ "| # | Dataset | Size | Purpose |\n"
718
+ "|---|---------|------|---------|\n"
719
+ "| 1 | [lavita/ChatDoctor-HealthCareMagic-100k](https://huggingface.co/datasets/lavita/ChatDoctor-HealthCareMagic-100k) | 100K | Real patient-doctor Q&A |\n"
720
+ "| 2 | [ruslanmv/ai-medical-chatbot](https://huggingface.co/datasets/ruslanmv/ai-medical-chatbot) | 250K+ | High-volume medical dialogues |\n"
721
+ "| 3 | [medalpaca/medical_meadow_wikidoc_patient_information](https://huggingface.co/datasets/medalpaca/medical_meadow_wikidoc_patient_information) | 5.5K | Clinical knowledge (plain language) |\n"
722
+ "| 4 | [Amod/mental_health_counseling_conversations](https://huggingface.co/datasets/Amod/mental_health_counseling_conversations) | 3.5K | Empathetic counseling |\n"
723
+ "| 5 | [Malikeh1375/medical-question-answering-datasets](https://huggingface.co/datasets/Malikeh1375/medical-question-answering-datasets) | 152MB | Unified 46-dataset bundle |"
724
+ )
725
+
726
+ # ── Tab 4: Training Config ──────────────────────────────────────────────
727
+ with gr.Tab("βš™οΈ Training Config"):
728
+ gr.Markdown(
729
+ "### LoRA Fine-Tuning Presets\n"
730
+ "Select a training preset to view the full configuration. "
731
+ "All hyperparameters are grounded in published medical LLM papers."
732
+ )
733
+
734
+ preset_selector = gr.Radio(
735
+ choices=["Quick Test", "Standard", "High Quality", "Maximum (Full FT Parity)"],
736
+ value="Standard",
737
+ label="Select Preset",
738
+ )
739
+ config_display = gr.Markdown()
740
+ preset_selector.change(get_training_config, inputs=preset_selector, outputs=config_display)
741
+
742
+ # Load default
743
+ demo.load(get_training_config, inputs=preset_selector, outputs=config_display)
744
+
745
+ gr.Markdown(
746
+ "### πŸ—οΈ Architecture Overview\n\n"
747
+ "```\n"
748
+ "β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n"
749
+ "β”‚ User Input │───▢│ Llama Guard │───▢│ PCOS LLM β”‚\n"
750
+ "β”‚ β”‚ β”‚ (Input Safety) β”‚ β”‚ + RAG Pipeline β”‚\n"
751
+ "β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜\n"
752
+ " β”‚\n"
753
+ " β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”\n"
754
+ " β”‚ NeMo Guardrails │◀──│ Llama Guard β”‚\n"
755
+ " β”‚ (Topic Control) β”‚ β”‚ (Output Safety) β”‚\n"
756
+ " β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜\n"
757
+ " β”‚\n"
758
+ " β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”\n"
759
+ " β”‚ Safe Response β”‚\n"
760
+ " β”‚ + Disclaimer β”‚\n"
761
+ " οΏ½οΏ½οΏ½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜\n"
762
+ "```\n\n"
763
+ "### πŸ“š Paper References\n\n"
764
+ "| Paper | Key Contribution |\n"
765
+ "|-------|------------------|\n"
766
+ "| Med42 (arXiv:2404.14779) | LoRA all-linear targets outperform attention-only |\n"
767
+ "| BioMistral (arXiv:2402.10373) | Medical pre-training on PMC, r=16, dropout=0.05 |\n"
768
+ "| LoRA Without Regret (2025) | r=256 matches full fine-tuning at 67% compute |\n"
769
+ "| ChatDoctor (arXiv:2303.14070) | 100K patient-doctor conversation dataset |\n"
770
+ "| SoulChat (arXiv:2311.00273) | Empathetic health conversation patterns |\n"
771
+ "| Llama Guard (arXiv:2312.06674) | Input/output safety classification, F1=0.945 |\n"
772
+ "| NeMo Guardrails (arXiv:2310.10501) | Programmable safety rails via Colang |"
773
+ )
774
+
775
+ gr.Markdown(
776
+ "---\n"
777
+ "**Built by [gthor7](https://huggingface.co/gthor7)** | "
778
+ "[πŸ“¦ Full Code Repository](https://huggingface.co/gthor7/pcos-health-ai-pipeline) | "
779
+ "🩺 *This is a research demo β€” not medical advice.*"
780
+ )
781
+
782
+
783
+ if __name__ == "__main__":
784
+ demo.launch(theme=gr.themes.Soft(), css=CSS)