Muhammadidrees commited on
Commit
191951d
·
verified ·
1 Parent(s): dcfa02a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +532 -0
app.py ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import torch
4
+ import threading
5
+ from datetime import datetime
6
+ from huggingface_hub import login
7
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
8
+ from reportlab.lib.pagesizes import letter
9
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
10
+ from reportlab.lib.units import inch
11
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
12
+ from reportlab.lib import colors
13
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT
14
+ import re
15
+
16
+ print("=" * 50)
17
+ print("🚀 Starting AI Health Assistant")
18
+ print(f"Torch version: {torch.__version__}")
19
+ print(f"CUDA available: {torch.cuda.is_available()}")
20
+ print("=" * 50)
21
+
22
+ # ------------------------------------------------------
23
+ # 🔹 STEP 1: Authentication for HF Spaces
24
+ # ------------------------------------------------------
25
+ hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")
26
+
27
+ if hf_token:
28
+ print("✅ HF Token found, logging in...")
29
+ login(token=hf_token)
30
+ else:
31
+ print("⚠️ No HF_TOKEN found - attempting to load model without authentication")
32
+
33
+ # ------------------------------------------------------
34
+ # 🔹 STEP 2: Load model and tokenizer
35
+ # ------------------------------------------------------
36
+ model_id = "google/medgemma-27b-text-it"
37
+
38
+ print("🔄 Loading tokenizer...")
39
+ try:
40
+ tokenizer = AutoTokenizer.from_pretrained(model_id, token=hf_token)
41
+ if tokenizer.pad_token is None:
42
+ tokenizer.pad_token = tokenizer.eos_token
43
+ print("✅ Tokenizer loaded successfully!")
44
+ except Exception as e:
45
+ print(f"❌ Error loading tokenizer: {e}")
46
+ raise
47
+
48
+ print("🔄 Loading model... (this may take several minutes)")
49
+ try:
50
+ model = AutoModelForCausalLM.from_pretrained(
51
+ model_id,
52
+ token=hf_token,
53
+ torch_dtype=torch.bfloat16,
54
+ device_map="auto",
55
+ low_cpu_mem_usage=True
56
+ )
57
+ print("✅ Model loaded successfully!")
58
+ except Exception as e:
59
+ print(f"❌ Error loading model: {e}")
60
+ raise
61
+
62
+ # ------------------------------------------------------
63
+ # 🔹 STEP 3: Input validation helpers
64
+ # ------------------------------------------------------
65
+ def validate_numeric(value, name, min_val=0, max_val=None):
66
+ """Validate numeric input"""
67
+ try:
68
+ num = float(value)
69
+ if num < min_val:
70
+ return False, f"{name} must be >= {min_val}"
71
+ if max_val and num > max_val:
72
+ return False, f"{name} must be <= {max_val}"
73
+ return True, num
74
+ except (ValueError, TypeError):
75
+ return False, f"{name} must be a valid number"
76
+
77
+ # ------------------------------------------------------
78
+ # 🔹 STEP 4: PDF Generation Function
79
+ # ------------------------------------------------------
80
+ def parse_markdown_table(md_text):
81
+ """Extract and parse markdown tables from text"""
82
+ tables = []
83
+ lines = md_text.split('\n')
84
+
85
+ i = 0
86
+ while i < len(lines):
87
+ line = lines[i].strip()
88
+ if '|' in line and line.startswith('|'):
89
+ table_lines = [line]
90
+ i += 1
91
+ if i < len(lines) and '|' in lines[i]:
92
+ i += 1
93
+ while i < len(lines) and '|' in lines[i] and lines[i].strip():
94
+ table_lines.append(lines[i].strip())
95
+ i += 1
96
+
97
+ parsed_table = []
98
+ for tline in table_lines:
99
+ cells = [cell.strip() for cell in tline.split('|')[1:-1]]
100
+ if cells:
101
+ parsed_table.append(cells)
102
+
103
+ if len(parsed_table) > 1:
104
+ tables.append(parsed_table)
105
+ i += 1
106
+
107
+ return tables
108
+
109
+ def create_pdf_report(patient_data, biomarkers, ai_response, filename="health_report.pdf"):
110
+ """Generate a professional PDF report"""
111
+
112
+ doc = SimpleDocTemplate(filename, pagesize=letter,
113
+ topMargin=0.5*inch, bottomMargin=0.5*inch,
114
+ leftMargin=0.75*inch, rightMargin=0.75*inch)
115
+
116
+ story = []
117
+ styles = getSampleStyleSheet()
118
+
119
+ # Custom styles
120
+ title_style = ParagraphStyle(
121
+ 'CustomTitle',
122
+ parent=styles['Heading1'],
123
+ fontSize=24,
124
+ textColor=colors.HexColor('#1a5490'),
125
+ spaceAfter=30,
126
+ alignment=TA_CENTER,
127
+ fontName='Helvetica-Bold'
128
+ )
129
+
130
+ heading_style = ParagraphStyle(
131
+ 'CustomHeading',
132
+ parent=styles['Heading2'],
133
+ fontSize=14,
134
+ textColor=colors.HexColor('#2c5aa0'),
135
+ spaceAfter=12,
136
+ spaceBefore=12,
137
+ fontName='Helvetica-Bold'
138
+ )
139
+
140
+ subheading_style = ParagraphStyle(
141
+ 'CustomSubHeading',
142
+ parent=styles['Heading3'],
143
+ fontSize=12,
144
+ textColor=colors.HexColor('#444444'),
145
+ spaceAfter=10,
146
+ spaceBefore=10,
147
+ fontName='Helvetica-Bold'
148
+ )
149
+
150
+ normal_style = ParagraphStyle(
151
+ 'CustomNormal',
152
+ parent=styles['Normal'],
153
+ fontSize=10,
154
+ spaceAfter=8,
155
+ leading=14
156
+ )
157
+
158
+ # Title
159
+ story.append(Paragraph("AI Health Assessment Report", title_style))
160
+ story.append(Paragraph(f"Generated on: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}",
161
+ ParagraphStyle('Date', parent=styles['Normal'], fontSize=9,
162
+ textColor=colors.grey, alignment=TA_CENTER)))
163
+ story.append(Spacer(1, 0.3*inch))
164
+
165
+ # Patient Information Section
166
+ story.append(Paragraph("Patient Information", heading_style))
167
+
168
+ patient_table_data = [
169
+ ['Age', f"{patient_data['age']} years", 'Gender', patient_data['gender']],
170
+ ['Height', f"{patient_data['height']} cm", 'Weight', f"{patient_data['weight']} kg"],
171
+ ['BMI', f"{patient_data['weight'] / ((patient_data['height']/100)**2):.1f}", 'Report ID', f"RPT-{datetime.now().strftime('%Y%m%d%H%M')}"]
172
+ ]
173
+
174
+ patient_table = Table(patient_table_data, colWidths=[1.2*inch, 1.8*inch, 1.2*inch, 1.8*inch])
175
+ patient_table.setStyle(TableStyle([
176
+ ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#f0f4f8')),
177
+ ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
178
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
179
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
180
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
181
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
182
+ ('FONTNAME', (2, 0), (2, -1), 'Helvetica-Bold'),
183
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
184
+ ('TOPPADDING', (0, 0), (-1, -1), 8),
185
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
186
+ ]))
187
+
188
+ story.append(patient_table)
189
+ story.append(Spacer(1, 0.2*inch))
190
+
191
+ # Biomarkers Input Section
192
+ story.append(Paragraph("Laboratory Biomarkers - Input Values", heading_style))
193
+
194
+ biomarker_table_data = [
195
+ ['Biomarker', 'Value', 'Unit', 'Biomarker', 'Value', 'Unit']
196
+ ]
197
+
198
+ biomarker_list = [
199
+ ('Albumin', biomarkers['albumin'], 'g/dL'),
200
+ ('Creatinine', biomarkers['creatinine'], 'mg/dL'),
201
+ ('Glucose', biomarkers['glucose'], 'mg/dL'),
202
+ ('CRP', biomarkers['crp'], 'mg/L'),
203
+ ('MCV', biomarkers['mcv'], 'fL'),
204
+ ('RDW', biomarkers['rdw'], '%'),
205
+ ('ALP', biomarkers['alp'], 'U/L'),
206
+ ('WBC', biomarkers['wbc'], 'x10^3/uL'),
207
+ ('Lymphocytes', biomarkers['lymphocytes'], '%'),
208
+ ('Hemoglobin', biomarkers['hb'], 'g/dL'),
209
+ ('Plasma (PV)', biomarkers['pv'], 'mL'),
210
+ ]
211
+
212
+ for i in range(0, len(biomarker_list), 2):
213
+ row = list(biomarker_list[i])
214
+ if i + 1 < len(biomarker_list):
215
+ row.extend(list(biomarker_list[i + 1]))
216
+ else:
217
+ row.extend(['', '', ''])
218
+ biomarker_table_data.append(row)
219
+
220
+ biomarker_table = Table(biomarker_table_data, colWidths=[1.4*inch, 0.9*inch, 0.7*inch, 1.4*inch, 0.9*inch, 0.7*inch])
221
+ biomarker_table.setStyle(TableStyle([
222
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2c5aa0')),
223
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
224
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
225
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
226
+ ('FONTSIZE', (0, 0), (-1, 0), 11),
227
+ ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
228
+ ('FONTSIZE', (0, 1), (-1, -1), 9),
229
+ ('BACKGROUND', (0, 1), (-1, -1), colors.white),
230
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
231
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f8f9fa')]),
232
+ ('TOPPADDING', (0, 0), (-1, -1), 6),
233
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
234
+ ]))
235
+
236
+ story.append(biomarker_table)
237
+ story.append(Spacer(1, 0.3*inch))
238
+
239
+ # AI Analysis Section
240
+ story.append(PageBreak())
241
+ story.append(Paragraph("AI-Generated Health Analysis", heading_style))
242
+ story.append(Spacer(1, 0.1*inch))
243
+
244
+ sections = ai_response.split('###')
245
+
246
+ for section in sections:
247
+ if not section.strip():
248
+ continue
249
+
250
+ lines = section.strip().split('\n')
251
+ section_title = lines[0].strip()
252
+ section_content = '\n'.join(lines[1:]).strip()
253
+
254
+ if section_title:
255
+ story.append(Paragraph(section_title, subheading_style))
256
+
257
+ if '|' in section_content:
258
+ tables = parse_markdown_table(section_content)
259
+
260
+ for table_data in tables:
261
+ if len(table_data) > 1:
262
+ pdf_table = Table(table_data, repeatRows=1)
263
+
264
+ table_style = TableStyle([
265
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2c5aa0')),
266
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
267
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
268
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
269
+ ('FONTSIZE', (0, 0), (-1, 0), 9),
270
+ ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
271
+ ('FONTSIZE', (0, 1), (-1, -1), 8),
272
+ ('BACKGROUND', (0, 1), (-1, -1), colors.white),
273
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
274
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f8f9fa')]),
275
+ ('TOPPADDING', (0, 0), (-1, -1), 6),
276
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
277
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'),
278
+ ])
279
+
280
+ pdf_table.setStyle(table_style)
281
+ story.append(pdf_table)
282
+ story.append(Spacer(1, 0.15*inch))
283
+
284
+ text_content = re.sub(r'\|[^\n]+\n', '', section_content)
285
+ text_content = re.sub(r'\n\s*\n', '\n', text_content).strip()
286
+
287
+ if text_content:
288
+ for para in text_content.split('\n'):
289
+ if para.strip():
290
+ story.append(Paragraph(para.strip(), normal_style))
291
+ else:
292
+ for para in section_content.split('\n'):
293
+ if para.strip():
294
+ story.append(Paragraph(para.strip(), normal_style))
295
+
296
+ story.append(Spacer(1, 0.1*inch))
297
+
298
+ # Footer/Disclaimer
299
+ story.append(Spacer(1, 0.2*inch))
300
+ disclaimer_style = ParagraphStyle(
301
+ 'Disclaimer',
302
+ parent=styles['Normal'],
303
+ fontSize=8,
304
+ textColor=colors.HexColor('#666666'),
305
+ alignment=TA_CENTER,
306
+ borderWidth=1,
307
+ borderColor=colors.HexColor('#cccccc'),
308
+ borderPadding=10,
309
+ backColor=colors.HexColor('#fffef0')
310
+ )
311
+
312
+ story.append(Paragraph(
313
+ "<b>IMPORTANT DISCLAIMER:</b> This report is generated by an AI system for informational purposes only. "
314
+ "It is NOT a medical diagnosis and should NOT replace professional medical advice. "
315
+ "Always consult with qualified healthcare professionals for medical decisions and treatment.",
316
+ disclaimer_style
317
+ ))
318
+
319
+ doc.build(story)
320
+ return filename
321
+
322
+ # ------------------------------------------------------
323
+ # 🔹 STEP 5: Define the model interaction function
324
+ # ------------------------------------------------------
325
+ def respond(albumin, creatinine, glucose, crp, mcv, rdw, alp, wbc, lymphocytes, hb, pv, age, gender, height, weight):
326
+ try:
327
+ # Validate all inputs
328
+ validations = [
329
+ validate_numeric(albumin, "Albumin", 0, 10),
330
+ validate_numeric(creatinine, "Creatinine", 0, 20),
331
+ validate_numeric(glucose, "Glucose", 0, 1000),
332
+ validate_numeric(crp, "CRP", 0, 500),
333
+ validate_numeric(mcv, "MCV", 0, 200),
334
+ validate_numeric(rdw, "RDW", 0, 50),
335
+ validate_numeric(alp, "ALP", 0, 1000),
336
+ validate_numeric(wbc, "WBC", 0, 100),
337
+ validate_numeric(lymphocytes, "Lymphocytes", 0, 100),
338
+ validate_numeric(hb, "Hemoglobin", 0, 25),
339
+ validate_numeric(pv, "Plasma", 0, 10000),
340
+ validate_numeric(age, "Age", 0, 150),
341
+ validate_numeric(height, "Height", 0, 300),
342
+ validate_numeric(weight, "Weight", 0, 500),
343
+ ]
344
+
345
+ for is_valid, result in validations:
346
+ if not is_valid:
347
+ return f"❌ Validation Error: {result}", None
348
+
349
+ validated_values = [result for is_valid, result in validations]
350
+ albumin, creatinine, glucose, crp, mcv, rdw, alp, wbc, lymphocytes, hb, pv, age, height, weight = validated_values
351
+
352
+ system_message = (
353
+ "You are an AI Health Assistant that analyzes laboratory biomarkers "
354
+ "and generates structured, patient-friendly health summaries.\n\n"
355
+ "Your task is to evaluate the provided biomarkers and generate an AI-driven medical report "
356
+ "with insights, observations, and clear explanations.\n"
357
+ "You must strictly follow this structured format:\n\n"
358
+ "### Tabular Mapping\n"
359
+ "- Always include a Markdown table with exactly five columns:\n"
360
+ "| Biomarker | Value | Status (Low/Normal/High) | AI-Inferred Insight | Reference Range |\n"
361
+ "- Include **all available biomarkers** below:\n"
362
+ "Albumin, Creatinine, Glucose, CRP, MCV, RDW, ALP, WBC, Lymphocytes, Hemoglobin, Plasma (PV)\n"
363
+ "- The first row after the header must begin directly with 'Albumin'.\n"
364
+ "- Each biomarker must appear exactly once as a separate row.\n\n"
365
+ "### Executive Summary\n"
366
+ "- List Top 3 Health Priorities.\n"
367
+ "- Highlight Key Strengths or normal biomarkers.\n\n"
368
+ "### System-Specific Analysis\n"
369
+ "- Summarize findings grouped by organ systems (Liver, Kidney, Immune, Blood, etc.).\n"
370
+ "- Status: 'Optimal' | 'Monitor' | 'Needs Attention'.\n"
371
+ "- Provide 2-3 sentences of explanation in plain, supportive language.\n\n"
372
+ "### Personalized Action Plan\n"
373
+ "- Provide categorized recommendations (Nutrition, Lifestyle, Testing, Medical Consultation).\n"
374
+ "- Never recommend medication or treatment.\n\n"
375
+ "### Interaction Alerts\n"
376
+ "- Highlight potential relationships between markers (e.g., high CRP + low Albumin).\n\n"
377
+ "### Constraints\n"
378
+ "- Never give a diagnosis or prescribe medicine.\n"
379
+ "- Never use data not present in the input.\n"
380
+ "- Always recommend consulting a healthcare professional.\n"
381
+ "- Always include normal reference ranges for each biomarker.\n"
382
+ "- Use simple, clear, patient-friendly language.\n"
383
+ "- Provide additional explanation instead of just writing direct points.\n"
384
+ "- Be concise and avoid repetition."
385
+ )
386
+
387
+ user_message = (
388
+ f"Patient Info:\n"
389
+ f"- Age: {age} years\n"
390
+ f"- Gender: {gender}\n"
391
+ f"- Height: {height} cm\n"
392
+ f"- Weight: {weight} kg\n\n"
393
+ f"Biomarkers:\n"
394
+ f"- Albumin: {albumin} g/dL\n"
395
+ f"- Creatinine: {creatinine} mg/dL\n"
396
+ f"- Glucose: {glucose} mg/dL\n"
397
+ f"- CRP: {crp} mg/L\n"
398
+ f"- MCV: {mcv} fL\n"
399
+ f"- RDW: {rdw} %\n"
400
+ f"- ALP: {alp} U/L\n"
401
+ f"- WBC: {wbc} x10^3/μL\n"
402
+ f"- Lymphocytes: {lymphocytes} %\n"
403
+ f"- Hemoglobin: {hb} g/dL\n"
404
+ f"- Plasma (PV): {pv} mL"
405
+ )
406
+
407
+ messages = [
408
+ {"role": "system", "content": system_message},
409
+ {"role": "user", "content": user_message}
410
+ ]
411
+
412
+ encodings = tokenizer.apply_chat_template(
413
+ messages,
414
+ return_tensors="pt",
415
+ add_special_tokens=True,
416
+ padding=True,
417
+ truncation=True
418
+ ).to(model.device)
419
+
420
+ attention_mask = encodings.ne(tokenizer.pad_token_id)
421
+
422
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
423
+ generation_kwargs = dict(
424
+ input_ids=encodings,
425
+ attention_mask=attention_mask,
426
+ max_new_tokens=2000,
427
+ temperature=0.2,
428
+ top_p=0.9,
429
+ do_sample=True,
430
+ streamer=streamer,
431
+ pad_token_id=tokenizer.pad_token_id
432
+ )
433
+
434
+ thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
435
+ thread.start()
436
+
437
+ response = ""
438
+ for token in streamer:
439
+ response += token
440
+
441
+ thread.join()
442
+
443
+ patient_data = {
444
+ 'age': age,
445
+ 'gender': gender,
446
+ 'height': height,
447
+ 'weight': weight
448
+ }
449
+
450
+ biomarkers = {
451
+ 'albumin': albumin,
452
+ 'creatinine': creatinine,
453
+ 'glucose': glucose,
454
+ 'crp': crp,
455
+ 'mcv': mcv,
456
+ 'rdw': rdw,
457
+ 'alp': alp,
458
+ 'wbc': wbc,
459
+ 'lymphocytes': lymphocytes,
460
+ 'hb': hb,
461
+ 'pv': pv
462
+ }
463
+
464
+ pdf_filename = f"health_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
465
+ pdf_path = create_pdf_report(patient_data, biomarkers, response, pdf_filename)
466
+
467
+ return response, pdf_path
468
+
469
+ except Exception as e:
470
+ import traceback
471
+ error_msg = f"❌ Error generating report: {str(e)}\n\n{traceback.format_exc()}"
472
+ print(error_msg)
473
+ return error_msg, None
474
+
475
+ # ------------------------------------------------------
476
+ # 🔹 STEP 6: Gradio UI
477
+ # ------------------------------------------------------
478
+ with gr.Blocks(theme=gr.themes.Soft(), css="""
479
+ .output-markdown table { border-collapse: collapse; width: 100%; margin: 20px 0; }
480
+ .output-markdown th { background-color: #2c5aa0; color: white; padding: 12px; text-align: left; }
481
+ .output-markdown td { padding: 10px; border: 1px solid #ddd; }
482
+ .output-markdown tr:nth-child(even) { background-color: #f8f9fa; }
483
+ .output-markdown h3 { color: #2c5aa0; margin-top: 20px; }
484
+ """) as demo:
485
+ gr.Markdown("# 🧪 AI Health Assistant with PDF Export")
486
+ gr.Markdown("*Analyze biomarkers with AI-powered insights and download a professional PDF report.*")
487
+
488
+ with gr.Row():
489
+ with gr.Column():
490
+ gr.Markdown("### 📊 Biomarkers")
491
+ albumin = gr.Number(label="Albumin (g/dL)", value=4.5, minimum=0, maximum=10)
492
+ creatinine = gr.Number(label="Creatinine (mg/dL)", value=1.5, minimum=0, maximum=20)
493
+ glucose = gr.Number(label="Glucose (mg/dL, fasting)", value=160, minimum=0, maximum=1000)
494
+ crp = gr.Number(label="CRP (mg/L)", value=2.5, minimum=0, maximum=500)
495
+ mcv = gr.Number(label="MCV (fL)", value=90, minimum=0, maximum=200)
496
+ rdw = gr.Number(label="RDW (%)", value=13, minimum=0, maximum=50)
497
+ alp = gr.Number(label="ALP (U/L)", value=70, minimum=0, maximum=1000)
498
+ wbc = gr.Number(label="WBC (10^3/μL)", value=7.5, minimum=0, maximum=100)
499
+ lymphocytes = gr.Number(label="Lymphocytes (%)", value=30, minimum=0, maximum=100)
500
+ hb = gr.Number(label="Hemoglobin (g/dL)", value=14.5, minimum=0, maximum=25)
501
+ pv = gr.Number(label="Plasma (PV) (mL)", value=3000, minimum=0, maximum=10000)
502
+
503
+ with gr.Column():
504
+ gr.Markdown("### 👤 Patient Information")
505
+ age = gr.Number(label="Age (years)", value=30, minimum=0, maximum=150)
506
+ gender = gr.Dropdown(choices=["Male", "Female"], label="Gender", value="Male")
507
+ height = gr.Number(label="Height (cm)", value=170, minimum=0, maximum=300)
508
+ weight = gr.Number(label="Weight (kg)", value=70, minimum=0, maximum=500)
509
+
510
+ btn = gr.Button("🔬 Generate Health Report & PDF", variant="primary", size="lg")
511
+
512
+ with gr.Row():
513
+ output = gr.Markdown(label="AI Health Report")
514
+
515
+ with gr.Row():
516
+ pdf_output = gr.File(label="📄 Download PDF Report")
517
+
518
+ gr.Markdown("---")
519
+ gr.Markdown("⚠️ **Disclaimer:** This tool provides informational insights only and is not a substitute for professional medical advice.")
520
+
521
+ btn.click(
522
+ respond,
523
+ inputs=[albumin, creatinine, glucose, crp, mcv, rdw, alp, wbc, lymphocytes, hb, pv, age, gender, height, weight],
524
+ outputs=[output, pdf_output]
525
+ )
526
+
527
+ # ------------------------------------------------------
528
+ # 🔹 STEP 7: Launch for Hugging Face Spaces
529
+ # ------------------------------------------------------
530
+ print("🚀 Launching Gradio interface...")
531
+ demo.queue()
532
+ demo.launch()