Spaces:
Sleeping
Sleeping
File size: 5,883 Bytes
d98611c ea2b6ec d98611c ea2b6ec d98611c ea2b6ec d98611c ea2b6ec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | """
FastAPI application for Job Failure Prediction and Anomaly Detection.
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import Optional, Dict, List, Any
import pandas as pd
from datetime import datetime
import os
from preprocessing import engineer_features, align_schema_df
from model_utils import JobFailurePredictor, AnomalyDetector
app = FastAPI(
title="Job Failure Prediction & Anomaly Detection API",
description="ML service for predicting job failures and detecting anomalies",
version="1.0.0"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize models (lazy loading)
_predictor = None
_anomaly_detector = None
def get_predictor():
"""Lazy load predictor."""
global _predictor
if _predictor is None:
_predictor = JobFailurePredictor()
return _predictor
def get_anomaly_detector():
"""Lazy load anomaly detector."""
global _anomaly_detector
if _anomaly_detector is None:
_anomaly_detector = AnomalyDetector()
return _anomaly_detector
# Request/Response models
class JobFailureRequest(BaseModel):
"""Request model for job failure prediction."""
zone: Optional[str] = None
job_nm: Optional[str] = None
tasksgroup_nm: Optional[str] = None
round_time: Optional[str] = None
job_start_time: Optional[str] = None
job_end_time: Optional[str] = None
duration: Optional[str] = None
duration_sec: Optional[float] = None
status: Optional[str] = None
err_msg: Optional[str] = None
zeppelin: Optional[str] = None
ictrl_dt: Optional[str] = None
start_ictrl_dt: Optional[str] = None
end_ictrl_dt: Optional[str] = None
# Optional pre-computed features
run_hour: Optional[int] = None
run_dayofweek: Optional[int] = None
explain: bool = Field(default=False, description="Include SHAP explanations")
class JobFailureResponse(BaseModel):
"""Response model for job failure prediction."""
fail_probability: float
risk_level: str
top_drivers: Optional[List[Dict[str, Any]]] = None
recommended_actions: Optional[List[str]] = None
error: Optional[str] = None
class AnomalyRequest(BaseModel):
"""Request model for anomaly detection."""
features: Dict[str, float] = Field(
description="Dictionary of feature values (duration_sec, err_msg_len, etc.)"
)
threshold: Optional[float] = Field(
default=None,
description="Custom threshold (overrides model default)"
)
class AnomalyResponse(BaseModel):
"""Response model for anomaly detection."""
reconstruction_error: float
is_anomaly: bool
threshold: float
top_drivers: List[Dict[str, Any]]
error: Optional[str] = None
# Endpoints
@app.get("/health")
async def health():
"""Health check endpoint."""
return {
"status": "healthy",
"service": "job-failure-prediction",
"models_loaded": {
"predictor": _predictor is not None,
"anomaly_detector": _anomaly_detector is not None
}
}
@app.post("/predict/job-fail", response_model=JobFailureResponse)
async def predict_job_failure(request: JobFailureRequest):
"""
Predict job failure probability.
Accepts partial job data and returns failure probability, risk level,
and optional SHAP explanations.
"""
try:
# Convert request to DataFrame for preprocessing
data_dict = request.dict(exclude={'explain'})
# Create DataFrame with single row
df = pd.DataFrame([data_dict])
# If duration_sec not provided but duration is, parse it
if df['duration_sec'].isna().any() and df['duration'].notna().any():
from preprocessing import parse_duration
df['duration_sec'] = df['duration'].apply(parse_duration)
# If time features not provided, compute from job_start_time
if df['run_hour'].isna().any() and df['job_start_time'].notna().any():
df['job_start_time'] = pd.to_datetime(df['job_start_time'], errors='coerce')
df['run_hour'] = df['job_start_time'].dt.hour.fillna(0).astype(int)
df['run_dayofweek'] = df['job_start_time'].dt.dayofweek.fillna(0).astype(int)
# Engineer features
df = engineer_features(df)
# Predict
predictor = get_predictor()
result = predictor.predict(df, explain=request.explain)
return JobFailureResponse(**result)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
@app.post("/detect/anomaly", response_model=AnomalyResponse)
async def detect_anomaly(request: AnomalyRequest):
"""
Detect anomaly in feature vector.
Accepts a dictionary of feature values and returns reconstruction error,
anomaly flag, and top contributing features.
"""
try:
detector = get_anomaly_detector()
result = detector.detect(request.features, threshold=request.threshold)
return AnomalyResponse(**result)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Anomaly detection failed: {str(e)}")
@app.get("/")
async def root():
"""Root endpoint with API information."""
return {
"service": "Job Failure Prediction & Anomaly Detection API",
"version": "1.0.0",
"endpoints": {
"health": "/health",
"predict": "/predict/job-fail",
"anomaly": "/detect/anomaly"
},
"docs": "/docs"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)
|