fund-flow-backend / src /ml /predictor.py
Aniket2006's picture
refactor: implement network community overview sampling, fix ruff unused import, and untrack ruff cache
77518e8
Raw
History Blame Contribute Delete
2.88 kB
"""
Predictor Module - Fixed
- Batch scoring
- Unknown account warning flag
- Model version hash check
- No Streamlit
"""
import os
import hashlib
import joblib
import xgboost as xgb
import pandas as pd
from src.config_loader import get_config
_bundle_cache: dict = None
_model_hash: str = None
def _compute_model_hash(path: str) -> str:
h = hashlib.md5()
with open(path, 'rb') as f:
h.update(f.read(65536))
return h.hexdigest()
def load_model() -> dict:
global _bundle_cache, _model_hash
cfg = get_config()['ml']
model_path = cfg['model_path']
scaler_path = cfg['scaler_path']
if not os.path.exists(model_path):
return None
current_hash = _compute_model_hash(model_path)
if _bundle_cache is not None and _model_hash == current_hash:
return _bundle_cache
model = xgb.XGBClassifier()
model._estimator_type = 'classifier'
model.load_model(model_path)
meta = joblib.load(scaler_path) if os.path.exists(scaler_path) else {}
feature_cols = meta.get('feature_cols', cfg['feature_cols'])
_bundle_cache = {
'model': model,
'feature_cols': feature_cols,
'model_hash': current_hash,
}
_model_hash = current_hash
return _bundle_cache
def score_account(account_id: str, feature_df: pd.DataFrame, bundle: dict) -> dict:
row = feature_df[feature_df['account'] == account_id]
if row.empty:
return {'risk_score': None, 'fraud_probability': None, 'unscored': True}
cols = [c for c in bundle['feature_cols'] if c in row.columns]
row = row.copy()
row[cols] = row[cols].apply(pd.to_numeric, errors='coerce').fillna(0.0)
proba = float(bundle['model'].predict_proba(row[cols])[0][1])
risk_score = int(proba * 99)
return {'risk_score': risk_score, 'fraud_probability': proba, 'unscored': False}
def score_accounts_batch(account_ids: list, feature_df: pd.DataFrame, bundle: dict) -> dict:
"""Vectorised batch scoring — fully numpy, no Python loops."""
rows = feature_df[feature_df['account'].isin(account_ids)]
if rows.empty:
return {aid: {'risk_score': None, 'fraud_probability': None, 'unscored': True}
for aid in account_ids}
cols = [c for c in bundle['feature_cols'] if c in rows.columns]
rows = rows.copy()
rows[cols] = rows[cols].apply(pd.to_numeric, errors='coerce').fillna(0.0)
probas = bundle['model'].predict_proba(rows[cols])[:, 1]
accounts = rows['account'].values
risk_scores = (probas * 99).astype(int)
result = {
acct: {'risk_score': int(rs), 'fraud_probability': float(p), 'unscored': False}
for acct, rs, p in zip(accounts, risk_scores, probas)
}
for aid in account_ids:
if aid not in result:
result[aid] = {'risk_score': None, 'fraud_probability': None, 'unscored': True}
return result