# app.py — Heart‑Disease Predictor + CardioConsult‑Bot (Gemini 1.5 Flash) # ---------------------------------------------------------------------------------- import os, streamlit as st, pandas as pd, joblib from sklearn.impute import SimpleImputer from google import genai from google.genai import types from google.api_core.exceptions import GoogleAPIError # ---------------------------------------------------------------------------------- # 1. Gemini client (google‑genai) # ---------------------------------------------------------------------------------- API_KEY = "AIzaSyBjyI-6GuU9bWAHBgO3_GbCTuBRr2SBczo" if not API_KEY: st.stop("❌ GEMINI_API_KEY not found — set env var or .streamlit/secrets.toml") client = genai.Client(api_key=API_KEY) MODEL = "gemini-1.5-flash-8b" # change to gemini-pro if you prefer # ---------------------------------------------------------------------------------- # 2. Load ML model & scaler # ---------------------------------------------------------------------------------- best_model = joblib.load("best_model.pkl") scaler = joblib.load("scaler.pkl") # ---------------------------------------------------------------------------------- # 3. Lookup tables # ---------------------------------------------------------------------------------- sex_map = {"Male": 1, "Female": 0} cp_map = {"atypical angina": 0, "asymptomatic": 1, "non-anginal": 2, "typical angina": 3} fbs_map = {"TRUE": 1, "FALSE": 0} rest_map = {"left ventricular hypertrophy": 0, "normal": 1, "ST-T wave abnormality": 2} exang_map = {"TRUE": 1, "FALSE": 0} slope_map = {"downsloping": 0, "flat": 1, "upsloping": 2} thal_map = {"fixed defect": 0, "normal": 1, "reversable defect": 2} pred_map = {0:"No Heart Disease",1:"Mild Heart Disease", 2:"Moderate Heart Disease",3:"Severe Heart Disease",4:"Critical Heart Disease"} # ---------------------------------------------------------------------------------- # 4. Streamlit UI # ---------------------------------------------------------------------------------- st.set_page_config(page_title="Heart‑Disease Predictor + CardioConsult‑Bot", page_icon="🫀") st.title("🫀 Heart‑Disease Risk Predictor  +  CardioConsult‑Bot") st.caption("Machine‑learning screening + live cardiovascular chat • *Educational only — not medical advice*") # ---------------------- Prediction form ------------------------------------------ with st.form("prediction"): age = st.number_input("Age", 1, 120, 50) trestbps = st.number_input("Resting Blood Pressure (mmHg)", 50, 250, 130) chol = st.number_input("Serum Cholesterol (mg/dL)", 100, 600, 200) thalch = st.number_input("Max Heart Rate Achieved (bpm)", 50, 250, 150) oldpeak = st.number_input("ST Depression", 0.0, 10.0, 1.0, format="%.1f") ca = st.number_input("Number of Major Vessels (0–4)", 0, 4, 0) sex = st.selectbox("Sex", list(sex_map)) cp = st.selectbox("Chest‑Pain Type", list(cp_map)) fbs = st.selectbox("Fasting Blood Sugar >120 mg/dL", list(fbs_map)) restecg = st.selectbox("Resting ECG", list(rest_map)) exang = st.selectbox("Exercise‑Induced Angina", list(exang_map)) slope = st.selectbox("Slope of Peak Exercise ST", list(slope_map)) thal = st.selectbox("Thalassemia", list(thal_map)) submitted = st.form_submit_button("Predict") # ---------------------- Make prediction ------------------------------------------ if submitted: X = pd.DataFrame({ "age":[age],"sex":[sex_map[sex]],"cp":[cp_map[cp]],"trestbps":[trestbps], "chol":[chol],"fbs":[fbs_map[fbs]],"restecg":[rest_map[restecg]], "thalch":[thalch],"exang":[exang_map[exang]],"oldpeak":[oldpeak], "slope":[slope_map[slope]],"ca":[ca],"thal":[thal_map[thal]] }) if X.shape[1] != scaler.mean_.shape[0]: X = X.drop(columns=["thal"]) # scaler trained on 13 features X_scaled = scaler.transform(SimpleImputer(strategy="median").fit_transform(X)) y_pred = int(best_model.predict(X_scaled)[0]) pred_txt = pred_map.get(y_pred, "Unknown") st.subheader("🔎 Prediction Result") st.success(f"**Predicted Heart‑Disease Level:** {pred_txt}") st.session_state.prediction_ctx = dict(X.iloc[0], model_prediction=pred_txt) # ---------------------------------------------------------------------------------- # 5. Chat memory initialisation # ---------------------------------------------------------------------------------- if "messages" not in st.session_state: st.session_state.messages = [ ("assistant", "👋 Hi — I'm **CardioConsult‑Bot**. Ask me about your heart‑disease screening result or cardiovascular health. " "_I am not a substitute for a doctor; for urgent or non‑cardiac issues, seek professional care._") ] # Display last messages for role, msg in st.session_state.messages[-8:]: st.chat_message(role).markdown(msg) # ---------------------------------------------------------------------------------- # 6. Chat input -> Gemini stream # ---------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------- # 6. Chat input -> Gemini (show full response once) # ---------------------------------------------------------------------------------- if prompt := st.chat_input("Ask about your cardiovascular health…"): # 1️⃣ Save user message and show chat history st.session_state.messages.append(("user", prompt)) # Show previous messages (chat history) if "messages" in st.session_state: for message in st.session_state.messages: if message[0] == "user": st.chat_message("user").markdown(message[1]) else: st.chat_message("assistant").markdown(message[1]) # 2️⃣ Build single user prompt (instructions + context + question) instructions = ( "You are CardioConsult‑Bot, an educational assistant who discusses cardiovascular " "screening results, risk factors, and heart‑healthy lifestyle. " "Do **not** prescribe medications or diagnose conditions outside cardiology. " "Urge professional medical advice for emergencies.\n\n" ) context_txt = "" if "prediction_ctx" in st.session_state: ctx = st.session_state.prediction_ctx context_txt = "\n".join(f"{k}: {v}" for k, v in ctx.items()) + "\n\n" req_content = [ types.Content( role="user", parts=[types.Part(text=instructions + context_txt + prompt)] ) ] # Debugging output for `req_content` print("Request content:", req_content) gen_cfg = types.GenerateContentConfig( thinking_config=types.ThinkingConfig(thinking_budget=0), response_mime_type="text/plain", ) # 3️⃣ Call Gemini and buffer chunks full_reply = "" try: with st.spinner("Consulting Gemini…"): for chunk in client.models.generate_content_stream( model=MODEL, contents=req_content, config=gen_cfg ): if chunk.text: full_reply += chunk.text except (GoogleAPIError, Exception) as e: full_reply = f"⚠️ Service error: {getattr(e,'message',e)}" # 4️⃣ Display the complete answer exactly once st.chat_message("assistant").markdown(full_reply) st.session_state.messages.append(("assistant", full_reply))