Spaces:
Sleeping
Sleeping
| 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 | |
| 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 | |
| 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" | |
| } | |
| } | |
| 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) | |
| } | |
| 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)" | |
| } | |
| async def get_model_info(): | |
| return model_info | |
| 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)}") | |
| 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) |