sehatguard commited on
Commit
ac21e89
·
verified ·
1 Parent(s): 7d59777

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +19 -0
  2. README.md +27 -3
  3. app.py +286 -0
  4. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ COPY . .
13
+
14
+ EXPOSE 7860
15
+
16
+ ENV GRADIO_SERVER_NAME="0.0.0.0"
17
+ ENV GRADIO_SERVER_PORT="7860"
18
+
19
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,11 +1,35 @@
1
  ---
2
  title: Sehat Guard
3
- emoji: 📊
4
- colorFrom: yellow
5
  colorTo: blue
6
  sdk: docker
7
  pinned: false
8
  license: mit
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Sehat Guard
3
+ emoji: 🛡️
4
+ colorFrom: green
5
  colorTo: blue
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
+ short_description: AI Medical Assistant for Pakistan
10
  ---
11
 
12
+ # 🛡️ Sehat Guard | صحت گارڈ
13
+
14
+ **AI Medical Assistant for Pakistan** — powered by NVIDIA Build
15
+
16
+ ### Features
17
+ - 📋 **Lab report upload** — PDF or photo (CBC, LFT, RFT, thyroid, X-ray, ECG, etc.)
18
+ - 🩺 General Diagnosis from symptoms
19
+ - 👨‍⚕️ Doctor Mode — clinical decision support
20
+ - 🚨 Emergency Triage — 🔴🟡🟢 urgency rating
21
+ - 💊 Medication info — Pakistan brands + DRAP
22
+ - 🇵🇰 English + Urdu (اردو) support
23
+
24
+ ### Setup
25
+ Add your OpenAI API key as a **Secret**:
26
+ - **Name:** `OPENAI_API_KEY`
27
+ - **Value:** `sk-...`
28
+
29
+ Go to: Space Settings → Variables and Secrets → New Secret
30
+
31
+ ### Emergency Numbers (Pakistan)
32
+ - **1122** — Rescue / Ambulance
33
+ - **115** — Edhi Foundation
34
+
35
+ > ⚠️ For informational guidance only. Always consult a licensed doctor.
app.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ import tempfile
4
+ import gradio as gr
5
+ from openai import OpenAI
6
+ import fitz # PyMuPDF
7
+
8
+ # ── NVIDIA Build client (key stored in HF Secret) ─────────────────────────────
9
+ client = OpenAI(
10
+ base_url="https://integrate.api.nvidia.com/v1",
11
+ api_key=os.environ.get("NVIDIA_API_KEY"),
12
+ )
13
+
14
+ MODEL = "openai/gpt-4o" # GPT-4o via NVIDIA Build
15
+
16
+ # ── System prompts ────────────────────────────────────────────────────────────
17
+ SYSTEM_PROMPTS = {
18
+ "🩺 General Diagnosis": """You are Sehat Guard, an expert AI medical assistant for Pakistan.
19
+ Help patients understand symptoms and lab results.
20
+
21
+ RULES:
22
+ - Respond in the SAME LANGUAGE as the user (English or Urdu script).
23
+ - When a lab report is shared, analyze ALL values, flag abnormal ones (HIGH/LOW/NORMAL), explain in simple language, give overall interpretation.
24
+ - Structure: 📋 Lab Analysis | ⚠️ Abnormal Values | 💡 What This Means | 🩺 Possible Conditions | ✅ Recommendations | 🏥 When to See a Doctor.
25
+ - Consider Pakistani diseases: dengue, typhoid, TB, malaria, hepatitis B/C.
26
+ - Emergency numbers: 1122 (Rescue) | 115 (Edhi).
27
+ - End with: AI guidance only, not a substitute for professional medical care.""",
28
+
29
+ "👨‍⚕️ Doctor Mode": """You are Sehat Guard DOCTOR MODE — clinical decision support for licensed physicians in Pakistan.
30
+ When lab reports are uploaded:
31
+ - Interpret all values with clinical precision
32
+ - Flag critical values requiring immediate action
33
+ - Differential diagnosis based on the panel
34
+ - Suggest additional investigations
35
+ - Evidence-based treatment (WHO/DRAP guidelines)
36
+ - Drug dosages, interactions, contraindications
37
+ - ICD-10 codes where relevant
38
+ Respond in the same language as the user.""",
39
+
40
+ "🚨 Emergency Triage": """You are Sehat Guard TRIAGE MODULE.
41
+ When lab results or symptoms are shared:
42
+ - Flag any CRITICAL values immediately (K+ >6.5, Hb <5, Glucose >600, elevated Troponin)
43
+ - Assign: 🔴 IMMEDIATE / 🟡 URGENT / 🟢 NON-URGENT
44
+ - State exact reason for triage level
45
+ - Immediate action steps
46
+ - Pakistan emergency: 1122 | 115
47
+ Be direct and action-oriented. Respond in user's language.""",
48
+
49
+ "💊 Medications": """You are Sehat Guard MEDICATION MODULE.
50
+ When lab results or symptoms are shared:
51
+ - Identify conditions and recommend medications available in Pakistan
52
+ - Generic + brand names, dosage, frequency, duration
53
+ - Contraindications based on abnormal values
54
+ - Affordable alternatives for Pakistani patients
55
+ Prescriptions require a licensed doctor.
56
+ Respond in user's language.""",
57
+ }
58
+
59
+ DISCLAIMER = (
60
+ "⚠️ **Medical Disclaimer:** Sehat Guard is for informational guidance only. "
61
+ "Always consult a licensed doctor. "
62
+ "**Emergencies:** 1122 (Rescue) | 115 (Edhi)"
63
+ )
64
+
65
+ # ── Helpers ───────────────────────────────────────────────────────────────────
66
+ def encode_image_to_base64(image_path: str) -> str:
67
+ with open(image_path, "rb") as f:
68
+ return base64.b64encode(f.read()).decode("utf-8")
69
+
70
+ def pdf_to_base64_images(pdf_path: str) -> list:
71
+ doc = fitz.open(pdf_path)
72
+ images = []
73
+ for page in doc:
74
+ pix = page.get_pixmap(dpi=200)
75
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
76
+ pix.save(tmp.name)
77
+ images.append(encode_image_to_base64(tmp.name))
78
+ doc.close()
79
+ return images
80
+
81
+ def build_messages(system, history, user_text, image_b64_list=None):
82
+ messages = [{"role": "system", "content": system}]
83
+ for human, assistant in history:
84
+ messages.append({"role": "user", "content": human})
85
+ messages.append({"role": "assistant", "content": assistant})
86
+
87
+ if image_b64_list:
88
+ content = []
89
+ for b64 in image_b64_list:
90
+ content.append({
91
+ "type": "image_url",
92
+ "image_url": {"url": f"data:image/png;base64,{b64}", "detail": "high"},
93
+ })
94
+ content.append({"type": "text", "text": user_text})
95
+ messages.append({"role": "user", "content": content})
96
+ else:
97
+ messages.append({"role": "user", "content": user_text})
98
+
99
+ return messages
100
+
101
+ # ── Streaming chat function ───────────────────────────────────────────────────
102
+ def chat(message, history, mode, file_upload, age, gender, weight, conditions, allergies):
103
+ if not message.strip() and file_upload is None:
104
+ yield "", history
105
+ return
106
+
107
+ system = SYSTEM_PROMPTS.get(mode, SYSTEM_PROMPTS["🩺 General Diagnosis"])
108
+
109
+ # Patient context
110
+ parts = []
111
+ if age: parts.append(f"Age: {int(age)}y")
112
+ if gender: parts.append(f"Gender: {gender}")
113
+ if weight: parts.append(f"Weight: {int(weight)}kg")
114
+ if conditions: parts.append(f"Conditions: {conditions}")
115
+ if allergies: parts.append(f"Allergies: {allergies}")
116
+ patient_ctx = ("\n\n[Patient Info — " + " | ".join(parts) + "]") if parts else ""
117
+
118
+ user_text = (message.strip() or "Please analyze this uploaded medical document and provide a detailed interpretation.") + patient_ctx
119
+ display_msg = message.strip() or "📄 File uploaded — please analyze"
120
+
121
+ try:
122
+ image_b64_list = None
123
+
124
+ if file_upload is not None:
125
+ ext = os.path.splitext(file_upload)[1].lower()
126
+ if ext == ".pdf":
127
+ image_b64_list = pdf_to_base64_images(file_upload)[:6]
128
+ display_msg = f"📄 PDF uploaded" + (f" — {message.strip()}" if message.strip() else "")
129
+ elif ext in [".jpg", ".jpeg", ".png", ".webp", ".bmp"]:
130
+ image_b64_list = [encode_image_to_base64(file_upload)]
131
+ display_msg = f"🖼️ Image uploaded" + (f" — {message.strip()}" if message.strip() else "")
132
+ else:
133
+ history.append((display_msg, "❌ Unsupported file type. Please upload PDF, JPG, or PNG."))
134
+ yield "", history
135
+ return
136
+
137
+ messages = build_messages(system, history, user_text, image_b64_list)
138
+
139
+ # ── Streaming response ──
140
+ stream = client.chat.completions.create(
141
+ model=MODEL,
142
+ messages=messages,
143
+ max_tokens=1500,
144
+ temperature=0.3,
145
+ stream=True,
146
+ )
147
+
148
+ partial_reply = ""
149
+ history = history + [(display_msg, "")]
150
+
151
+ for chunk in stream:
152
+ if not getattr(chunk, "choices", None):
153
+ continue
154
+ delta = chunk.choices[0].delta
155
+ token = getattr(delta, "content", None)
156
+ if token:
157
+ partial_reply += token
158
+ history[-1] = (display_msg, partial_reply)
159
+ yield "", history
160
+
161
+ except Exception as e:
162
+ err = f"❌ Error: {str(e)}\n\nCheck your `NVIDIA_API_KEY` secret in Hugging Face Space Settings."
163
+ history.append((display_msg, err))
164
+ yield "", history
165
+
166
+ # ── Gradio UI ─────────────────────────────────────────────────────────────────
167
+ CSS = """
168
+ body { background: #f0f7f3; }
169
+ .gradio-container { max-width: 1150px !important; margin: 0 auto; }
170
+ #header {
171
+ background: linear-gradient(135deg, #0f4230 0%, #1a6b4a 60%, #27a96b 100%);
172
+ padding: 24px 32px; border-radius: 16px; margin-bottom: 16px; text-align: center;
173
+ }
174
+ #header h1 { color: #fff; font-size: 2rem; margin: 0; }
175
+ #header p { color: #a8dfc2; font-size: 0.9rem; margin: 6px 0 0; }
176
+ #disclaimer {
177
+ background: #fff8e1; border: 1.5px solid #f9c84a;
178
+ border-radius: 10px; padding: 10px 16px;
179
+ font-size: 0.82rem; color: #5a4000; margin-bottom: 14px;
180
+ }
181
+ .message.user > div {
182
+ background: #1a6b4a !important; color: #fff !important;
183
+ border-radius: 18px 18px 4px 18px !important;
184
+ }
185
+ .message.bot > div {
186
+ background: #e8f5ee !important; color: #1c2c24 !important;
187
+ border-radius: 18px 18px 18px 4px !important;
188
+ border: 1px solid #d0e4d8 !important;
189
+ }
190
+ """
191
+
192
+ WELCOME = (
193
+ "👋 Hello! I'm **Sehat Guard** 🛡️ — your AI medical assistant for Pakistan.\n\n"
194
+ "**I can help with:**\n"
195
+ "• 📋 **Lab report analysis** — upload PDF or photo\n"
196
+ "• 🩺 Symptom assessment & diagnosis\n"
197
+ "• 🚨 Emergency triage (🔴🟡🟢)\n"
198
+ "• 💊 Medication information\n"
199
+ "• 👨‍⚕️ Clinical support for doctors\n\n"
200
+ "Type in **English** or **Urdu** (اردو). Upload a lab report on the left."
201
+ )
202
+
203
+ with gr.Blocks(css=CSS, title="Sehat Guard — AI Medical Assistant") as demo:
204
+
205
+ gr.HTML("""
206
+ <div id="header">
207
+ <h1>🛡️ Sehat Guard &nbsp;|&nbsp; <span style="font-family:serif;font-size:1.5rem">صحت گارڈ</span></h1>
208
+ <p>AI Medical Assistant · Pakistan · Lab Reports · English & Urdu · Powered by NVIDIA Build</p>
209
+ </div>
210
+ """)
211
+ gr.HTML(f'<div id="disclaimer">{DISCLAIMER}</div>')
212
+
213
+ with gr.Row():
214
+
215
+ # ── Sidebar ───────────────────────────────────────────────────────────
216
+ with gr.Column(scale=1, min_width=270):
217
+
218
+ mode = gr.Radio(
219
+ choices=list(SYSTEM_PROMPTS.keys()),
220
+ value="🩺 General Diagnosis",
221
+ label="Consultation Mode",
222
+ )
223
+
224
+ gr.Markdown("---\n### 📁 Upload Lab Report / X-Ray / Prescription")
225
+ file_upload = gr.File(
226
+ label="Upload PDF or Image",
227
+ file_types=[".pdf", ".jpg", ".jpeg", ".png", ".webp"],
228
+ file_count="single",
229
+ )
230
+ gr.Markdown("_Supports: CBC, LFT, RFT, HbA1c, X-ray, ECG, ultrasound, prescriptions_")
231
+
232
+ gr.Markdown("---\n### 👤 Patient Info *(optional)*")
233
+ age = gr.Number(label="Age (years)", precision=0, minimum=0, maximum=120)
234
+ gender = gr.Dropdown(["", "Male", "Female", "Other"], label="Gender", value="")
235
+ weight = gr.Number(label="Weight (kg)", precision=0, minimum=0)
236
+ conditions = gr.Textbox(label="Known Conditions", placeholder="e.g. Diabetes, Hypertension")
237
+ allergies = gr.Textbox(label="Allergies", placeholder="e.g. Penicillin")
238
+
239
+ gr.Markdown("""
240
+ ---
241
+ 🇵🇰 **Emergency Numbers**
242
+ - **1122** — Rescue / Ambulance
243
+ - **115** — Edhi Foundation
244
+ - **115** — Chippa Welfare
245
+ """)
246
+
247
+ # ── Chat ──────────────────────────────────────────────────────────────
248
+ with gr.Column(scale=3):
249
+
250
+ chatbot = gr.Chatbot(
251
+ value=[[None, WELCOME]],
252
+ label="Sehat Guard Chat",
253
+ height=520,
254
+ )
255
+
256
+ with gr.Row():
257
+ user_input = gr.Textbox(
258
+ placeholder="Describe symptoms or ask a question... / علامات بتائیں یا سوال پوچھیں...",
259
+ label="Your Message",
260
+ scale=5,
261
+ lines=2,
262
+ )
263
+ send_btn = gr.Button("Send ➤", scale=1, variant="primary")
264
+
265
+ gr.Examples(
266
+ examples=[
267
+ ["I have fever 38.5°C, body aches and headache for 2 days. Could it be dengue?"],
268
+ ["مجھے 2 دن سے بخار ہے اور جسم میں درد ہے، کیا یہ ڈینگی ہو سکتا ہے؟"],
269
+ ["Please analyze my uploaded CBC report and explain what's abnormal."],
270
+ ["My HbA1c is 9.2%, fasting glucose 210 mg/dL. What does this mean?"],
271
+ ["میرا بلڈ پریشر 160/100 ہے، کیا کرنا چاہیے؟"],
272
+ ["CBC: Hb 7.2, WBC 14,000, Platelets 45,000. What could this indicate?"],
273
+ ["Triage: 70yo male, sudden severe headache, vomiting, neck stiffness."],
274
+ ["TSH 12.5 mIU/L, T4 low. What medication is needed in Pakistan?"],
275
+ ],
276
+ inputs=user_input,
277
+ label="💡 Example Questions",
278
+ )
279
+
280
+ inputs = [user_input, chatbot, mode, file_upload, age, gender, weight, conditions, allergies]
281
+ outputs = [user_input, chatbot]
282
+
283
+ send_btn.click(fn=chat, inputs=inputs, outputs=outputs)
284
+ user_input.submit(fn=chat, inputs=inputs, outputs=outputs)
285
+
286
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio==4.44.0
2
+ openai>=1.40.0
3
+ PyMuPDF>=1.24.0