Banking-Sales-Predictor / api /model /model_service.py
nbintang
fix: remove models info from spaces
371053c
Raw
History Blame Contribute Delete
4.64 kB
import asyncio
import json
import time
from concurrent.futures import ThreadPoolExecutor
from typing import Any, Dict, Optional, Tuple
from huggingface_hub import hf_hub_download
import joblib
import pandas as pd
class ModelService:
def __init__(self, model_repo: str, model_file: str, feature_file: str, metadata_file: str):
self.model_repo = model_repo
self.model_file = model_file
self.feature_file = feature_file
self.metadata_file = metadata_file
self.pipeline = None
self.expected_features = None
self.load_time = None
self.model_metadata: Optional[Dict[str, Any]] = None
self.executor = ThreadPoolExecutor(max_workers=4)
self._load_model()
self._load_model_metadata()
def _download_from_hf(self, filename: str) -> str:
path = hf_hub_download(repo_id=self.model_repo, filename=filename, cache_dir="models")
return path
def _load_model(self):
try:
start_time = time.time()
model_path = self._download_from_hf(self.model_file)
feature_path = self._download_from_hf(self.feature_file)
self.pipeline = joblib.load(model_path)
self.expected_features = joblib.load(feature_path)
self.load_time = round(time.time() - start_time, 3)
print("Model Loaded Successfully!")
except Exception as e:
self.pipeline = None
self.load_time = None
print(f"❌ Model failed to load: {e}")
def _load_model_metadata(self):
try:
metadata_path = self._download_from_hf(self.metadata_file)
with open(metadata_path, "r") as f:
self.model_metadata = json.load(f)
print("Metadata loaded!")
except Exception as e:
print("❌ Failed to load metadata:", e)
self.model_metadata = None
def get_model_info(self) -> Dict[str, Any]:
# Safety check
if self.model_metadata is None:
self._load_model_metadata()
return {
"path": self.model_path,
"load_time_sec": self.load_time,
"type": self.model_metadata["model_info"]["model_type"],
"pipeline": self.model_metadata["model_info"]["model_name"],
}
def is_model_loaded(self) -> bool:
return self.pipeline is not None
async def preprocess_data(self, data_dict: Dict[str, Any]) -> pd.DataFrame:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
self.executor, self._preprocess_sync, data_dict
)
def _preprocess_sync(self, data_dict: Dict[str, Any]) -> pd.DataFrame:
df = pd.DataFrame([data_dict])
# Reindex ke expected_features
if self.expected_features:
missing_model_features = [
c for c in self.expected_features if c not in df.columns
]
if missing_model_features:
raise ValueError(f"Missing model features: {missing_model_features}")
df = df.reindex(columns=self.expected_features, fill_value=0)
return df
async def predict(
self, df: pd.DataFrame
) -> Tuple[int, Optional[float], Optional[float]]:
if not self.is_model_loaded():
raise RuntimeError("Model not loaded")
loop = asyncio.get_event_loop()
return await loop.run_in_executor(self.executor, self._predict_sync, df)
def _predict_sync(
self, df: pd.DataFrame
) -> Tuple[int, Optional[float], Optional[float], Optional[str]]:
# Prediksi label
pred = self.pipeline.predict(df)[0]
# Prediksi probabilitas
if hasattr(self.pipeline, "predict_proba"):
proba = self.pipeline.predict_proba(df)[0]
proba_no, proba_yes = float(proba[0]), float(proba[1])
else:
proba_yes = None
proba_no = None
# Tentukan priority/confidence berdasarkan probabilitas yes
if proba_yes is not None:
if proba_yes >= 0.7:
priority = "HIGH"
elif proba_yes >= 0.5:
priority = "MEDIUM"
elif proba_yes >= 0.3:
priority = "LOW"
else:
priority = "VERY LOW"
else:
priority = None
return int(pred), proba_yes, proba_no, priority
def get_complete_model_info(self) -> (Dict[str, Any] | None):
if self.model_metadata is None:
self._load_model_metadata()
return self.model_metadata