import pickle import numpy as np import sys import traceback from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional from fastapi.middleware.cors import CORSMiddleware import os # Global variables for model and scaler model = None scaler = None model_info = { "loaded": False, "demo_mode": False, "error": None, "features_count": 10 # Updated for your dataset } def load_model_with_fallback(): """Load model with multiple fallback methods""" global model, scaler, model_info print("=" * 50) print("Diabetes Prediction API - Model Loading") print("=" * 50) # Check if files exist for filename in ['diabetes_model.pkl', 'scaler.pkl']: if os.path.exists(filename): size = os.path.getsize(filename) print(f"✓ {filename}: {size} bytes") else: print(f"✗ {filename}: NOT FOUND") print("\n" + "=" * 50) # Method 1: Try joblib first try: import joblib print("Method 1: Trying joblib...") model = joblib.load('diabetes_model.pkl') scaler = joblib.load('scaler.pkl') # Test with dummy data matching YOUR 10 features test_input = np.array([[ 0, # BMI 0, # Glucose 0, # BloodPressure 0, # Insulin 0, # Diabetes (this might be target, not feature) 0, # Increased_Thirst (0=False, 1=True) 0, # Increased_Hunger (0=False, 1=True) 0, # Fatigue_Tiredness (0=False, 1=True) 0, # Blurred_Vision (0=False, 1=True) 0 # Unexplained_Weight_Loss (0=False, 1=True) ]], dtype=np.float32) if hasattr(scaler, 'transform'): test_scaled = scaler.transform(test_input) else: test_scaled = test_input if hasattr(model, 'predict'): test_pred = model.predict(test_scaled) print(f"✓ Test prediction: {test_pred[0]}") model_info.update({ "loaded": True, "demo_mode": False, "method": "joblib", "model_type": str(type(model)), "features": 10 # YOUR dataset has 10 features }) print("✓ SUCCESS: Model loaded with joblib") return True except Exception as e: print(f"✗ Joblib failed: {str(e)[:100]}...") # Method 2: Try standard pickle try: print("\nMethod 2: Trying standard pickle...") with open('diabetes_model.pkl', 'rb') as f: model = pickle.load(f) with open('scaler.pkl', 'rb') as f: scaler = pickle.load(f) model_info.update({ "loaded": True, "demo_mode": False, "method": "pickle", "model_type": str(type(model)), "features": 10 # YOUR dataset has 10 features }) print("✓ SUCCESS: Model loaded with standard pickle") return True except Exception as e: print(f"✗ Standard pickle failed: {str(e)[:100]}...") # Method 3: Create demo model for YOUR 10 features print("\nMethod 3: Creating demo model for 10 features...") try: from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler # Create dummy data matching YOUR 10 features np.random.seed(42) n_samples = 100 # Based on YOUR dataset features: X = np.column_stack([ np.random.normal(28, 6, n_samples), # BMI (18-40 typical) np.random.normal(120, 30, n_samples), # Glucose np.random.normal(80, 15, n_samples), # BloodPressure np.random.exponential(50, n_samples), # Insulin np.random.randint(0, 2, n_samples), # Diabetes (binary) np.random.randint(0, 2, n_samples), # Increased_Thirst (binary) np.random.randint(0, 2, n_samples), # Increased_Hunger (binary) np.random.randint(0, 2, n_samples), # Fatigue_Tiredness (binary) np.random.randint(0, 2, n_samples), # Blurred_Vision (binary) np.random.randint(0, 2, n_samples), # Unexplained_Weight_Loss (binary) ]) # Create labels based on logical rules (higher glucose + symptoms = diabetic) y = ( (X[:, 1] > 140).astype(int) + # High glucose (X[:, 0] > 30).astype(int) + # High BMI (X[:, 4] == 1).astype(int) + # Existing diabetes (X[:, 5] == 1).astype(int) + # Increased thirst (X[:, 9] == 1).astype(int) # Weight loss ) >= 3 # At least 3 risk factors # Create and fit scaler scaler = StandardScaler() scaler.fit(X) # Create and fit model model = LogisticRegression() model.fit(scaler.transform(X), y) model_info.update({ "loaded": True, "demo_mode": True, "method": "demo", "model_type": "Gussian Naive Bayes", "features": 10, "accuracy": model.score(scaler.transform(X), y) }) print("✓ SUCCESS: Created model for 10 features") print(f" Demo model accuracy: {model_info['accuracy']:.2%}") return True except Exception as e: print(f"✗ Demo model failed: {str(e)[:100]}...") model_info["error"] = str(e) return False @asynccontextmanager async def lifespan(app: FastAPI): # Startup print("\n" + "=" * 50) print("Starting Diabetes Prediction API") print("=" * 50) print("Dataset: Custom 10-feature diabetes dataset") print("Features: BMI, Glucose, BloodPressure, Insulin, Diabetes,") print(" Increased_Thirst, Increased_Hunger, Fatigue_Tiredness,") print(" Blurred_Vision, Unexplained_Weight_Loss") print("=" * 50) # Load model success = load_model_with_fallback() if success: print(f"\n API Ready!") print(f" Model: {model_info['model_type']}") print(f" Mode: {'DEMO' if model_info['demo_mode'] else 'PRODUCTION'}") print(f" Features: {model_info.get('features', 'Unknown')}") if model_info.get('accuracy'): print(f" Accuracy: {model_info['accuracy']:.2%}") else: print(f"\n WARNING: Running in emergency mode") print(" Basic predictions based on glucose levels and symptoms") print("=" * 50 + "\n") yield # Shutdown print("\nShutting down Diabetes Prediction API...") app = FastAPI( title="Diabetes Prediction API", description="API for diabetes prediction using 10-feature dataset", version="2.0.0", lifespan=lifespan ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Pydantic model for YOUR 10 features class DiabetesFeatures(BaseModel): # Based on YOUR dataset bmi: float glucose: float blood_pressure: float insulin: float diabetes: int # 0 or 1 (existing diabetes diagnosis) increased_thirst: int # 0 or 1 increased_hunger: int # 0 or 1 fatigue_tiredness: int # 0 or 1 blurred_vision: int # 0 or 1 unexplained_weight_loss: int # 0 or 1 # FIXED: Changed PredictionResponse to return "Yes"/"No" instead of risk levels class PredictionResponse(BaseModel): success: bool prediction: int prediction_label: str # "Yes" or "No" probability: float confidence: str # High, Medium, Low confidence message: str demo_mode: bool = False features_used: Optional[List[float]] = None @app.get("/") async def home(): return { "app": "Diabetes Prediction API", "version": "2.0.0", "status": "active", "dataset": "Custom 10-feature diabetes dataset", "model": { "loaded": model_info["loaded"], "demo_mode": model_info["demo_mode"], "type": model_info.get("model_type", "Unknown"), "features": model_info.get("features", 10) }, "endpoints": { "GET /": "API information", "GET /health": "Health check", "GET /features": "Input features info", "GET /model-info": "Model details", "POST /predict": "Make prediction" } } @app.get("/health") async def health_check(): return { "status": "healthy" if model_info["loaded"] else "degraded", "model_loaded": model_info["loaded"], "demo_mode": model_info["demo_mode"], "features": model_info.get("features", 10), "timestamp": np.datetime64('now').astype(str) } @app.get("/features") async def get_features(): return { "features": [ { "name": "bmi", "description": "Body Mass Index", "type": "float", "range": "18-40", "important": "High", "normal": "18.5-24.9" }, { "name": "glucose", "description": "Blood Glucose Level", "type": "float", "range": "70-200 mg/dL", "important": "High", "normal": "70-100 mg/dL" }, { "name": "blood_pressure", "description": "Blood Pressure", "type": "float", "range": "60-140 mmHg", "important": "Medium", "normal": "<120/80 mmHg" }, { "name": "insulin", "description": "Insulin Level", "type": "float", "range": "0-300 mu U/ml", "important": "Medium" }, { "name": "diabetes", "description": "Existing Diabetes Diagnosis", "type": "int", "range": "0 or 1", "important": "High", "note": "0=No, 1=Yes" }, { "name": "increased_thirst", "description": "Increased Thirst Symptom", "type": "int", "range": "0 or 1", "important": "Medium", "note": "0=No, 1=Yes" }, { "name": "increased_hunger", "description": "Increased Hunger Symptom", "type": "int", "range": "0 or 1", "important": "Medium", "note": "0=No, 1=Yes" }, { "name": "fatigue_tiredness", "description": "Fatigue/Tiredness Symptom", "type": "int", "range": "0 or 1", "important": "Medium", "note": "0=No, 1=Yes" }, { "name": "blurred_vision", "description": "Blurred Vision Symptom", "type": "int", "range": "0 or 1", "important": "Medium", "note": "0=No, 1=Yes" }, { "name": "unexplained_weight_loss", "description": "Unexplained Weight Loss Symptom", "type": "int", "range": "0 or 1", "important": "High", "note": "0=No, 1=Yes" } ], "total_features": 10, "target": "Diabetes prediction (1=Yes, 0=No)" } @app.get("/model-info") async def get_model_info(): return model_info @app.post("/predict", response_model=PredictionResponse) async def predict(features: DiabetesFeatures): """Main prediction endpoint for YOUR 10-feature dataset""" # Validate input ranges validation_errors = [] if features.bmi < 10 or features.bmi > 50: validation_errors.append("BMI should be between 10-50") if features.glucose < 50 or features.glucose > 300: validation_errors.append("Glucose should be between 50-300 mg/dL") if features.blood_pressure < 40 or features.blood_pressure > 200: validation_errors.append("Blood pressure should be between 40-200 mmHg") if features.insulin < 0 or features.insulin > 500: validation_errors.append("Insulin should be between 0-500 mu U/ml") # Validate binary features binary_features = [ ('diabetes', features.diabetes), ('increased_thirst', features.increased_thirst), ('increased_hunger', features.increased_hunger), ('fatigue_tiredness', features.fatigue_tiredness), ('blurred_vision', features.blurred_vision), ('unexplained_weight_loss', features.unexplained_weight_loss), ] for name, value in binary_features: if value not in [0, 1]: validation_errors.append(f"{name} should be 0 or 1") if validation_errors: raise HTTPException(status_code=400, detail={"errors": validation_errors}) try: # Prepare input data in the correct order input_features = [ features.bmi, features.glucose, features.blood_pressure, features.insulin, float(features.diabetes), float(features.increased_thirst), float(features.increased_hunger), float(features.fatigue_tiredness), float(features.blurred_vision), float(features.unexplained_weight_loss) ] input_data = np.array([input_features], dtype=np.float32) # Scale data if scaler exists if scaler and hasattr(scaler, 'transform'): scaled_data = scaler.transform(input_data) else: scaled_data = input_data # Make prediction prediction = 0 probability = 0.5 if model and hasattr(model, 'predict'): prediction = int(model.predict(scaled_data)[0]) # Get probability if hasattr(model, 'predict_proba'): probability = float(model.predict_proba(scaled_data)[0][1]) elif hasattr(model, 'decision_function'): try: score = model.decision_function(scaled_data)[0] probability = 1 / (1 + np.exp(-score)) except: probability = 0.5 if prediction == 1 else 0.5 else: probability = 0.7 if prediction == 1 else 0.3 else: # Emergency fallback: rule-based prediction risk_score = 0 # Calculate risk based on features if features.glucose > 140: risk_score += 2 if features.bmi > 30: risk_score += 1 if features.diabetes == 1: risk_score += 3 if features.increased_thirst == 1: risk_score += 1 if features.unexplained_weight_loss == 1: risk_score += 2 if features.blurred_vision == 1: risk_score += 1 if features.fatigue_tiredness == 1: risk_score += 1 prediction = 1 if risk_score >= 4 else 0 probability = min(0.95, risk_score / 10) # FIXED: Determine prediction label and confidence (not risk level) prediction_label = "Yes" if prediction == 1 else "No" # Determine confidence level based on probability if probability >= 0.8 or probability <= 0.2: confidence = "High" elif probability >= 0.7 or probability <= 0.3: confidence = "Medium" else: confidence = "Low" # FIXED: Simple message based on prediction if prediction == 1: message = "Diabetes detected" else: message = "No diabetes detected" return PredictionResponse( success=True, prediction=prediction, prediction_label=prediction_label, probability=probability, confidence=confidence, message=message, demo_mode=model_info["demo_mode"], features_used=input_features ) except Exception as e: print(f"Prediction error: {e}") traceback.print_exc() raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}") @app.post("/quick-predict") async def quick_predict(data: dict): """Simplified endpoint with flexible input""" try: # Extract features with flexible naming feature_map = { 'bmi': ['bmi', 'BMI', 'body_mass_index'], 'glucose': ['glucose', 'Glucose', 'blood_sugar'], 'blood_pressure': ['blood_pressure', 'BloodPressure', 'bp', 'BP'], 'insulin': ['insulin', 'Insulin'], 'diabetes': ['diabetes', 'Diabetes', 'existing_diabetes'], 'increased_thirst': ['increased_thirst', 'Increased_Thirst', 'thirst'], 'increased_hunger': ['increased_hunger', 'Increased_Hunger', 'hunger'], 'fatigue_tiredness': ['fatigue_tiredness', 'Fatigue_Tiredness', 'fatigue', 'tiredness'], 'blurred_vision': ['blurred_vision', 'Blurred_Vision', 'vision'], 'unexplained_weight_loss': ['unexplained_weight_loss', 'Unexplained_Weight_Loss', 'weight_loss'] } extracted_features = {} for target_name, possible_keys in feature_map.items(): value = None for key in possible_keys: if key in data: value = data[key] break if value is None: # Set default values if target_name in ['bmi', 'glucose', 'blood_pressure', 'insulin']: value = 0.0 else: value = 0 # binary features default to 0 extracted_features[target_name] = value # Create features object features = DiabetesFeatures(**extracted_features) # Call the main predict function from fastapi.encoders import jsonable_encoder result = await predict(features) return jsonable_encoder(result) except Exception as e: return { "success": False, "error": str(e), "demo_mode": model_info["demo_mode"] } if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)