Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,486 +1,155 @@
|
|
| 1 |
-
import
|
| 2 |
-
import
|
| 3 |
-
import pandas as pd
|
| 4 |
-
import matplotlib.pyplot as plt
|
| 5 |
-
import seaborn as sns
|
| 6 |
import requests
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
"
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
"chol": {"label": "Cholesterol", "desc": "Serum cholesterol (mg/dl)",
|
| 30 |
-
"min": 100, "max": 600, "value": 200,
|
| 31 |
-
"healthy_range": "< 200", "risk_range": "240+"},
|
| 32 |
-
"fbs": {"label": "Fasting Blood Sugar", "desc": "> 120 mg/dl", "options": ["No", "Yes"],
|
| 33 |
-
"healthy": "No", "risk": "Yes"},
|
| 34 |
-
"restecg": {"label": "Resting ECG", "desc": "Resting electrocardiographic results",
|
| 35 |
-
"options": ["Normal", "ST-T wave abnormality", "Left ventricular hypertrophy"],
|
| 36 |
-
"healthy": "Normal", "risk": "Left ventricular hypertrophy"},
|
| 37 |
-
"thalach": {"label": "Max Heart Rate", "desc": "Maximum heart rate achieved",
|
| 38 |
-
"min": 60, "max": 220, "value": 150,
|
| 39 |
-
"healthy_range": "60-100 (resting)", "risk_range": "< 120 (exercise)"},
|
| 40 |
-
"exang": {"label": "Exercise Angina", "desc": "Exercise induced angina", "options": ["No", "Yes"],
|
| 41 |
-
"healthy": "No", "risk": "Yes"},
|
| 42 |
-
"oldpeak": {"label": "ST Depression", "desc": "ST depression induced by exercise",
|
| 43 |
-
"min": 0.0, "max": 6.0, "value": 1.0, "step": 0.1,
|
| 44 |
-
"healthy_range": "0-1", "risk_range": "2+"},
|
| 45 |
-
"slope": {"label": "ST Slope", "desc": "Slope of peak exercise ST segment",
|
| 46 |
-
"options": ["Upsloping", "Flat", "Downsloping"],
|
| 47 |
-
"healthy": "Upsloping", "risk": "Downsloping"},
|
| 48 |
-
"ca": {"label": "Major Vessels", "desc": "Number of major vessels colored by fluoroscopy",
|
| 49 |
-
"options": ["0", "1", "2", "3"],
|
| 50 |
-
"healthy": "0", "risk": "3"},
|
| 51 |
-
"thal": {"label": "Thalassemia", "desc": "Blood disorder called thalassemia",
|
| 52 |
-
"options": ["Normal", "Fixed defect", "Reversible defect"],
|
| 53 |
-
"healthy": "Normal", "risk": "Fixed defect"}
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
# Risk assessment thresholds
|
| 57 |
-
RISK_THRESHOLDS = {
|
| 58 |
-
"Low": 0.3,
|
| 59 |
-
"Moderate": 0.6,
|
| 60 |
-
"High": 1.0
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
# Sample healthy and unhealthy profiles
|
| 64 |
-
SAMPLE_PROFILES = {
|
| 65 |
-
"Healthy": {
|
| 66 |
-
"age": 45, "sex": "Female", "cp": "Typical angina",
|
| 67 |
-
"trestbps": 110, "chol": 180, "fbs": "No",
|
| 68 |
-
"restecg": "Normal", "thalach": 160, "exang": "No",
|
| 69 |
-
"oldpeak": 0.5, "slope": "Upsloping", "ca": "0",
|
| 70 |
-
"thal": "Normal"
|
| 71 |
},
|
| 72 |
-
"
|
| 73 |
-
"
|
| 74 |
-
"
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
},
|
| 79 |
-
"
|
| 80 |
-
"
|
| 81 |
-
"
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
}
|
| 86 |
}
|
| 87 |
|
| 88 |
-
def
|
| 89 |
-
"""
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
if inputs['trestbps'] >= 140:
|
| 111 |
-
risk_factors += 1
|
| 112 |
-
risk_details.append(f"- Blood Pressure: {inputs['trestbps']} mmHg (Stage 2 Hypertension)")
|
| 113 |
-
risk_params.append('trestbps')
|
| 114 |
-
elif inputs['trestbps'] >= 130:
|
| 115 |
-
risk_factors += 0.5
|
| 116 |
-
risk_details.append(f"- Blood Pressure: {inputs['trestbps']} mmHg (Elevated)")
|
| 117 |
-
if inputs['chol'] >= 240:
|
| 118 |
-
risk_factors += 1
|
| 119 |
-
risk_details.append(f"- Cholesterol: {inputs['chol']} mg/dL (High)")
|
| 120 |
-
risk_params.append('chol')
|
| 121 |
-
elif inputs['chol'] >= 200:
|
| 122 |
-
risk_factors += 0.5
|
| 123 |
-
risk_details.append(f"- Cholesterol: {inputs['chol']} mg/dL (Borderline High)")
|
| 124 |
-
if inputs['fbs'] == 'Yes':
|
| 125 |
-
risk_factors += 1
|
| 126 |
-
risk_details.append("- Fasting Blood Sugar > 120 mg/dL")
|
| 127 |
-
risk_params.append('fbs')
|
| 128 |
-
if inputs['restecg'] == 'Left ventricular hypertrophy':
|
| 129 |
-
risk_factors += 1
|
| 130 |
-
risk_details.append("- ECG: Left Ventricular Hypertrophy")
|
| 131 |
-
risk_params.append('restecg')
|
| 132 |
-
elif inputs['restecg'] == 'ST-T wave abnormality':
|
| 133 |
-
risk_factors += 0.5
|
| 134 |
-
risk_details.append("- ECG: ST-T Wave Abnormality")
|
| 135 |
-
if inputs['thalach'] < 120:
|
| 136 |
-
risk_factors += 1
|
| 137 |
-
risk_details.append(f"- Max Heart Rate: {inputs['thalach']} (Low)")
|
| 138 |
-
risk_params.append('thalach')
|
| 139 |
-
if inputs['exang'] == 'Yes':
|
| 140 |
-
risk_factors += 1
|
| 141 |
-
risk_details.append("- Exercise Induced Angina: Yes")
|
| 142 |
-
risk_params.append('exang')
|
| 143 |
-
if inputs['oldpeak'] >= 2.0:
|
| 144 |
-
risk_factors += 1
|
| 145 |
-
risk_details.append(f"- ST Depression: {inputs['oldpeak']} (High)")
|
| 146 |
-
risk_params.append('oldpeak')
|
| 147 |
-
elif inputs['oldpeak'] >= 1.0:
|
| 148 |
-
risk_factors += 0.5
|
| 149 |
-
risk_details.append(f"- ST Depression: {inputs['oldpeak']} (Moderate)")
|
| 150 |
-
if inputs['slope'] == 'Downsloping':
|
| 151 |
-
risk_factors += 1
|
| 152 |
-
risk_details.append("- ST Slope: Downsloping (Highest risk)")
|
| 153 |
-
risk_params.append('slope')
|
| 154 |
-
elif inputs['slope'] == 'Flat':
|
| 155 |
-
risk_factors += 0.5
|
| 156 |
-
risk_details.append("- ST Slope: Flat (Moderate risk)")
|
| 157 |
-
if inputs['ca'] == '3':
|
| 158 |
-
risk_factors += 1
|
| 159 |
-
risk_details.append("- Major Vessels: 3 (Highest risk)")
|
| 160 |
-
risk_params.append('ca')
|
| 161 |
-
elif inputs['ca'] in ['1', '2']:
|
| 162 |
-
risk_factors += 0.5
|
| 163 |
-
risk_details.append(f"- Major Vessels: {inputs['ca']} (Moderate risk)")
|
| 164 |
-
if inputs['thal'] == 'Fixed defect':
|
| 165 |
-
risk_factors += 1
|
| 166 |
-
risk_details.append("- Thalassemia: Fixed Defect")
|
| 167 |
-
risk_params.append('thal')
|
| 168 |
-
elif inputs['thal'] == 'Reversible defect':
|
| 169 |
-
risk_factors += 0.5
|
| 170 |
-
risk_details.append("- Thalassemia: Reversible Defect")
|
| 171 |
-
|
| 172 |
-
probability = min(risk_factors / total_possible, 0.99) # Cap at 99%
|
| 173 |
-
|
| 174 |
-
if probability <= RISK_THRESHOLDS['Low']:
|
| 175 |
-
risk_level = 'Low'
|
| 176 |
-
color = "green"
|
| 177 |
-
icon = "✅"
|
| 178 |
-
advice = "Maintain your healthy lifestyle with regular check-ups."
|
| 179 |
-
elif probability <= RISK_THRESHOLDS['Moderate']:
|
| 180 |
-
risk_level = 'Moderate'
|
| 181 |
-
color = "orange"
|
| 182 |
-
icon = "⚠️"
|
| 183 |
-
advice = "Consider lifestyle changes and consult your doctor."
|
| 184 |
-
else:
|
| 185 |
-
risk_level = 'High'
|
| 186 |
-
color = "red"
|
| 187 |
-
icon = "❗"
|
| 188 |
-
advice = "Please consult a cardiologist immediately."
|
| 189 |
-
|
| 190 |
-
return risk_level, probability, color, icon, advice, risk_details, risk_params
|
| 191 |
-
|
| 192 |
-
# App layout
|
| 193 |
-
st.title("❤️ Heart Disease Risk Assessment")
|
| 194 |
-
st.markdown("""
|
| 195 |
-
This tool evaluates your heart disease risk based on clinical parameters and provides personalized insights.
|
| 196 |
-
""")
|
| 197 |
-
|
| 198 |
-
# Create tabs
|
| 199 |
-
tab1, tab2, tab3 = st.tabs(["Risk Assessment", "Parameter Analysis", "Health Guidance"])
|
| 200 |
-
|
| 201 |
-
with tab1:
|
| 202 |
-
with st.form("heart_form"):
|
| 203 |
-
st.subheader("Enter Your Health Parameters")
|
| 204 |
-
col1, col2, col3 = st.columns(3)
|
| 205 |
-
inputs = {}
|
| 206 |
-
|
| 207 |
-
# Column 1
|
| 208 |
-
with col1:
|
| 209 |
-
inputs["age"] = st.number_input(
|
| 210 |
-
feature_info["age"]["label"],
|
| 211 |
-
min_value=feature_info["age"]["min"],
|
| 212 |
-
max_value=feature_info["age"]["max"],
|
| 213 |
-
value=st.session_state.get("age", feature_info["age"]["value"]),
|
| 214 |
-
help=f"{feature_info['age']['desc']}. Healthy range: {feature_info['age']['healthy_range']}"
|
| 215 |
-
)
|
| 216 |
-
inputs["sex"] = st.selectbox(
|
| 217 |
-
feature_info["sex"]["label"],
|
| 218 |
-
feature_info["sex"]["options"],
|
| 219 |
-
index=feature_info["sex"]["options"].index(st.session_state.get("sex", feature_info["sex"]["options"][0])),
|
| 220 |
-
help=feature_info["sex"]["desc"]
|
| 221 |
-
)
|
| 222 |
-
inputs["cp"] = st.selectbox(
|
| 223 |
-
feature_info["cp"]["label"],
|
| 224 |
-
feature_info["cp"]["options"],
|
| 225 |
-
index=feature_info["cp"]["options"].index(st.session_state.get("cp", feature_info["cp"]["options"][0])),
|
| 226 |
-
help=f"{feature_info['cp']['desc']}. Healthy: {feature_info['cp']['healthy']}"
|
| 227 |
-
)
|
| 228 |
-
inputs["trestbps"] = st.number_input(
|
| 229 |
-
feature_info["trestbps"]["label"],
|
| 230 |
-
min_value=feature_info["trestbps"]["min"],
|
| 231 |
-
max_value=feature_info["trestbps"]["max"],
|
| 232 |
-
value=st.session_state.get("trestbps", feature_info["trestbps"]["value"]),
|
| 233 |
-
help=f"{feature_info['trestbps']['desc']}. Healthy: {feature_info['trestbps']['healthy_range']}"
|
| 234 |
-
)
|
| 235 |
-
inputs["chol"] = st.number_input(
|
| 236 |
-
feature_info["chol"]["label"],
|
| 237 |
-
min_value=feature_info["chol"]["min"],
|
| 238 |
-
max_value=feature_info["chol"]["max"],
|
| 239 |
-
value=st.session_state.get("chol", feature_info["chol"]["value"]),
|
| 240 |
-
help=f"{feature_info['chol']['desc']}. Healthy: {feature_info['chol']['healthy_range']}"
|
| 241 |
-
)
|
| 242 |
-
|
| 243 |
-
# Column 2
|
| 244 |
-
with col2:
|
| 245 |
-
inputs["fbs"] = st.selectbox(
|
| 246 |
-
feature_info["fbs"]["label"],
|
| 247 |
-
feature_info["fbs"]["options"],
|
| 248 |
-
index=feature_info["fbs"]["options"].index(st.session_state.get("fbs", feature_info["fbs"]["options"][0])),
|
| 249 |
-
help=f"{feature_info['fbs']['desc']}. Healthy: {feature_info['fbs']['healthy']}"
|
| 250 |
-
)
|
| 251 |
-
inputs["restecg"] = st.selectbox(
|
| 252 |
-
feature_info["restecg"]["label"],
|
| 253 |
-
feature_info["restecg"]["options"],
|
| 254 |
-
index=feature_info["restecg"]["options"].index(st.session_state.get("restecg", feature_info["restecg"]["options"][0])),
|
| 255 |
-
help=f"{feature_info['restecg']['desc']}. Healthy: {feature_info['restecg']['healthy']}"
|
| 256 |
-
)
|
| 257 |
-
inputs["thalach"] = st.number_input(
|
| 258 |
-
feature_info["thalach"]["label"],
|
| 259 |
-
min_value=feature_info["thalach"]["min"],
|
| 260 |
-
max_value=feature_info["thalach"]["max"],
|
| 261 |
-
value=st.session_state.get("thalach", feature_info["thalach"]["value"]),
|
| 262 |
-
help=f"{feature_info['thalach']['desc']}. Healthy: {feature_info['thalach']['healthy_range']}"
|
| 263 |
-
)
|
| 264 |
-
inputs["exang"] = st.selectbox(
|
| 265 |
-
feature_info["exang"]["label"],
|
| 266 |
-
feature_info["exang"]["options"],
|
| 267 |
-
index=feature_info["exang"]["options"].index(st.session_state.get("exang", feature_info["exang"]["options"][0])),
|
| 268 |
-
help=f"{feature_info['exang']['desc']}. Healthy: {feature_info['exang']['healthy']}"
|
| 269 |
-
)
|
| 270 |
-
inputs["oldpeak"] = st.number_input(
|
| 271 |
-
feature_info["oldpeak"]["label"],
|
| 272 |
-
min_value=feature_info["oldpeak"]["min"],
|
| 273 |
-
max_value=feature_info["oldpeak"]["max"],
|
| 274 |
-
value=st.session_state.get("oldpeak", feature_info["oldpeak"]["value"]),
|
| 275 |
-
step=feature_info["oldpeak"]["step"],
|
| 276 |
-
help=f"{feature_info['oldpeak']['desc']}. Healthy: {feature_info['oldpeak']['healthy_range']}"
|
| 277 |
-
)
|
| 278 |
-
|
| 279 |
-
# Column 3
|
| 280 |
-
with col3:
|
| 281 |
-
inputs["slope"] = st.selectbox(
|
| 282 |
-
feature_info["slope"]["label"],
|
| 283 |
-
feature_info["slope"]["options"],
|
| 284 |
-
index=feature_info["slope"]["options"].index(st.session_state.get("slope", feature_info["slope"]["options"][0])),
|
| 285 |
-
help=f"{feature_info['slope']['desc']}. Healthy: {feature_info['slope']['healthy']}"
|
| 286 |
-
)
|
| 287 |
-
inputs["ca"] = st.selectbox(
|
| 288 |
-
feature_info["ca"]["label"],
|
| 289 |
-
feature_info["ca"]["options"],
|
| 290 |
-
index=feature_info["ca"]["options"].index(st.session_state.get("ca", feature_info["ca"]["options"][0])),
|
| 291 |
-
help=f"{feature_info['ca']['desc']}. Healthy: {feature_info['ca']['healthy']}"
|
| 292 |
-
)
|
| 293 |
-
inputs["thal"] = st.selectbox(
|
| 294 |
-
feature_info["thal"]["label"],
|
| 295 |
-
feature_info["thal"]["options"],
|
| 296 |
-
index=feature_info["thal"]["options"].index(st.session_state.get("thal", feature_info["thal"]["options"][0])),
|
| 297 |
-
help=f"{feature_info['thal']['desc']}. Healthy: {feature_info['thal']['healthy']}"
|
| 298 |
-
)
|
| 299 |
-
|
| 300 |
-
submitted = st.form_submit_button("Assess My Heart Disease Risk")
|
| 301 |
-
|
| 302 |
-
if submitted:
|
| 303 |
-
risk_level, prediction_proba, color, icon, advice, risk_details, risk_params = calculate_risk(inputs)
|
| 304 |
-
|
| 305 |
-
st.subheader("Risk Assessment Results")
|
| 306 |
-
cols = st.columns(3)
|
| 307 |
-
with cols[1]:
|
| 308 |
-
st.metric("Heart Disease Risk", f"{prediction_proba * 100:.1f}%", f"{risk_level} Risk", delta_color="off")
|
| 309 |
-
risk_meter = st.progress(prediction_proba)
|
| 310 |
-
st.markdown(f"""
|
| 311 |
-
<div style="background-color:#f0f2f6;padding:15px;border-radius:10px">
|
| 312 |
-
<h4 style="color:{color};text-align:center">{icon} {risk_level} Risk Category</h4>
|
| 313 |
-
<p style="text-align:center">{advice}</p>
|
| 314 |
-
</div>
|
| 315 |
-
""", unsafe_allow_html=True)
|
| 316 |
-
|
| 317 |
-
st.subheader("Risk Factor Analysis")
|
| 318 |
-
total_factors = 13
|
| 319 |
-
risk_factors = len(risk_details)
|
| 320 |
-
st.write(f"**{risk_factors} out of {total_factors}** parameters show elevated risk:")
|
| 321 |
-
if risk_details:
|
| 322 |
-
st.markdown("\n".join(risk_details))
|
| 323 |
-
else:
|
| 324 |
-
st.success("All parameters are within healthy ranges!")
|
| 325 |
-
|
| 326 |
-
st.session_state.results = {
|
| 327 |
-
"risk_level": risk_level,
|
| 328 |
-
"probability": prediction_proba,
|
| 329 |
-
"risk_factors": risk_details,
|
| 330 |
-
"risk_params": risk_params,
|
| 331 |
-
"inputs": inputs
|
| 332 |
-
}
|
| 333 |
-
|
| 334 |
-
with tab2:
|
| 335 |
-
st.header("Parameter Analysis")
|
| 336 |
-
if "results" not in st.session_state:
|
| 337 |
-
st.warning("Please complete the risk assessment first.")
|
| 338 |
-
else:
|
| 339 |
-
st.subheader("Health Parameter Radar Chart")
|
| 340 |
-
radar_params = ["age", "trestbps", "chol", "thalach", "oldpeak"]
|
| 341 |
-
radar_labels = [feature_info[p]["label"] for p in radar_params]
|
| 342 |
-
radar_values = [st.session_state.results["inputs"][p] for p in radar_params]
|
| 343 |
-
|
| 344 |
-
max_values = {
|
| 345 |
-
"age": 100,
|
| 346 |
-
"trestbps": 200,
|
| 347 |
-
"chol": 600,
|
| 348 |
-
"thalach": 220,
|
| 349 |
-
"oldpeak": 6.0
|
| 350 |
}
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
with st.spinner("Generating AI recommendations..."):
|
| 424 |
-
try:
|
| 425 |
-
# Prepare the prompt for Mistral API
|
| 426 |
-
prompt = f"""
|
| 427 |
-
Provide detailed, personalized heart health recommendations for a patient with the following profile:
|
| 428 |
-
- Age: {st.session_state.results["inputs"]["age"]}
|
| 429 |
-
- Sex: {st.session_state.results["inputs"]["sex"]}
|
| 430 |
-
- Risk Level: {risk_level}
|
| 431 |
-
- Key Risk Factors: {', '.join(st.session_state.results["risk_params"])}
|
| 432 |
-
|
| 433 |
-
Provide specific, actionable advice on:
|
| 434 |
-
1. Lifestyle modifications
|
| 435 |
-
2. Dietary recommendations
|
| 436 |
-
3. Exercise suggestions
|
| 437 |
-
4. Monitoring suggestions
|
| 438 |
-
5. When to seek medical attention
|
| 439 |
-
|
| 440 |
-
Format the response with clear headings and bullet points.
|
| 441 |
-
"""
|
| 442 |
-
|
| 443 |
-
# Call Mistral API
|
| 444 |
-
headers = {
|
| 445 |
-
"Authorization": f"Bearer {MISTRAL_API_KEY}",
|
| 446 |
-
"Content-Type": "application/json"
|
| 447 |
-
}
|
| 448 |
-
|
| 449 |
-
data = {
|
| 450 |
-
"model": "mistral-tiny",
|
| 451 |
-
"messages": [{"role": "user", "content": prompt}]
|
| 452 |
-
}
|
| 453 |
-
|
| 454 |
-
response = requests.post(
|
| 455 |
-
"https://api.mistral.ai/v1/chat/completions",
|
| 456 |
-
headers=headers,
|
| 457 |
-
json=data
|
| 458 |
-
)
|
| 459 |
-
|
| 460 |
-
if response.status_code == 200:
|
| 461 |
-
ai_response = response.json()["choices"][0]["message"]["content"]
|
| 462 |
-
st.markdown("### AI Health Advisor Recommendations")
|
| 463 |
-
st.write(ai_response)
|
| 464 |
-
else:
|
| 465 |
-
st.error(f"Failed to get AI recommendations. Status code: {response.status_code}")
|
| 466 |
-
|
| 467 |
-
except Exception as e:
|
| 468 |
-
st.error(f"An error occurred: {str(e)}")
|
| 469 |
-
|
| 470 |
-
# Sidebar with sample profiles
|
| 471 |
-
st.sidebar.header("Quick Start")
|
| 472 |
-
profile = st.sidebar.selectbox("Load sample profile:", list(SAMPLE_PROFILES.keys()))
|
| 473 |
-
if st.sidebar.button("Load Profile"):
|
| 474 |
-
for key in list(st.session_state.keys()):
|
| 475 |
-
if key in feature_info:
|
| 476 |
-
del st.session_state[key]
|
| 477 |
-
for key, value in SAMPLE_PROFILES[profile].items():
|
| 478 |
-
st.session_state[key] = value
|
| 479 |
-
st.rerun()
|
| 480 |
|
| 481 |
-
|
| 482 |
-
st.sidebar.markdown("""
|
| 483 |
-
### Important Disclaimer
|
| 484 |
-
This tool provides risk assessment only and is not a substitute for professional medical advice.
|
| 485 |
-
Always consult with a qualified healthcare provider for medical concerns.
|
| 486 |
-
""")
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
|
|
|
|
|
|
|
|
|
| 3 |
import requests
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
|
| 6 |
+
# Mistral API setup
|
| 7 |
+
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY", "your_api_key_here")
|
| 8 |
+
MISTRAL_API_URL = "https://api.mistral.ai/v1/chat/completions"
|
| 9 |
+
|
| 10 |
+
# Local medical knowledge base
|
| 11 |
+
MEDICAL_KNOWLEDGE = {
|
| 12 |
+
"headache": {
|
| 13 |
+
"assessment": "Could be tension-type, migraine, or other common headache",
|
| 14 |
+
"recommendations": [
|
| 15 |
+
"Rest in a quiet, dark room",
|
| 16 |
+
"Apply cold compress to forehead",
|
| 17 |
+
"Hydrate well (at least 8 glasses water/day)",
|
| 18 |
+
"Try gentle neck stretches",
|
| 19 |
+
"Consider OTC pain relievers (ibuprofen 200-400mg or paracetamol 500mg)"
|
| 20 |
+
],
|
| 21 |
+
"warnings": [
|
| 22 |
+
"If sudden/severe 'thunderclap' headache",
|
| 23 |
+
"If with fever, stiff neck, or confusion",
|
| 24 |
+
"If following head injury"
|
| 25 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
},
|
| 27 |
+
"fever": {
|
| 28 |
+
"assessment": "Common symptom of infection or inflammation",
|
| 29 |
+
"recommendations": [
|
| 30 |
+
"Rest and increase fluid intake",
|
| 31 |
+
"Use light clothing and keep room cool",
|
| 32 |
+
"Sponge with lukewarm water if uncomfortable",
|
| 33 |
+
"Paracetamol 500mg every 6 hours (max 4 doses/day)",
|
| 34 |
+
"Monitor temperature every 4 hours"
|
| 35 |
+
],
|
| 36 |
+
"warnings": [
|
| 37 |
+
"Fever >39°C lasting >3 days",
|
| 38 |
+
"If with rash, difficulty breathing, or seizures",
|
| 39 |
+
"In infants <3 months with any fever"
|
| 40 |
+
]
|
| 41 |
},
|
| 42 |
+
"sore throat": {
|
| 43 |
+
"assessment": "Often viral, but could be strep throat",
|
| 44 |
+
"recommendations": [
|
| 45 |
+
"Gargle with warm salt water 3-4 times daily",
|
| 46 |
+
"Drink warm liquids (tea with honey)",
|
| 47 |
+
"Use throat lozenges",
|
| 48 |
+
"Stay hydrated",
|
| 49 |
+
"Rest your voice"
|
| 50 |
+
],
|
| 51 |
+
"warnings": [
|
| 52 |
+
"Difficulty swallowing or breathing",
|
| 53 |
+
"White patches on tonsils",
|
| 54 |
+
"Fever >38.5°C with throat pain"
|
| 55 |
+
]
|
| 56 |
}
|
| 57 |
}
|
| 58 |
|
| 59 |
+
def get_local_advice(symptom):
|
| 60 |
+
"""Get structured medical advice from local knowledge base"""
|
| 61 |
+
symptom_lower = symptom.lower()
|
| 62 |
+
for key in MEDICAL_KNOWLEDGE:
|
| 63 |
+
if key in symptom_lower:
|
| 64 |
+
knowledge = MEDICAL_KNOWLEDGE[key]
|
| 65 |
+
response = "🔍 [Assessment]\n- " + knowledge["assessment"] + "\n\n"
|
| 66 |
+
response += "💡 [Self-Care Recommendations]\n" + "\n".join(["• " + item for item in knowledge["recommendations"]]) + "\n\n"
|
| 67 |
+
response += "⚠️ [When to Seek Medical Care]\n" + "\n".join(["• " + item for item in knowledge["warnings"]]) + "\n\n"
|
| 68 |
+
response += "📅 [Follow-up]\n• Re-evaluate in 2-3 days if not improving\n• See doctor if symptoms worsen or persist beyond 5 days"
|
| 69 |
+
return response
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
def query_mistral(prompt):
|
| 73 |
+
"""Try API first, fallback to local knowledge"""
|
| 74 |
+
try:
|
| 75 |
+
headers = {"Authorization": f"Bearer {MISTRAL_API_KEY}", "Content-Type": "application/json"}
|
| 76 |
+
payload = {
|
| 77 |
+
"model": "mistral-tiny",
|
| 78 |
+
"messages": [{"role": "user", "content": prompt}],
|
| 79 |
+
"temperature": 0.7,
|
| 80 |
+
"max_tokens": 1024
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
+
response = requests.post(MISTRAL_API_URL, headers=headers, json=payload, timeout=10)
|
| 83 |
+
response.raise_for_status()
|
| 84 |
+
return response.json()['choices'][0]['message']['content']
|
| 85 |
+
except:
|
| 86 |
+
return None
|
| 87 |
+
|
| 88 |
+
def generate_response(name, message):
|
| 89 |
+
"""Generate medical response with fallback"""
|
| 90 |
+
# First try local knowledge base
|
| 91 |
+
local_response = get_local_advice(message)
|
| 92 |
+
if local_response:
|
| 93 |
+
return f"Hello {name},\n\n{local_response}\n\nWishing you good health,\nDr. Alex"
|
| 94 |
+
|
| 95 |
+
# Then try API
|
| 96 |
+
prompt = f"""As Dr. Alex, provide structured advice for {name} who reports: "{message}"
|
| 97 |
+
|
| 98 |
+
Format response with these sections:
|
| 99 |
+
|
| 100 |
+
[Assessment] - Brief professional evaluation
|
| 101 |
+
[Self-Care Recommendations] - 3-5 specific actionable steps
|
| 102 |
+
[When to Seek Medical Care] - Clear warning signs
|
| 103 |
+
[Follow-up] - Monitoring advice
|
| 104 |
+
|
| 105 |
+
Use bullet points, professional yet compassionate tone."""
|
| 106 |
+
|
| 107 |
+
api_response = query_mistral(prompt)
|
| 108 |
+
if api_response:
|
| 109 |
+
return api_response
|
| 110 |
+
|
| 111 |
+
# Final fallback
|
| 112 |
+
common_conditions = "\n".join([f"- {cond}" for cond in MEDICAL_KNOWLEDGE.keys()])
|
| 113 |
+
return f"""Hello {name},
|
| 114 |
+
|
| 115 |
+
I'm currently unable to access detailed medical databases. For general advice:
|
| 116 |
+
|
| 117 |
+
Common conditions I can advise on:
|
| 118 |
+
{common_conditions}
|
| 119 |
+
|
| 120 |
+
For immediate concerns:
|
| 121 |
+
• Contact your local healthcare provider
|
| 122 |
+
• In emergencies, call your local emergency number
|
| 123 |
+
|
| 124 |
+
Please try asking about one of these common conditions or consult a healthcare professional.
|
| 125 |
+
|
| 126 |
+
Best regards,
|
| 127 |
+
Dr. Alex"""
|
| 128 |
+
|
| 129 |
+
# Gradio Interface
|
| 130 |
+
with gr.Blocks(title="Dr. Alex Medical Advisor", theme=gr.themes.Soft()) as demo:
|
| 131 |
+
gr.Markdown("# 🩺 Dr. Alex - General Health Advisor")
|
| 132 |
+
name = gr.Textbox(label="Your Name", placeholder="Enter your name")
|
| 133 |
+
|
| 134 |
+
with gr.Tab("Health Consultation"):
|
| 135 |
+
gr.Markdown("## Describe your health concern")
|
| 136 |
+
msg = gr.Textbox(label="Symptoms/Question", lines=3)
|
| 137 |
+
submit_btn = gr.Button("Get Medical Advice")
|
| 138 |
+
output = gr.Textbox(label="Dr. Alex's Advice", lines=15, interactive=False)
|
| 139 |
+
|
| 140 |
+
submit_btn.click(
|
| 141 |
+
fn=lambda name, msg: generate_response(name, msg),
|
| 142 |
+
inputs=[name, msg],
|
| 143 |
+
outputs=output
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
gr.Markdown("### Common Health Topics")
|
| 147 |
+
with gr.Row():
|
| 148 |
+
for condition in MEDICAL_KNOWLEDGE:
|
| 149 |
+
gr.Button(condition.capitalize()).click(
|
| 150 |
+
fn=lambda name, cond=condition: generate_response(name, cond),
|
| 151 |
+
inputs=[name],
|
| 152 |
+
outputs=output
|
| 153 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|