3v324v23 commited on
Commit
48c8b68
·
1 Parent(s): 3b7d047

feat(api): Implement FastAPI endpoints and ML service

Browse files
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ libpq-dev \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Install python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy application code
16
+ COPY . .
17
+
18
+ # Expose port
19
+ EXPOSE 7860
20
+
21
+ # Command to run the application
22
+ # Using 7860 for Hugging Face Spaces compatibility
23
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/main.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.routers import prediction
3
+ from app.core.database import engine, Base
4
+
5
+ # Create tables on startup (for simplicity in this POC, though usually done via migration scripts)
6
+ # Base.metadata.create_all(bind=engine)
7
+ # We will use a separate script for DB creation as requested.
8
+
9
+ app = FastAPI(
10
+ title="ML Prediction API",
11
+ description="API for XGBoost Model Predictions",
12
+ version="1.0.0"
13
+ )
14
+
15
+ app.include_router(prediction.router)
16
+
17
+ @app.get("/")
18
+ def root():
19
+ return {"message": "Welcome to the ML Prediction API. Visit /docs for documentation."}
app/routers/prediction.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Header, status
2
+ from sqlalchemy.orm import Session
3
+ from app.core.database import get_db
4
+ from app.models.schemas import InputSchema, PredictionOutput
5
+ from app.models.models import PredictionLog
6
+ from app.services.ml_service import ml_service
7
+ from app.core.config import settings
8
+
9
+ router = APIRouter()
10
+
11
+ def verify_api_key(x_api_key: str = Header(...)):
12
+ if x_api_key != settings.API_KEY:
13
+ raise HTTPException(
14
+ status_code=status.HTTP_401_UNAUTHORIZED,
15
+ detail="Invalid API Key",
16
+ )
17
+ return x_api_key
18
+
19
+ @router.post("/predict", response_model=PredictionOutput)
20
+ def predict(input_data: InputSchema, db: Session = Depends(get_db), api_key: str = Depends(verify_api_key)):
21
+ # Convert Pydantic model to dict
22
+ data_dict = input_data.dict()
23
+
24
+ try:
25
+ # Make prediction
26
+ prediction, probability = ml_service.predict(data_dict)
27
+
28
+ # Log to Database
29
+ db_log = PredictionLog(
30
+ **data_dict,
31
+ prediction=prediction,
32
+ probability=probability
33
+ )
34
+ db.add(db_log)
35
+ db.commit()
36
+ db.refresh(db_log)
37
+
38
+ return PredictionOutput(prediction=prediction, probability=probability)
39
+
40
+ except Exception as e:
41
+ print(f"Error during prediction: {e}")
42
+ raise HTTPException(status_code=500, detail=str(e))
43
+
44
+ @router.get("/health")
45
+ def health_check():
46
+ return {"status": "healthy"}
47
+
48
+ @router.get("/model/info")
49
+ def model_info(api_key: str = Depends(verify_api_key)):
50
+ model = ml_service.model
51
+ if not model:
52
+ return {"status": "Model not loaded"}
53
+ return {
54
+ "type": str(type(model)),
55
+ "params": model.get_params() if hasattr(model, "get_params") else "Unknown"
56
+ }
app/services/ml_service.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ import pandas as pd
3
+ import os
4
+ from app.core.config import settings
5
+
6
+ class MLService:
7
+ def __init__(self):
8
+ self.model = None
9
+ self.expected_features = None
10
+ self.load_model()
11
+
12
+ def load_model(self):
13
+ model_path = settings.MODEL_PATH
14
+ if not os.path.exists(model_path):
15
+ print(f"Warning: Model file not found at {model_path}")
16
+ return
17
+
18
+ print(f"Loading model from {model_path}...")
19
+ with open(model_path, 'rb') as f:
20
+ self.model = pickle.load(f)
21
+
22
+ if hasattr(self.model, "feature_names_in_"):
23
+ self.expected_features = self.model.feature_names_in_
24
+ print(f"Model expects {len(self.expected_features)} features.")
25
+ else:
26
+ print("Warning: Model does not have feature_names_in_. Preprocessing might fail.")
27
+
28
+ print("Model loaded successfully.")
29
+
30
+ def predict(self, input_data: dict):
31
+ if not self.model:
32
+ raise RuntimeError("Model is not loaded")
33
+
34
+ # Convert input dict to DataFrame
35
+ df = pd.DataFrame([input_data])
36
+
37
+ # Preprocessing: One-Hot Encoding
38
+ # We use pd.get_dummies to encode categorical variables
39
+ # Then we align with expected features
40
+
41
+ df_encoded = pd.get_dummies(df)
42
+
43
+ if self.expected_features is not None:
44
+ # Add missing columns with 0
45
+ # Remove extra columns (if any, though unlikely with single row input unless new category appears)
46
+ # Reorder columns to match model expectation
47
+
48
+ # This reindex handles both adding missing cols (filling with 0) and reordering
49
+ df_final = df_encoded.reindex(columns=self.expected_features, fill_value=0)
50
+ else:
51
+ df_final = df_encoded
52
+
53
+ # Predict
54
+ prediction = self.model.predict(df_final)[0]
55
+
56
+ # Try to get probability if available
57
+ probability = None
58
+ if hasattr(self.model, "predict_proba"):
59
+ try:
60
+ probs = self.model.predict_proba(df_final)
61
+ probability = float(probs[0][1]) # Assuming binary classification
62
+ except Exception as e:
63
+ print(f"Could not get probability: {e}")
64
+
65
+ return int(prediction), probability
66
+
67
+ ml_service = MLService()