Spaces:
Runtime error
Runtime error
refactor: implement network community overview sampling, fix ruff unused import, and untrack ruff cache
77518e8 | """ | |
| 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 | |