Project_AML / app.py
Alizain78688's picture
Upload app.py
e0b867a verified
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)