Spaces:
Sleeping
Sleeping
Deploy to Hugging Face Spaces: Add application files and dependencies
Browse files- Add FastAPI application (app.py)
- Add preprocessing module (preprocessing.py)
- Add model utilities (model_utils.py)
- Add Dockerfile for HF Spaces deployment
- Add requirements.txt
- Add deployment documentation
- Add configuration files
- .dockerignore +50 -0
- .gitignore +65 -0
- DEPLOYMENT.md +410 -0
- DEPLOYMENT_SUMMARY.md +180 -0
- Dockerfile +7 -0
- README.md +286 -10
- README_HF_SPACE.md +179 -0
- model_utils.py +360 -0
- preprocessing.py +175 -0
.dockerignore
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
*.so
|
| 8 |
+
*.egg
|
| 9 |
+
*.egg-info
|
| 10 |
+
dist
|
| 11 |
+
build
|
| 12 |
+
.venv
|
| 13 |
+
venv/
|
| 14 |
+
ENV/
|
| 15 |
+
|
| 16 |
+
# Git
|
| 17 |
+
.git
|
| 18 |
+
.gitignore
|
| 19 |
+
.gitattributes
|
| 20 |
+
|
| 21 |
+
# Environment
|
| 22 |
+
.env
|
| 23 |
+
*.log
|
| 24 |
+
|
| 25 |
+
# OS
|
| 26 |
+
.DS_Store
|
| 27 |
+
Thumbs.db
|
| 28 |
+
|
| 29 |
+
# Documentation (keep README.md for HF Spaces)
|
| 30 |
+
*.md
|
| 31 |
+
!README.md
|
| 32 |
+
|
| 33 |
+
# Training data (not needed in production)
|
| 34 |
+
data/
|
| 35 |
+
*.csv
|
| 36 |
+
|
| 37 |
+
# Development files
|
| 38 |
+
.ipynb_checkpoints
|
| 39 |
+
.pytest_cache
|
| 40 |
+
.coverage
|
| 41 |
+
htmlcov/
|
| 42 |
+
test_*.py
|
| 43 |
+
train_*.py
|
| 44 |
+
|
| 45 |
+
# Docker
|
| 46 |
+
docker-compose.yml
|
| 47 |
+
.dockerignore
|
| 48 |
+
|
| 49 |
+
# Keep models/ directory - models are committed to repo
|
| 50 |
+
# Keep all application code (app.py, model_utils.py, preprocessing.py)
|
.gitignore
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual environments
|
| 24 |
+
venv/
|
| 25 |
+
ENV/
|
| 26 |
+
env/
|
| 27 |
+
.venv
|
| 28 |
+
|
| 29 |
+
# IDE
|
| 30 |
+
.vscode/
|
| 31 |
+
.idea/
|
| 32 |
+
*.swp
|
| 33 |
+
*.swo
|
| 34 |
+
*~
|
| 35 |
+
|
| 36 |
+
# Models and data
|
| 37 |
+
models/
|
| 38 |
+
data/
|
| 39 |
+
*.joblib
|
| 40 |
+
*.keras
|
| 41 |
+
*.h5
|
| 42 |
+
*.pkl
|
| 43 |
+
*.npy
|
| 44 |
+
|
| 45 |
+
# Logs
|
| 46 |
+
*.log
|
| 47 |
+
logs/
|
| 48 |
+
|
| 49 |
+
# Environment variables
|
| 50 |
+
.env
|
| 51 |
+
.env.local
|
| 52 |
+
|
| 53 |
+
# Jupyter
|
| 54 |
+
.ipynb_checkpoints/
|
| 55 |
+
*.ipynb
|
| 56 |
+
|
| 57 |
+
# Testing
|
| 58 |
+
.pytest_cache/
|
| 59 |
+
.coverage
|
| 60 |
+
htmlcov/
|
| 61 |
+
.tox/
|
| 62 |
+
|
| 63 |
+
# OS
|
| 64 |
+
.DS_Store
|
| 65 |
+
Thumbs.db
|
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces Deployment Guide
|
| 2 |
+
|
| 3 |
+
คู่มือการ deploy API ไปยัง Hugging Face Spaces สำหรับ production
|
| 4 |
+
|
| 5 |
+
## 📋 คำถามสำคัญก่อน Deploy
|
| 6 |
+
|
| 7 |
+
### Q1: ใช้ Docker Space หรือ Python Space?
|
| 8 |
+
**คำตอบ: Docker Space ✅**
|
| 9 |
+
|
| 10 |
+
**เหตุผล:**
|
| 11 |
+
- มี TensorFlow + XGBoost + joblib หลาย dependencies
|
| 12 |
+
- ต้องควบคุมเวอร์ชัน dependencies เอง
|
| 13 |
+
- Python Space อาจมี dependency conflicts
|
| 14 |
+
- Docker Space ให้ความยืดหยุ่นและควบคุมได้มากกว่า
|
| 15 |
+
|
| 16 |
+
### Q2: โหลด model จากไหน?
|
| 17 |
+
**คำตอบ: Commit models/ เข้า repo ✅**
|
| 18 |
+
|
| 19 |
+
**เหตุผล:**
|
| 20 |
+
- ✅ Predictable - models อยู่ใน repo เดียวกับ code
|
| 21 |
+
- ✅ ไม่ต้อง auth - ไม่ต้องใช้ HF token
|
| 22 |
+
- ✅ Startup เร็ว - ไม่ต้อง download ตอน startup
|
| 23 |
+
- ✅ Rollback ง่าย - git revert ได้เลย
|
| 24 |
+
|
| 25 |
+
**หมายเหตุ:** ถ้า models ใหญ่เกิน 1GB ให้พิจารณาใช้ HF Model Hub แทน
|
| 26 |
+
|
| 27 |
+
### Q3: API Pattern?
|
| 28 |
+
**คำตอบ: ตรงตาม microservice standard ✅**
|
| 29 |
+
|
| 30 |
+
- `/health` - Health check
|
| 31 |
+
- `/predict/job-fail` - Job failure prediction
|
| 32 |
+
- `/detect/anomaly` - Anomaly detection
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## 🚀 Step-by-Step Deployment
|
| 37 |
+
|
| 38 |
+
### Step 1: เตรียม Models
|
| 39 |
+
|
| 40 |
+
1. **Train models** (ถ้ายังไม่มี):
|
| 41 |
+
```bash
|
| 42 |
+
python train_job_failure.py data/*.csv
|
| 43 |
+
python train_anomaly.py data/*.csv
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
2. **ตรวจสอบ models directory:**
|
| 47 |
+
```bash
|
| 48 |
+
ls -lh models/
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
ต้องมีไฟล์เหล่านี้:
|
| 52 |
+
- `job_fail_pipeline_cpu.joblib`
|
| 53 |
+
- `anomaly_autoencoder_cpu.keras`
|
| 54 |
+
- `anomaly_scaler.joblib`
|
| 55 |
+
- `anomaly_features.joblib`
|
| 56 |
+
- `anomaly_threshold.joblib`
|
| 57 |
+
- `feature_schema.json`
|
| 58 |
+
- `shap_background.npy`
|
| 59 |
+
|
| 60 |
+
3. **ตรวจสอบขนาดไฟล์:**
|
| 61 |
+
```bash
|
| 62 |
+
du -sh models/
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
⚠️ **สำคัญ:** Hugging Face Spaces จำกัด repo size ~50GB (free tier)
|
| 66 |
+
- ถ้า models > 1GB ให้พิจารณาใช้ Git LFS หรือ HF Model Hub
|
| 67 |
+
|
| 68 |
+
### Step 2: เตรียม Code สำหรับ Deploy
|
| 69 |
+
|
| 70 |
+
1. **ตรวจสอบไฟล์ที่จำเป็น:**
|
| 71 |
+
```bash
|
| 72 |
+
# ไฟล์ที่ต้องมี
|
| 73 |
+
- Dockerfile
|
| 74 |
+
- requirements.txt
|
| 75 |
+
- app.py
|
| 76 |
+
- model_utils.py
|
| 77 |
+
- preprocessing.py
|
| 78 |
+
- models/ (directory with all model files)
|
| 79 |
+
- README.md หรือ README_HF_SPACE.md
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
2. **ตรวจสอบ .dockerignore:**
|
| 83 |
+
- ต้อง exclude `data/`, `*.csv`, `train_*.py`, `test_*.py`
|
| 84 |
+
- ต้อง **include** `models/` directory
|
| 85 |
+
|
| 86 |
+
3. **ตรวจสอบ Dockerfile:**
|
| 87 |
+
- Port ต้องเป็น `7860` (HF Spaces default)
|
| 88 |
+
- Health check ต้องใช้ port `7860`
|
| 89 |
+
|
| 90 |
+
### Step 3: สร้าง Hugging Face Space
|
| 91 |
+
|
| 92 |
+
1. **ไปที่ Hugging Face Spaces:**
|
| 93 |
+
- เปิด https://huggingface.co/spaces
|
| 94 |
+
- คลิก "Create new Space"
|
| 95 |
+
|
| 96 |
+
2. **ตั้งค่า Space:**
|
| 97 |
+
- **Space name:** `job-failure-prediction-api` (หรือชื่อที่ต้องการ)
|
| 98 |
+
- **SDK:** เลือก **Docker** (ไม่ใช่ Python)
|
| 99 |
+
- **Visibility:** Public หรือ Private (ตามต้องการ)
|
| 100 |
+
- **Hardware:** CPU Basic (free) หรือ GPU (ถ้าต้องการ)
|
| 101 |
+
|
| 102 |
+
3. **คลิก "Create Space"**
|
| 103 |
+
|
| 104 |
+
### Step 4: Upload Code ไปยัง Space
|
| 105 |
+
|
| 106 |
+
#### Option A: ใช้ Git (แนะนำ)
|
| 107 |
+
|
| 108 |
+
1. **Clone Space repository:**
|
| 109 |
+
```bash
|
| 110 |
+
git clone https://huggingface.co/spaces/[your-username]/[space-name]
|
| 111 |
+
cd [space-name]
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
2. **Copy files จาก project:**
|
| 115 |
+
```bash
|
| 116 |
+
# Copy application files
|
| 117 |
+
cp ../ml_service/app.py .
|
| 118 |
+
cp ../ml_service/model_utils.py .
|
| 119 |
+
cp ../ml_service/preprocessing.py .
|
| 120 |
+
cp ../ml_service/requirements.txt .
|
| 121 |
+
cp ../ml_service/Dockerfile .
|
| 122 |
+
|
| 123 |
+
# Copy models directory
|
| 124 |
+
cp -r ../ml_service/models .
|
| 125 |
+
|
| 126 |
+
# Copy README for HF Space
|
| 127 |
+
cp ../ml_service/README_HF_SPACE.md README.md
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
3. **Commit และ Push:**
|
| 131 |
+
```bash
|
| 132 |
+
git add .
|
| 133 |
+
git commit -m "Initial deployment: Job Failure Prediction API"
|
| 134 |
+
git push
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
#### Option B: ใช้ Web UI
|
| 138 |
+
|
| 139 |
+
1. **Upload files ผ่าน web interface:**
|
| 140 |
+
- ไปที่ Space page
|
| 141 |
+
- คลิก "Files and versions" tab
|
| 142 |
+
- Upload files ทีละไฟล์:
|
| 143 |
+
- `Dockerfile`
|
| 144 |
+
- `requirements.txt`
|
| 145 |
+
- `app.py`
|
| 146 |
+
- `model_utils.py`
|
| 147 |
+
- `preprocessing.py`
|
| 148 |
+
- `README.md` (copy จาก README_HF_SPACE.md)
|
| 149 |
+
|
| 150 |
+
2. **Upload models:**
|
| 151 |
+
- สร้าง folder `models/`
|
| 152 |
+
- Upload model files ทั้งหมดเข้าไป
|
| 153 |
+
|
| 154 |
+
⚠️ **หมายเหตุ:** Git method เร็วกว่า��ละเหมาะกับ large files มากกว่า
|
| 155 |
+
|
| 156 |
+
### Step 5: รอ Build และ Deploy
|
| 157 |
+
|
| 158 |
+
1. **ตรวจสอบ Build Logs:**
|
| 159 |
+
- ไปที่ Space page
|
| 160 |
+
- คลิก "Logs" tab
|
| 161 |
+
- ดู build progress
|
| 162 |
+
|
| 163 |
+
2. **Build time:** ประมาณ 5-15 นาที (ขึ้นกับขนาด models)
|
| 164 |
+
|
| 165 |
+
3. **ตรวจสอบ Health:**
|
| 166 |
+
- เมื่อ build เสร็จ ไปที่ `https://[space-name].hf.space/health`
|
| 167 |
+
- ควรได้ response:
|
| 168 |
+
```json
|
| 169 |
+
{
|
| 170 |
+
"status": "healthy",
|
| 171 |
+
"service": "job-failure-prediction",
|
| 172 |
+
"models_loaded": {
|
| 173 |
+
"predictor": true,
|
| 174 |
+
"anomaly_detector": true
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
### Step 6: ทดสอบ API
|
| 180 |
+
|
| 181 |
+
1. **Test Health Endpoint:**
|
| 182 |
+
```bash
|
| 183 |
+
curl https://[your-username]-[space-name].hf.space/health
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
2. **Test Prediction Endpoint:**
|
| 187 |
+
```bash
|
| 188 |
+
curl -X POST https://[your-username]-[space-name].hf.space/predict/job-fail \
|
| 189 |
+
-H "Content-Type: application/json" \
|
| 190 |
+
-d '{
|
| 191 |
+
"zone": "prod",
|
| 192 |
+
"job_nm": "test_job",
|
| 193 |
+
"job_start_time": "2026-01-21T01:00:00",
|
| 194 |
+
"duration_sec": 3600,
|
| 195 |
+
"status": "SUCCESS"
|
| 196 |
+
}'
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
3. **Test Anomaly Detection:**
|
| 200 |
+
```bash
|
| 201 |
+
curl -X POST https://[your-username]-[space-name].hf.space/detect/anomaly \
|
| 202 |
+
-H "Content-Type: application/json" \
|
| 203 |
+
-d '{
|
| 204 |
+
"features": {
|
| 205 |
+
"duration_sec": 5400,
|
| 206 |
+
"duration_zscore": 1.6,
|
| 207 |
+
"err_msg_len": 0
|
| 208 |
+
}
|
| 209 |
+
}'
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
4. **Test API Documentation:**
|
| 213 |
+
- เปิด `https://[space-name].hf.space/docs` (Swagger UI)
|
| 214 |
+
- เปิด `https://[space-name].hf.space/redoc` (ReDoc)
|
| 215 |
+
|
| 216 |
+
### Step 7: Configuration (Optional)
|
| 217 |
+
|
| 218 |
+
#### Environment Variables
|
| 219 |
+
|
| 220 |
+
ถ้าต้องการ config แบบ dynamic สามารถเพิ่ม environment variables ใน Space settings:
|
| 221 |
+
|
| 222 |
+
1. ไปที่ Space → Settings → Variables
|
| 223 |
+
2. เพิ่ม variables (ถ้าต้องการ):
|
| 224 |
+
- `MODEL_PATH` (default: `models/`)
|
| 225 |
+
- `LOG_LEVEL` (default: `INFO`)
|
| 226 |
+
|
| 227 |
+
#### Hardware Upgrade
|
| 228 |
+
|
| 229 |
+
ถ้าต้องการ performance มากขึ้น:
|
| 230 |
+
1. ไปที่ Settings → Hardware
|
| 231 |
+
2. เลือก:
|
| 232 |
+
- **CPU Basic** (free) - เหมาะกับ production แรก
|
| 233 |
+
- **CPU Upgrade** (paid) - สำหรับ traffic สูง
|
| 234 |
+
- **GPU** (paid) - ถ้า models ใหญ่และต้องการ inference เร็ว
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## 🔍 Troubleshooting
|
| 239 |
+
|
| 240 |
+
### Problem: Build Fails
|
| 241 |
+
|
| 242 |
+
**สาเหตุที่เป็นไปได้:**
|
| 243 |
+
1. Dockerfile syntax error
|
| 244 |
+
2. Dependencies conflict
|
| 245 |
+
3. Models ไม่มีใน repo
|
| 246 |
+
|
| 247 |
+
**แก้ไข:**
|
| 248 |
+
```bash
|
| 249 |
+
# ตรวจสอบ Dockerfile
|
| 250 |
+
docker build -t test-build .
|
| 251 |
+
|
| 252 |
+
# ตรวจสอบ logs ใน HF Spaces
|
| 253 |
+
# ดู error message ใน Logs tab
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
### Problem: Models Not Loading
|
| 257 |
+
|
| 258 |
+
**สาเหตุ:**
|
| 259 |
+
- Models directory ไม่ถูก copy
|
| 260 |
+
- Path ไม่ถูกต้อง
|
| 261 |
+
|
| 262 |
+
**แก้ไข:**
|
| 263 |
+
```bash
|
| 264 |
+
# ตรวจสอบใน Space
|
| 265 |
+
# ไปที่ Files tab → ดูว่ามี models/ directory หรือไม่
|
| 266 |
+
|
| 267 |
+
# ตรวจสอบ logs
|
| 268 |
+
# ดู error message เกี่ยวกับ model loading
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
### Problem: Port Error
|
| 272 |
+
|
| 273 |
+
**สาเหตุ:**
|
| 274 |
+
- App ใช้ port ไม่ใช่ 7860
|
| 275 |
+
|
| 276 |
+
**แก้ไข:**
|
| 277 |
+
- ตรวจสอบ `app.py` และ `Dockerfile` ว่าใช้ port 7860
|
| 278 |
+
|
| 279 |
+
### Problem: Slow Startup
|
| 280 |
+
|
| 281 |
+
**สาเหตุ:**
|
| 282 |
+
- Models ใหญ่เกินไป
|
| 283 |
+
- Loading models จาก network
|
| 284 |
+
|
| 285 |
+
**แก้ไข:**
|
| 286 |
+
- ใช้ models ที่ commit ใน repo (ไม่ download)
|
| 287 |
+
- ตรวจสอบ model size และ optimize ถ้าจำเป็น
|
| 288 |
+
|
| 289 |
+
---
|
| 290 |
+
|
| 291 |
+
## 📊 Monitoring
|
| 292 |
+
|
| 293 |
+
### Health Check
|
| 294 |
+
|
| 295 |
+
ตั้งค่า monitoring เพื่อตรวจสอบ health:
|
| 296 |
+
```bash
|
| 297 |
+
# Cron job หรือ monitoring service
|
| 298 |
+
curl https://[space-name].hf.space/health
|
| 299 |
+
```
|
| 300 |
+
|
| 301 |
+
### Logs
|
| 302 |
+
|
| 303 |
+
ดู logs ใน Hugging Face Spaces:
|
| 304 |
+
1. ไปที่ Space → Logs tab
|
| 305 |
+
2. ดู application logs และ errors
|
| 306 |
+
|
| 307 |
+
### Metrics
|
| 308 |
+
|
| 309 |
+
Hugging Face Spaces แสดง:
|
| 310 |
+
- Request count
|
| 311 |
+
- Response time
|
| 312 |
+
- Error rate
|
| 313 |
+
- Resource usage
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## 🔄 Updates และ Rollback
|
| 318 |
+
|
| 319 |
+
### Update Models
|
| 320 |
+
|
| 321 |
+
1. **Train new models:**
|
| 322 |
+
```bash
|
| 323 |
+
python train_job_failure.py data/*.csv
|
| 324 |
+
python train_anomaly.py data/*.csv
|
| 325 |
+
```
|
| 326 |
+
|
| 327 |
+
2. **Update in Space:**
|
| 328 |
+
```bash
|
| 329 |
+
cd [space-repo]
|
| 330 |
+
cp -r ../ml_service/models .
|
| 331 |
+
git add models/
|
| 332 |
+
git commit -m "Update models"
|
| 333 |
+
git push
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
3. **Space จะ rebuild อัตโนมัติ**
|
| 337 |
+
|
| 338 |
+
### Rollback
|
| 339 |
+
|
| 340 |
+
```bash
|
| 341 |
+
# ดู commit history
|
| 342 |
+
git log
|
| 343 |
+
|
| 344 |
+
# Rollback to previous version
|
| 345 |
+
git revert HEAD
|
| 346 |
+
git push
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
---
|
| 350 |
+
|
| 351 |
+
## 🎯 Best Practices
|
| 352 |
+
|
| 353 |
+
1. **Version Control:**
|
| 354 |
+
- ใช้ Git tags สำหรับ releases
|
| 355 |
+
- Commit messages ที่ชัดเจน
|
| 356 |
+
|
| 357 |
+
2. **Testing:**
|
| 358 |
+
- Test ทุก endpoint หลัง deploy
|
| 359 |
+
- ใช้ staging space สำหรับ testing
|
| 360 |
+
|
| 361 |
+
3. **Documentation:**
|
| 362 |
+
- อัพเดท README.md เมื่อมีการเปลี่ยนแปลง
|
| 363 |
+
- Document API changes
|
| 364 |
+
|
| 365 |
+
4. **Monitoring:**
|
| 366 |
+
- ตั้งค่า alerts สำหรับ health check failures
|
| 367 |
+
- Monitor response times
|
| 368 |
+
|
| 369 |
+
5. **Security:**
|
| 370 |
+
- ใช้ Private Space สำหรับ sensitive data
|
| 371 |
+
- ตรวจสอบ CORS settings
|
| 372 |
+
|
| 373 |
+
---
|
| 374 |
+
|
| 375 |
+
## 📚 Resources
|
| 376 |
+
|
| 377 |
+
- [Hugging Face Spaces Documentation](https://huggingface.co/docs/hub/spaces)
|
| 378 |
+
- [Docker Spaces Guide](https://huggingface.co/docs/hub/spaces-sdks-docker)
|
| 379 |
+
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
| 380 |
+
|
| 381 |
+
---
|
| 382 |
+
|
| 383 |
+
## ✅ Checklist
|
| 384 |
+
|
| 385 |
+
ก่อน deploy ตรวจสอบ:
|
| 386 |
+
|
| 387 |
+
- [ ] Models ถูก train และอยู่ใน `models/` directory
|
| 388 |
+
- [ ] Dockerfile ใช้ port 7860
|
| 389 |
+
- [ ] Health check ใช้ port 7860
|
| 390 |
+
- [ ] `.dockerignore` exclude files ที่ไม่จำเป็น
|
| 391 |
+
- [ ] `requirements.txt` มี dependencies ครบ
|
| 392 |
+
- [ ] `README.md` สำหรับ HF Space พร้อม
|
| 393 |
+
- [ ] Test API locally ก่อน deploy
|
| 394 |
+
- [ ] Space ถูกสร้างและตั้งค่าเป็น Docker
|
| 395 |
+
- [ ] Code และ models ถูก upload ครบ
|
| 396 |
+
- [ ] Build สำเร็จและ health check ผ่าน
|
| 397 |
+
- [ ] ทุก endpoint ทำงานถูกต้อง
|
| 398 |
+
|
| 399 |
+
---
|
| 400 |
+
|
| 401 |
+
## 🎉 Success!
|
| 402 |
+
|
| 403 |
+
เมื่อ deploy สำเร็จ คุณจะมี production-ready API ที่:
|
| 404 |
+
- ✅ Auto-scaling
|
| 405 |
+
- ✅ HTTPS enabled
|
| 406 |
+
- ✅ Global CDN
|
| 407 |
+
- ✅ Monitoring และ logging
|
| 408 |
+
- ✅ Easy rollback
|
| 409 |
+
|
| 410 |
+
API URL: `https://[your-username]-[space-name].hf.space`
|
DEPLOYMENT_SUMMARY.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 Hugging Face Deployment - สรุปการเตรียมพร้อม
|
| 2 |
+
|
| 3 |
+
## ✅ คำตอบ 3 คำถามสำคัญ
|
| 4 |
+
|
| 5 |
+
### Q1: ใช้ Docker Space หรือ Python Space?
|
| 6 |
+
**คำตอบ: Docker Space ✅**
|
| 7 |
+
|
| 8 |
+
**เหตุผล:**
|
| 9 |
+
- มี TensorFlow + XGBoost + joblib หลาย dependencies
|
| 10 |
+
- ต้องควบคุมเวอร์ชัน dependencies เอง
|
| 11 |
+
- Python Space อาจมี dependency conflicts
|
| 12 |
+
- Docker Space ให้ความยืดหยุ่นและควบคุมได้มากกว่า
|
| 13 |
+
|
| 14 |
+
### Q2: โหลด model จากไหน?
|
| 15 |
+
**คำตอบ: Commit models/ เข้า repo ✅**
|
| 16 |
+
|
| 17 |
+
**เหตุผล:**
|
| 18 |
+
- ✅ Predictable - models อยู่ใน repo เดียวกับ code
|
| 19 |
+
- ✅ ไม่ต้อง auth - ไม่ต้องใช้ HF token
|
| 20 |
+
- ✅ Startup เร็ว - ไม่ต้อง download ตอน startup
|
| 21 |
+
- ✅ Rollback ง่าย - git revert ได้เลย
|
| 22 |
+
|
| 23 |
+
### Q3: API Pattern?
|
| 24 |
+
**คำตอบ: ตรงตาม microservice standard ✅**
|
| 25 |
+
|
| 26 |
+
- `/health` - Health check
|
| 27 |
+
- `/predict/job-fail` - Job failure prediction
|
| 28 |
+
- `/detect/anomaly` - Anomaly detection
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## 📝 สิ่งที่ทำเสร็จแล้ว
|
| 33 |
+
|
| 34 |
+
### 1. ✅ Dockerfile (อัพเดทแล้ว)
|
| 35 |
+
- ใช้ port **7860** (Hugging Face Spaces default)
|
| 36 |
+
- Health check ใช้ port 7860
|
| 37 |
+
- Optimize สำหรับ production
|
| 38 |
+
- เพิ่ม build dependencies
|
| 39 |
+
|
| 40 |
+
### 2. ✅ .dockerignore (อัพเดทแล้ว)
|
| 41 |
+
- Exclude files ที่ไม่จำเป็น (data/, train_*.py, test_*.py)
|
| 42 |
+
- **Keep** models/ directory (models จะถูก commit)
|
| 43 |
+
- Optimize build time
|
| 44 |
+
|
| 45 |
+
### 3. ✅ model_utils.py (เพิ่ม Environment Variables)
|
| 46 |
+
- รองรับ `MODEL_BASE_PATH` environment variable
|
| 47 |
+
- รองรับ individual model paths ผ่าน env vars
|
| 48 |
+
- Default ยังคงเป็น local models/ directory
|
| 49 |
+
- พร้อมสำหรับ future: load จาก HF Hub (ถ้าต้องการ)
|
| 50 |
+
|
| 51 |
+
### 4. ✅ README_HF_SPACE.md (สร้างใหม่)
|
| 52 |
+
- README สำหรับ Hugging Face Space
|
| 53 |
+
- API documentation
|
| 54 |
+
- Example usage
|
| 55 |
+
- Configuration guide
|
| 56 |
+
|
| 57 |
+
### 5. ✅ DEPLOYMENT.md (สร้างใหม่)
|
| 58 |
+
- Step-by-step deployment guide
|
| 59 |
+
- Troubleshooting
|
| 60 |
+
- Best practices
|
| 61 |
+
- Checklist
|
| 62 |
+
|
| 63 |
+
### 6. ✅ app.py (ตรวจสอบแล้ว)
|
| 64 |
+
- ใช้ port 7860 ✅
|
| 65 |
+
- FastAPI setup ถูกต้อง ✅
|
| 66 |
+
- CORS enabled ✅
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
## 🎯 ขั้นตอนต่อไป (Deploy)
|
| 71 |
+
|
| 72 |
+
### Step 1: เตรียม Models
|
| 73 |
+
```bash
|
| 74 |
+
# ตรวจสอบว่า models ถูก train แล้ว
|
| 75 |
+
ls -lh models/
|
| 76 |
+
|
| 77 |
+
# ต้องมีไฟล์เหล่านี้:
|
| 78 |
+
# - job_fail_pipeline_cpu.joblib
|
| 79 |
+
# - anomaly_autoencoder_cpu.keras
|
| 80 |
+
# - anomaly_scaler.joblib
|
| 81 |
+
# - anomaly_features.joblib
|
| 82 |
+
# - anomaly_threshold.joblib
|
| 83 |
+
# - feature_schema.json
|
| 84 |
+
# - shap_background.npy
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### Step 2: สร้าง Hugging Face Space
|
| 88 |
+
1. ไปที่ https://huggingface.co/spaces
|
| 89 |
+
2. คลิก "Create new Space"
|
| 90 |
+
3. ตั้งค่า:
|
| 91 |
+
- **SDK:** Docker (ไม่ใช่ Python!)
|
| 92 |
+
- **Hardware:** CPU Basic (free) หรือ upgrade
|
| 93 |
+
- **Visibility:** Public/Private
|
| 94 |
+
|
| 95 |
+
### Step 3: Upload Code
|
| 96 |
+
```bash
|
| 97 |
+
# Option A: Git (แนะนำ)
|
| 98 |
+
git clone https://huggingface.co/spaces/[username]/[space-name]
|
| 99 |
+
cd [space-name]
|
| 100 |
+
|
| 101 |
+
# Copy files
|
| 102 |
+
cp ../ml_service/app.py .
|
| 103 |
+
cp ../ml_service/model_utils.py .
|
| 104 |
+
cp ../ml_service/preprocessing.py .
|
| 105 |
+
cp ../ml_service/requirements.txt .
|
| 106 |
+
cp ../ml_service/Dockerfile .
|
| 107 |
+
cp ../ml_service/README_HF_SPACE.md README.md
|
| 108 |
+
cp -r ../ml_service/models .
|
| 109 |
+
|
| 110 |
+
# Commit
|
| 111 |
+
git add .
|
| 112 |
+
git commit -m "Initial deployment"
|
| 113 |
+
git push
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
### Step 4: รอ Build
|
| 117 |
+
- ดู logs ใน Space → Logs tab
|
| 118 |
+
- Build time: ~5-15 นาที
|
| 119 |
+
|
| 120 |
+
### Step 5: Test
|
| 121 |
+
```bash
|
| 122 |
+
# Health check
|
| 123 |
+
curl https://[space-name].hf.space/health
|
| 124 |
+
|
| 125 |
+
# Test prediction
|
| 126 |
+
curl -X POST https://[space-name].hf.space/predict/job-fail \
|
| 127 |
+
-H "Content-Type: application/json" \
|
| 128 |
+
-d '{"zone": "prod", "job_nm": "test", "job_start_time": "2026-01-21T01:00:00", "duration_sec": 3600}'
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## 📋 Checklist ก่อน Deploy
|
| 134 |
+
|
| 135 |
+
- [x] Dockerfile ใช้ port 7860
|
| 136 |
+
- [x] Health check ใช้ port 7860
|
| 137 |
+
- [x] .dockerignore exclude files ที่ไม่จำเป็น
|
| 138 |
+
- [x] models/ directory พร้อม commit
|
| 139 |
+
- [x] Environment variables support
|
| 140 |
+
- [x] README_HF_SPACE.md พร้อม
|
| 141 |
+
- [x] DEPLOYMENT.md มีคำแนะนำครบ
|
| 142 |
+
- [ ] Models ถูก train และอยู่ใน models/
|
| 143 |
+
- [ ] Test API locally ก่อน deploy
|
| 144 |
+
- [ ] สร้าง Hugging Face Space
|
| 145 |
+
- [ ] Upload code และ models
|
| 146 |
+
- [ ] Test ทุก endpoint หลัง deploy
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
## 🔗 ไฟล์ที่สำคัญ
|
| 151 |
+
|
| 152 |
+
1. **Dockerfile** - Docker configuration สำหรับ HF Spaces
|
| 153 |
+
2. **README_HF_SPACE.md** - README สำหรับ Space page
|
| 154 |
+
3. **DEPLOYMENT.md** - คู่มือ deploy แบบละเอียด
|
| 155 |
+
4. **.dockerignore** - Exclude files สำหรับ Docker build
|
| 156 |
+
5. **model_utils.py** - รองรับ environment variables
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## 🎉 พร้อม Deploy!
|
| 161 |
+
|
| 162 |
+
ทุกอย่างพร้อมแล้วสำหรับ production deployment ไปยัง Hugging Face Spaces
|
| 163 |
+
|
| 164 |
+
**API URL หลัง deploy:**
|
| 165 |
+
```
|
| 166 |
+
https://[your-username]-[space-name].hf.space
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
**Documentation:**
|
| 170 |
+
- Swagger UI: `/docs`
|
| 171 |
+
- ReDoc: `/redoc`
|
| 172 |
+
- Health: `/health`
|
| 173 |
+
|
| 174 |
+
---
|
| 175 |
+
|
| 176 |
+
## 📚 อ่านเพิ่มเติม
|
| 177 |
+
|
| 178 |
+
- [DEPLOYMENT.md](DEPLOYMENT.md) - คู่มือ deploy แบบละเอียด
|
| 179 |
+
- [README_HF_SPACE.md](README_HF_SPACE.md) - README สำหรับ Space
|
| 180 |
+
- [README.md](README.md) - Documentation หลัก
|
Dockerfile
CHANGED
|
@@ -20,6 +20,13 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
|
| 20 |
# Copy application code and models
|
| 21 |
COPY --chown=user . /app
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
# Ensure models directory exists (models should be committed to repo)
|
| 24 |
RUN mkdir -p /app/models /app/data
|
| 25 |
|
|
|
|
| 20 |
# Copy application code and models
|
| 21 |
COPY --chown=user . /app
|
| 22 |
|
| 23 |
+
# Verify critical files exist (helps debug if files are missing)
|
| 24 |
+
RUN ls -la /app/*.py && \
|
| 25 |
+
test -f /app/app.py || (echo "ERROR: app.py not found!" && exit 1) && \
|
| 26 |
+
test -f /app/preprocessing.py || (echo "ERROR: preprocessing.py not found!" && exit 1) && \
|
| 27 |
+
test -f /app/model_utils.py || (echo "ERROR: model_utils.py not found!" && exit 1) && \
|
| 28 |
+
echo "All required Python files found!"
|
| 29 |
+
|
| 30 |
# Ensure models directory exists (models should be committed to repo)
|
| 31 |
RUN mkdir -p /app/models /app/data
|
| 32 |
|
README.md
CHANGED
|
@@ -1,10 +1,286 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
--
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Job Failure Prediction & Anomaly Detection ML Service
|
| 2 |
+
|
| 3 |
+
Production-ready ML service for predicting job failures and detecting anomalies in job execution data.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Job Failure Prediction**: XGBoost-based classifier with SHAP explainability
|
| 8 |
+
- **Anomaly Detection**: Autoencoder-based unsupervised anomaly detection
|
| 9 |
+
- **FastAPI REST API**: Production-ready endpoints
|
| 10 |
+
- **Docker Support**: Containerized deployment
|
| 11 |
+
- **n8n Integration**: Ready-to-use workflow examples
|
| 12 |
+
|
| 13 |
+
## Quick Start
|
| 14 |
+
|
| 15 |
+
### 1. Training Models
|
| 16 |
+
|
| 17 |
+
Place your CSV files in the `data/` directory:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
mkdir -p data models
|
| 21 |
+
# Copy your CSV files: true_export_report_20260120.csv, true_export_report_20260121.csv
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
Train the job failure prediction model:
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
python train_job_failure.py data/true_export_report_20260120.csv data/true_export_report_20260121.csv
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Train the anomaly detection model:
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
python train_anomaly.py data/true_export_report_20260120.csv data/true_export_report_20260121.csv
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
Models will be saved to `models/` directory.
|
| 37 |
+
|
| 38 |
+
### 2. Running the Service
|
| 39 |
+
|
| 40 |
+
#### Local Development
|
| 41 |
+
|
| 42 |
+
```bash
|
| 43 |
+
pip install -r requirements.txt
|
| 44 |
+
uvicorn app:app --host 0.0.0.0 --port 8000
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
#### Docker
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
docker-compose up --build
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
The service will be available at `http://localhost:8000`
|
| 54 |
+
|
| 55 |
+
### 3. API Documentation
|
| 56 |
+
|
| 57 |
+
Once running, visit:
|
| 58 |
+
- Swagger UI: `http://localhost:8000/docs`
|
| 59 |
+
- ReDoc: `http://localhost:8000/redoc`
|
| 60 |
+
|
| 61 |
+
## API Endpoints
|
| 62 |
+
|
| 63 |
+
### Health Check
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
GET /health
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### Job Failure Prediction
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
POST /predict/job-fail
|
| 73 |
+
Content-Type: application/json
|
| 74 |
+
|
| 75 |
+
{
|
| 76 |
+
"zone": "prod",
|
| 77 |
+
"job_nm": "daily_export_customer",
|
| 78 |
+
"job_start_time": "2026-01-21T01:00:00",
|
| 79 |
+
"duration_sec": 5400,
|
| 80 |
+
"status": "SUCCESS",
|
| 81 |
+
"err_msg": "",
|
| 82 |
+
"explain": true
|
| 83 |
+
}
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
**Response:**
|
| 87 |
+
```json
|
| 88 |
+
{
|
| 89 |
+
"fail_probability": 0.79,
|
| 90 |
+
"risk_level": "MEDIUM",
|
| 91 |
+
"top_drivers": [
|
| 92 |
+
{
|
| 93 |
+
"feature": "failure_rate_7",
|
| 94 |
+
"shap_value": 0.30,
|
| 95 |
+
"effect": "increase"
|
| 96 |
+
}
|
| 97 |
+
],
|
| 98 |
+
"recommended_actions": [
|
| 99 |
+
"Monitor upstream dependencies and recent job history"
|
| 100 |
+
]
|
| 101 |
+
}
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### Anomaly Detection
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
POST /detect/anomaly
|
| 108 |
+
Content-Type: application/json
|
| 109 |
+
|
| 110 |
+
{
|
| 111 |
+
"features": {
|
| 112 |
+
"duration_sec": 5400,
|
| 113 |
+
"duration_zscore": 1.6,
|
| 114 |
+
"err_msg_len": 0
|
| 115 |
+
},
|
| 116 |
+
"threshold": 0.01
|
| 117 |
+
}
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
**Response:**
|
| 121 |
+
```json
|
| 122 |
+
{
|
| 123 |
+
"reconstruction_error": 0.0235,
|
| 124 |
+
"is_anomaly": true,
|
| 125 |
+
"threshold": 0.01,
|
| 126 |
+
"top_drivers": [
|
| 127 |
+
{
|
| 128 |
+
"feature": "duration_zscore",
|
| 129 |
+
"error": 0.0142
|
| 130 |
+
}
|
| 131 |
+
]
|
| 132 |
+
}
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
## Project Structure
|
| 136 |
+
|
| 137 |
+
```
|
| 138 |
+
ml_service/
|
| 139 |
+
├── app.py # FastAPI application
|
| 140 |
+
├── preprocessing.py # Data preprocessing and feature engineering
|
| 141 |
+
├── model_utils.py # Model inference with SHAP
|
| 142 |
+
├── train_job_failure.py # Training script for failure prediction
|
| 143 |
+
├── train_anomaly.py # Training script for anomaly detection
|
| 144 |
+
├── requirements.txt # Python dependencies
|
| 145 |
+
├── Dockerfile # Docker image definition
|
| 146 |
+
├── docker-compose.yml # Docker Compose configuration
|
| 147 |
+
├── n8n_workflow_examples.md # n8n integration guide
|
| 148 |
+
├── n8n_workflow_job_monitoring.json # Importable n8n workflow
|
| 149 |
+
├── models/ # Trained models (created after training)
|
| 150 |
+
│ ├── job_fail_pipeline_cpu.joblib
|
| 151 |
+
│ ├── anomaly_autoencoder_cpu.keras
|
| 152 |
+
│ ├── anomaly_scaler.joblib
|
| 153 |
+
│ ├── feature_schema.json
|
| 154 |
+
│ └── ...
|
| 155 |
+
└── data/ # Training data (user-provided)
|
| 156 |
+
├── true_export_report_20260120.csv
|
| 157 |
+
└── true_export_report_20260121.csv
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
## Features Engineering
|
| 161 |
+
|
| 162 |
+
The service automatically engineers the following features:
|
| 163 |
+
|
| 164 |
+
### Numeric Features
|
| 165 |
+
- `duration_sec`: Job duration in seconds
|
| 166 |
+
- `duration_zscore`: Z-score relative to rolling average
|
| 167 |
+
- `avg_duration_7`: 7-day rolling average duration
|
| 168 |
+
- `failure_rate_7`: 7-day rolling failure rate
|
| 169 |
+
- `err_msg_len`: Error message length
|
| 170 |
+
- `hour_sin`, `hour_cos`: Cyclical hour encoding
|
| 171 |
+
|
| 172 |
+
### Categorical Features
|
| 173 |
+
- `job_nm`: Job name
|
| 174 |
+
- `tasksgroup_nm`: Task group name
|
| 175 |
+
- `zone`: Environment/cluster
|
| 176 |
+
- `is_zeppelin`: Zeppelin flag
|
| 177 |
+
- `is_weekend`: Weekend indicator
|
| 178 |
+
|
| 179 |
+
## Model Details
|
| 180 |
+
|
| 181 |
+
### Job Failure Prediction
|
| 182 |
+
- **Algorithm**: XGBoost Classifier
|
| 183 |
+
- **Preprocessing**: StandardScaler for numeric, OneHotEncoder for categorical
|
| 184 |
+
- **Explainability**: SHAP values for feature importance
|
| 185 |
+
- **Output**: Failure probability (0-1), risk level, top drivers, recommended actions
|
| 186 |
+
|
| 187 |
+
### Anomaly Detection
|
| 188 |
+
- **Algorithm**: Autoencoder (TensorFlow/Keras)
|
| 189 |
+
- **Architecture**: Input → 64 → 32 → 64 → Output
|
| 190 |
+
- **Threshold**: Per-job 97th percentile or global threshold
|
| 191 |
+
- **Output**: Reconstruction error, anomaly flag, top contributing features
|
| 192 |
+
|
| 193 |
+
## n8n Integration
|
| 194 |
+
|
| 195 |
+
See `n8n_workflow_examples.md` for detailed integration examples and `n8n_workflow_job_monitoring.json` for an importable workflow.
|
| 196 |
+
|
| 197 |
+
### Quick n8n Setup
|
| 198 |
+
|
| 199 |
+
1. Import `n8n_workflow_job_monitoring.json` into n8n
|
| 200 |
+
2. Update the HTTP Request URLs to match your service endpoint
|
| 201 |
+
3. Configure Slack credentials (or replace with your alerting system)
|
| 202 |
+
4. Activate the workflow
|
| 203 |
+
|
| 204 |
+
## Risk Levels
|
| 205 |
+
|
| 206 |
+
- **MINIMAL**: `fail_probability < 0.3`
|
| 207 |
+
- **LOW**: `0.3 <= fail_probability < 0.5`
|
| 208 |
+
- **MEDIUM**: `0.5 <= fail_probability < 0.8`
|
| 209 |
+
- **CRITICAL**: `fail_probability >= 0.8`
|
| 210 |
+
|
| 211 |
+
## Alert Conditions
|
| 212 |
+
|
| 213 |
+
### WARNING
|
| 214 |
+
- `fail_probability >= 0.5 && fail_probability < 0.8`
|
| 215 |
+
- OR `is_anomaly === true` with moderate reconstruction error
|
| 216 |
+
|
| 217 |
+
### CRITICAL
|
| 218 |
+
- `fail_probability >= 0.8`
|
| 219 |
+
- OR `is_anomaly === true` with high reconstruction error (> 3x threshold)
|
| 220 |
+
|
| 221 |
+
## Development
|
| 222 |
+
|
| 223 |
+
### Running Tests
|
| 224 |
+
|
| 225 |
+
```bash
|
| 226 |
+
# Test prediction endpoint
|
| 227 |
+
curl -X POST http://localhost:8000/predict/job-fail \
|
| 228 |
+
-H "Content-Type: application/json" \
|
| 229 |
+
-d '{
|
| 230 |
+
"zone": "prod",
|
| 231 |
+
"job_nm": "test_job",
|
| 232 |
+
"job_start_time": "2026-01-21T01:00:00",
|
| 233 |
+
"duration_sec": 3600,
|
| 234 |
+
"status": "SUCCESS"
|
| 235 |
+
}'
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
### Model Retraining
|
| 239 |
+
|
| 240 |
+
Models should be retrained periodically as new data becomes available:
|
| 241 |
+
|
| 242 |
+
```bash
|
| 243 |
+
# Add new CSV files to data/ directory
|
| 244 |
+
python train_job_failure.py data/*.csv
|
| 245 |
+
python train_anomaly.py data/*.csv
|
| 246 |
+
|
| 247 |
+
# Restart service to load new models
|
| 248 |
+
docker-compose restart
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
## Monitoring
|
| 252 |
+
|
| 253 |
+
The service includes a health check endpoint that reports model loading status:
|
| 254 |
+
|
| 255 |
+
```bash
|
| 256 |
+
GET /health
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
Response:
|
| 260 |
+
```json
|
| 261 |
+
{
|
| 262 |
+
"status": "healthy",
|
| 263 |
+
"service": "job-failure-prediction",
|
| 264 |
+
"models_loaded": {
|
| 265 |
+
"predictor": true,
|
| 266 |
+
"anomaly_detector": true
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
## Production Deployment
|
| 272 |
+
|
| 273 |
+
1. **Train models** on historical data
|
| 274 |
+
2. **Save models** to `models/` directory
|
| 275 |
+
3. **Build Docker image**: `docker build -t job-ml-service .`
|
| 276 |
+
4. **Deploy** using docker-compose or Kubernetes
|
| 277 |
+
5. **Configure n8n** workflows for monitoring
|
| 278 |
+
6. **Set up alerting** (Slack, PagerDuty, etc.)
|
| 279 |
+
|
| 280 |
+
## License
|
| 281 |
+
|
| 282 |
+
[Your License Here]
|
| 283 |
+
|
| 284 |
+
## Support
|
| 285 |
+
|
| 286 |
+
For issues or questions, please refer to the documentation or contact the development team.
|
README_HF_SPACE.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Job Failure Prediction & Anomaly Detection API
|
| 3 |
+
emoji: 🔮
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
sdk_version: latest
|
| 8 |
+
app_port: 7860
|
| 9 |
+
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# Job Failure Prediction & Anomaly Detection API
|
| 14 |
+
|
| 15 |
+
Production-ready ML service for predicting job failures and detecting anomalies in job execution data.
|
| 16 |
+
|
| 17 |
+
## 🚀 Quick Start
|
| 18 |
+
|
| 19 |
+
The API is automatically deployed and available at:
|
| 20 |
+
```
|
| 21 |
+
https://[your-space-name].hf.space
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## 📡 API Endpoints
|
| 25 |
+
|
| 26 |
+
### Health Check
|
| 27 |
+
```bash
|
| 28 |
+
GET /health
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
### Job Failure Prediction
|
| 32 |
+
```bash
|
| 33 |
+
POST /predict/job-fail
|
| 34 |
+
Content-Type: application/json
|
| 35 |
+
|
| 36 |
+
{
|
| 37 |
+
"zone": "prod",
|
| 38 |
+
"job_nm": "daily_export_customer",
|
| 39 |
+
"job_start_time": "2026-01-21T01:00:00",
|
| 40 |
+
"duration_sec": 5400,
|
| 41 |
+
"status": "SUCCESS",
|
| 42 |
+
"err_msg": "",
|
| 43 |
+
"explain": true
|
| 44 |
+
}
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
**Response:**
|
| 48 |
+
```json
|
| 49 |
+
{
|
| 50 |
+
"fail_probability": 0.79,
|
| 51 |
+
"risk_level": "MEDIUM",
|
| 52 |
+
"top_drivers": [
|
| 53 |
+
{
|
| 54 |
+
"feature": "failure_rate_7",
|
| 55 |
+
"shap_value": 0.30,
|
| 56 |
+
"effect": "increase"
|
| 57 |
+
}
|
| 58 |
+
],
|
| 59 |
+
"recommended_actions": [
|
| 60 |
+
"Monitor upstream dependencies and recent job history"
|
| 61 |
+
]
|
| 62 |
+
}
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### Anomaly Detection
|
| 66 |
+
```bash
|
| 67 |
+
POST /detect/anomaly
|
| 68 |
+
Content-Type: application/json
|
| 69 |
+
|
| 70 |
+
{
|
| 71 |
+
"features": {
|
| 72 |
+
"duration_sec": 5400,
|
| 73 |
+
"duration_zscore": 1.6,
|
| 74 |
+
"err_msg_len": 0
|
| 75 |
+
},
|
| 76 |
+
"threshold": 0.01
|
| 77 |
+
}
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
**Response:**
|
| 81 |
+
```json
|
| 82 |
+
{
|
| 83 |
+
"reconstruction_error": 0.0235,
|
| 84 |
+
"is_anomaly": true,
|
| 85 |
+
"threshold": 0.01,
|
| 86 |
+
"top_drivers": [
|
| 87 |
+
{
|
| 88 |
+
"feature": "duration_zscore",
|
| 89 |
+
"error": 0.0142
|
| 90 |
+
}
|
| 91 |
+
]
|
| 92 |
+
}
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## 📚 API Documentation
|
| 96 |
+
|
| 97 |
+
Interactive API documentation is available at:
|
| 98 |
+
- Swagger UI: `/docs`
|
| 99 |
+
- ReDoc: `/redoc`
|
| 100 |
+
|
| 101 |
+
## 🎯 Features
|
| 102 |
+
|
| 103 |
+
- **Job Failure Prediction**: XGBoost-based classifier with SHAP explainability
|
| 104 |
+
- **Anomaly Detection**: Autoencoder-based unsupervised anomaly detection
|
| 105 |
+
- **FastAPI REST API**: Production-ready endpoints
|
| 106 |
+
- **Docker Deployment**: Containerized for Hugging Face Spaces
|
| 107 |
+
|
| 108 |
+
## 📊 Risk Levels
|
| 109 |
+
|
| 110 |
+
- **MINIMAL**: `fail_probability < 0.3`
|
| 111 |
+
- **LOW**: `0.3 <= fail_probability < 0.5`
|
| 112 |
+
- **MEDIUM**: `0.5 <= fail_probability < 0.8`
|
| 113 |
+
- **CRITICAL**: `fail_probability >= 0.8`
|
| 114 |
+
|
| 115 |
+
## 🔧 Model Details
|
| 116 |
+
|
| 117 |
+
### Job Failure Prediction
|
| 118 |
+
- **Algorithm**: XGBoost Classifier
|
| 119 |
+
- **Preprocessing**: StandardScaler for numeric, OneHotEncoder for categorical
|
| 120 |
+
- **Explainability**: SHAP values for feature importance
|
| 121 |
+
|
| 122 |
+
### Anomaly Detection
|
| 123 |
+
- **Algorithm**: Autoencoder (TensorFlow/Keras)
|
| 124 |
+
- **Architecture**: Input → 64 → 32 → 64 → Output
|
| 125 |
+
- **Threshold**: Per-job 97th percentile or global threshold
|
| 126 |
+
|
| 127 |
+
## 📝 Example Usage
|
| 128 |
+
|
| 129 |
+
### Python
|
| 130 |
+
```python
|
| 131 |
+
import requests
|
| 132 |
+
|
| 133 |
+
url = "https://[your-space-name].hf.space/predict/job-fail"
|
| 134 |
+
response = requests.post(url, json={
|
| 135 |
+
"zone": "prod",
|
| 136 |
+
"job_nm": "daily_export",
|
| 137 |
+
"job_start_time": "2026-01-21T01:00:00",
|
| 138 |
+
"duration_sec": 5400,
|
| 139 |
+
"status": "SUCCESS",
|
| 140 |
+
"explain": True
|
| 141 |
+
})
|
| 142 |
+
print(response.json())
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### cURL
|
| 146 |
+
```bash
|
| 147 |
+
curl -X POST https://[your-space-name].hf.space/predict/job-fail \
|
| 148 |
+
-H "Content-Type: application/json" \
|
| 149 |
+
-d '{
|
| 150 |
+
"zone": "prod",
|
| 151 |
+
"job_nm": "daily_export",
|
| 152 |
+
"job_start_time": "2026-01-21T01:00:00",
|
| 153 |
+
"duration_sec": 5400,
|
| 154 |
+
"status": "SUCCESS"
|
| 155 |
+
}'
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
## 📖 Full Documentation
|
| 159 |
+
|
| 160 |
+
See the main [README.md](README.md) for complete documentation, training instructions, and local development setup.
|
| 161 |
+
|
| 162 |
+
## ⚙️ Configuration
|
| 163 |
+
|
| 164 |
+
Models are loaded from the `models/` directory. Ensure all required model files are committed to the repository:
|
| 165 |
+
- `job_fail_pipeline_cpu.joblib`
|
| 166 |
+
- `anomaly_autoencoder_cpu.keras`
|
| 167 |
+
- `anomaly_scaler.joblib`
|
| 168 |
+
- `feature_schema.json`
|
| 169 |
+
- `shap_background.npy`
|
| 170 |
+
- `anomaly_features.joblib`
|
| 171 |
+
- `anomaly_threshold.joblib`
|
| 172 |
+
|
| 173 |
+
## 🔒 Production Ready
|
| 174 |
+
|
| 175 |
+
- Health check endpoint for monitoring
|
| 176 |
+
- Error handling and validation
|
| 177 |
+
- CORS enabled for cross-origin requests
|
| 178 |
+
- Dockerized for consistent deployment
|
| 179 |
+
- Optimized for Hugging Face Spaces infrastructure
|
model_utils.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Model inference utilities with SHAP integration.
|
| 3 |
+
"""
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import joblib
|
| 7 |
+
import shap
|
| 8 |
+
from typing import Dict, List, Optional, Tuple
|
| 9 |
+
import os
|
| 10 |
+
from preprocessing import align_schema_df, get_feature_columns
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class JobFailurePredictor:
|
| 14 |
+
"""Wrapper for job failure prediction model with SHAP explainability."""
|
| 15 |
+
|
| 16 |
+
def __init__(self, model_path: str = None,
|
| 17 |
+
background_path: str = None,
|
| 18 |
+
schema_path: str = None):
|
| 19 |
+
"""
|
| 20 |
+
Initialize predictor.
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
model_path: Path to saved pipeline (defaults to env var or 'models/job_fail_pipeline_cpu.joblib')
|
| 24 |
+
background_path: Path to SHAP background sample (defaults to env var or 'models/shap_background.npy')
|
| 25 |
+
schema_path: Path to feature schema (defaults to env var or 'models/feature_schema.json')
|
| 26 |
+
"""
|
| 27 |
+
# Support environment variables for flexible deployment
|
| 28 |
+
model_base = os.getenv('MODEL_BASE_PATH', 'models')
|
| 29 |
+
|
| 30 |
+
self.model_path = model_path or os.getenv(
|
| 31 |
+
'JOB_FAIL_MODEL_PATH',
|
| 32 |
+
f'{model_base}/job_fail_pipeline_cpu.joblib'
|
| 33 |
+
)
|
| 34 |
+
self.background_path = background_path or os.getenv(
|
| 35 |
+
'SHAP_BACKGROUND_PATH',
|
| 36 |
+
f'{model_base}/shap_background.npy'
|
| 37 |
+
)
|
| 38 |
+
self.schema_path = schema_path or os.getenv(
|
| 39 |
+
'FEATURE_SCHEMA_PATH',
|
| 40 |
+
f'{model_base}/feature_schema.json'
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
if os.path.exists(model_path):
|
| 44 |
+
self.pipeline = joblib.load(model_path)
|
| 45 |
+
print(f"Loaded model from {model_path}")
|
| 46 |
+
else:
|
| 47 |
+
self.pipeline = None
|
| 48 |
+
print(f"Warning: Model not found at {model_path}")
|
| 49 |
+
|
| 50 |
+
self.explainer = None
|
| 51 |
+
self.background = None
|
| 52 |
+
self._load_shap_background()
|
| 53 |
+
|
| 54 |
+
def _load_shap_background(self):
|
| 55 |
+
"""Load SHAP background sample if available."""
|
| 56 |
+
if os.path.exists(self.background_path):
|
| 57 |
+
try:
|
| 58 |
+
self.background = np.load(self.background_path)
|
| 59 |
+
# Use generic Explainer for compatibility
|
| 60 |
+
if self.pipeline is not None:
|
| 61 |
+
self.explainer = shap.Explainer(
|
| 62 |
+
self.pipeline.predict_proba,
|
| 63 |
+
self.background,
|
| 64 |
+
feature_names=self._get_feature_names()
|
| 65 |
+
)
|
| 66 |
+
print(f"Loaded SHAP background from {self.background_path}")
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"Warning: Could not load SHAP background: {e}")
|
| 69 |
+
self.explainer = None
|
| 70 |
+
|
| 71 |
+
def _get_feature_names(self) -> List[str]:
|
| 72 |
+
"""Get feature names from pipeline."""
|
| 73 |
+
if self.pipeline is None:
|
| 74 |
+
return []
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
preprocess = self.pipeline.named_steps['preprocess']
|
| 78 |
+
num_cols = get_feature_columns()['numeric']
|
| 79 |
+
cat_cols = get_feature_columns()['categorical']
|
| 80 |
+
|
| 81 |
+
# Get one-hot encoded names
|
| 82 |
+
cat_encoder = preprocess.named_transformers_['cat']
|
| 83 |
+
if hasattr(cat_encoder, 'get_feature_names_out'):
|
| 84 |
+
cat_names = cat_encoder.get_feature_names_out(cat_cols).tolist()
|
| 85 |
+
else:
|
| 86 |
+
cat_names = [f"cat_{i}" for i in range(len(cat_cols))]
|
| 87 |
+
|
| 88 |
+
return num_cols + cat_names
|
| 89 |
+
except Exception:
|
| 90 |
+
return []
|
| 91 |
+
|
| 92 |
+
def predict(self, data: pd.DataFrame, explain: bool = False) -> Dict:
|
| 93 |
+
"""
|
| 94 |
+
Predict job failure probability.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
data: DataFrame with job features
|
| 98 |
+
explain: Whether to compute SHAP explanations
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
Dictionary with predictions and optional explanations
|
| 102 |
+
"""
|
| 103 |
+
if self.pipeline is None:
|
| 104 |
+
return {
|
| 105 |
+
'fail_probability': 0.5,
|
| 106 |
+
'risk_level': 'UNKNOWN',
|
| 107 |
+
'error': 'Model not loaded'
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
try:
|
| 111 |
+
# Align to schema
|
| 112 |
+
data = align_schema_df(data, self.schema_path)
|
| 113 |
+
|
| 114 |
+
# Get feature columns
|
| 115 |
+
feature_cols = get_feature_columns()
|
| 116 |
+
num_cols = feature_cols['numeric']
|
| 117 |
+
cat_cols = feature_cols['categorical']
|
| 118 |
+
|
| 119 |
+
X = data[num_cols + cat_cols].copy()
|
| 120 |
+
|
| 121 |
+
# Predict
|
| 122 |
+
proba = self.pipeline.predict_proba(X)[:, 1]
|
| 123 |
+
fail_prob = float(proba[0]) if len(proba) == 1 else float(proba.mean())
|
| 124 |
+
|
| 125 |
+
# Determine risk level
|
| 126 |
+
if fail_prob >= 0.8:
|
| 127 |
+
risk_level = 'CRITICAL'
|
| 128 |
+
elif fail_prob >= 0.5:
|
| 129 |
+
risk_level = 'MEDIUM'
|
| 130 |
+
elif fail_prob >= 0.3:
|
| 131 |
+
risk_level = 'LOW'
|
| 132 |
+
else:
|
| 133 |
+
risk_level = 'MINIMAL'
|
| 134 |
+
|
| 135 |
+
result = {
|
| 136 |
+
'fail_probability': fail_prob,
|
| 137 |
+
'risk_level': risk_level
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
# Add SHAP explanation if requested
|
| 141 |
+
if explain and self.explainer is not None:
|
| 142 |
+
try:
|
| 143 |
+
# Preprocess to get encoded features
|
| 144 |
+
preprocess = self.pipeline.named_steps['preprocess']
|
| 145 |
+
X_encoded = preprocess.transform(X)
|
| 146 |
+
|
| 147 |
+
# Compute SHAP values
|
| 148 |
+
shap_values = self.explainer(X_encoded)
|
| 149 |
+
|
| 150 |
+
# Handle different SHAP output shapes
|
| 151 |
+
if hasattr(shap_values, 'values'):
|
| 152 |
+
sv = shap_values.values
|
| 153 |
+
if len(sv.shape) == 3: # (n_samples, n_features, n_classes)
|
| 154 |
+
sv = sv[:, :, 1] # Take positive class
|
| 155 |
+
elif len(sv.shape) == 2:
|
| 156 |
+
sv = sv
|
| 157 |
+
else:
|
| 158 |
+
sv = sv.flatten()
|
| 159 |
+
else:
|
| 160 |
+
sv = shap_values
|
| 161 |
+
|
| 162 |
+
# Get feature names
|
| 163 |
+
feature_names = self._get_feature_names()
|
| 164 |
+
if len(sv.shape) == 1:
|
| 165 |
+
sv = sv.reshape(1, -1)
|
| 166 |
+
|
| 167 |
+
# Get top drivers (absolute values)
|
| 168 |
+
if len(sv) > 0:
|
| 169 |
+
abs_sv = np.abs(sv[0])
|
| 170 |
+
top_indices = np.argsort(abs_sv)[::-1][:5]
|
| 171 |
+
|
| 172 |
+
top_drivers = []
|
| 173 |
+
for idx in top_indices:
|
| 174 |
+
if idx < len(feature_names):
|
| 175 |
+
feature_name = feature_names[idx]
|
| 176 |
+
shap_val = float(sv[0, idx])
|
| 177 |
+
top_drivers.append({
|
| 178 |
+
'feature': feature_name,
|
| 179 |
+
'shap_value': shap_val,
|
| 180 |
+
'effect': 'increase' if shap_val > 0 else 'decrease'
|
| 181 |
+
})
|
| 182 |
+
|
| 183 |
+
result['top_drivers'] = top_drivers
|
| 184 |
+
|
| 185 |
+
# Generate recommended actions
|
| 186 |
+
result['recommended_actions'] = self._generate_actions(
|
| 187 |
+
top_drivers, fail_prob
|
| 188 |
+
)
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"Warning: SHAP explanation failed: {e}")
|
| 191 |
+
result['top_drivers'] = []
|
| 192 |
+
|
| 193 |
+
return result
|
| 194 |
+
|
| 195 |
+
except Exception as e:
|
| 196 |
+
return {
|
| 197 |
+
'fail_probability': 0.5,
|
| 198 |
+
'risk_level': 'UNKNOWN',
|
| 199 |
+
'error': str(e)
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
def _generate_actions(self, top_drivers: List[Dict], fail_prob: float) -> List[str]:
|
| 203 |
+
"""Generate recommended actions based on top drivers."""
|
| 204 |
+
actions = []
|
| 205 |
+
|
| 206 |
+
for driver in top_drivers[:3]:
|
| 207 |
+
feature = driver['feature']
|
| 208 |
+
effect = driver['effect']
|
| 209 |
+
|
| 210 |
+
if 'failure_rate' in feature:
|
| 211 |
+
actions.append("Monitor upstream dependencies and recent job history")
|
| 212 |
+
elif 'duration' in feature:
|
| 213 |
+
if effect == 'increase':
|
| 214 |
+
actions.append("Check for resource constraints or data volume spikes")
|
| 215 |
+
else:
|
| 216 |
+
actions.append("Verify job completed successfully (unusually fast)")
|
| 217 |
+
elif 'err_msg' in feature:
|
| 218 |
+
actions.append("Review error logs and investigate root cause")
|
| 219 |
+
elif 'job_nm' in feature or 'tasksgroup' in feature:
|
| 220 |
+
actions.append("Check job configuration and dependencies")
|
| 221 |
+
|
| 222 |
+
if fail_prob >= 0.8:
|
| 223 |
+
actions.append("Consider immediate intervention or rerun")
|
| 224 |
+
elif fail_prob >= 0.5:
|
| 225 |
+
actions.append("Increase monitoring frequency")
|
| 226 |
+
|
| 227 |
+
# Deduplicate
|
| 228 |
+
return list(dict.fromkeys(actions))[:5]
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class AnomalyDetector:
|
| 232 |
+
"""Wrapper for anomaly detection model."""
|
| 233 |
+
|
| 234 |
+
def __init__(self, model_path: str = None,
|
| 235 |
+
scaler_path: str = None,
|
| 236 |
+
feature_path: str = None,
|
| 237 |
+
threshold_path: str = None):
|
| 238 |
+
"""
|
| 239 |
+
Initialize anomaly detector.
|
| 240 |
+
|
| 241 |
+
Args:
|
| 242 |
+
model_path: Path to saved autoencoder (defaults to env var or 'models/anomaly_autoencoder_cpu.keras')
|
| 243 |
+
scaler_path: Path to saved scaler (defaults to env var or 'models/anomaly_scaler.joblib')
|
| 244 |
+
feature_path: Path to saved feature list (defaults to env var or 'models/anomaly_features.joblib')
|
| 245 |
+
threshold_path: Path to saved threshold (defaults to env var or 'models/anomaly_threshold.joblib')
|
| 246 |
+
"""
|
| 247 |
+
# Support environment variables for flexible deployment
|
| 248 |
+
model_base = os.getenv('MODEL_BASE_PATH', 'models')
|
| 249 |
+
|
| 250 |
+
self.model_path = model_path or os.getenv(
|
| 251 |
+
'ANOMALY_MODEL_PATH',
|
| 252 |
+
f'{model_base}/anomaly_autoencoder_cpu.keras'
|
| 253 |
+
)
|
| 254 |
+
self.scaler_path = scaler_path or os.getenv(
|
| 255 |
+
'ANOMALY_SCALER_PATH',
|
| 256 |
+
f'{model_base}/anomaly_scaler.joblib'
|
| 257 |
+
)
|
| 258 |
+
self.feature_path = feature_path or os.getenv(
|
| 259 |
+
'ANOMALY_FEATURE_PATH',
|
| 260 |
+
f'{model_base}/anomaly_features.joblib'
|
| 261 |
+
)
|
| 262 |
+
self.threshold_path = threshold_path or os.getenv(
|
| 263 |
+
'ANOMALY_THRESHOLD_PATH',
|
| 264 |
+
f'{model_base}/anomaly_threshold.joblib'
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
if os.path.exists(model_path):
|
| 268 |
+
from tensorflow import keras
|
| 269 |
+
self.model = keras.models.load_model(model_path)
|
| 270 |
+
print(f"Loaded autoencoder from {model_path}")
|
| 271 |
+
else:
|
| 272 |
+
self.model = None
|
| 273 |
+
print(f"Warning: Model not found at {model_path}")
|
| 274 |
+
|
| 275 |
+
if os.path.exists(scaler_path):
|
| 276 |
+
self.scaler = joblib.load(scaler_path)
|
| 277 |
+
print(f"Loaded scaler from {scaler_path}")
|
| 278 |
+
else:
|
| 279 |
+
self.scaler = None
|
| 280 |
+
|
| 281 |
+
if os.path.exists(feature_path):
|
| 282 |
+
self.feature_list = joblib.load(feature_path)
|
| 283 |
+
print(f"Loaded feature list from {feature_path}")
|
| 284 |
+
else:
|
| 285 |
+
self.feature_list = get_feature_columns()['anomaly_numeric']
|
| 286 |
+
|
| 287 |
+
if os.path.exists(threshold_path):
|
| 288 |
+
self.global_threshold = joblib.load(threshold_path)
|
| 289 |
+
print(f"Loaded threshold from {threshold_path}")
|
| 290 |
+
else:
|
| 291 |
+
self.global_threshold = 0.01
|
| 292 |
+
|
| 293 |
+
def detect(self, features: Dict, threshold: Optional[float] = None) -> Dict:
|
| 294 |
+
"""
|
| 295 |
+
Detect anomaly in feature vector.
|
| 296 |
+
|
| 297 |
+
Args:
|
| 298 |
+
features: Dictionary of feature values
|
| 299 |
+
threshold: Optional custom threshold (overrides default)
|
| 300 |
+
|
| 301 |
+
Returns:
|
| 302 |
+
Dictionary with anomaly detection results
|
| 303 |
+
"""
|
| 304 |
+
if self.model is None or self.scaler is None:
|
| 305 |
+
return {
|
| 306 |
+
'reconstruction_error': 0.0,
|
| 307 |
+
'is_anomaly': False,
|
| 308 |
+
'error': 'Model not loaded'
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
try:
|
| 312 |
+
# Build feature vector
|
| 313 |
+
feature_vec = []
|
| 314 |
+
for feat in self.feature_list:
|
| 315 |
+
value = features.get(feat, 0.0)
|
| 316 |
+
try:
|
| 317 |
+
feature_vec.append(float(value))
|
| 318 |
+
except (ValueError, TypeError):
|
| 319 |
+
feature_vec.append(0.0)
|
| 320 |
+
|
| 321 |
+
feature_vec = np.array(feature_vec).reshape(1, -1)
|
| 322 |
+
|
| 323 |
+
# Scale
|
| 324 |
+
feature_scaled = self.scaler.transform(feature_vec)
|
| 325 |
+
|
| 326 |
+
# Reconstruct
|
| 327 |
+
reconstructed = self.model.predict(feature_scaled, verbose=0)
|
| 328 |
+
|
| 329 |
+
# Compute reconstruction error
|
| 330 |
+
recon_error = np.mean((feature_scaled - reconstructed) ** 2)
|
| 331 |
+
|
| 332 |
+
# Determine if anomaly
|
| 333 |
+
thresh = threshold if threshold is not None else self.global_threshold
|
| 334 |
+
is_anomaly = recon_error > thresh
|
| 335 |
+
|
| 336 |
+
# Compute per-feature errors for top drivers
|
| 337 |
+
per_feature_errors = np.square(feature_scaled - reconstructed).flatten()
|
| 338 |
+
top_indices = np.argsort(per_feature_errors)[::-1][:3]
|
| 339 |
+
|
| 340 |
+
top_drivers = []
|
| 341 |
+
for idx in top_indices:
|
| 342 |
+
if idx < len(self.feature_list):
|
| 343 |
+
top_drivers.append({
|
| 344 |
+
'feature': self.feature_list[idx],
|
| 345 |
+
'error': float(per_feature_errors[idx])
|
| 346 |
+
})
|
| 347 |
+
|
| 348 |
+
return {
|
| 349 |
+
'reconstruction_error': float(recon_error),
|
| 350 |
+
'is_anomaly': bool(is_anomaly),
|
| 351 |
+
'threshold': float(thresh),
|
| 352 |
+
'top_drivers': top_drivers
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
except Exception as e:
|
| 356 |
+
return {
|
| 357 |
+
'reconstruction_error': 0.0,
|
| 358 |
+
'is_anomaly': False,
|
| 359 |
+
'error': str(e)
|
| 360 |
+
}
|
preprocessing.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data preprocessing and feature engineering utilities for job failure prediction.
|
| 3 |
+
"""
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import numpy as np
|
| 6 |
+
from typing import Dict, List, Optional
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def parse_duration(x) -> float:
|
| 11 |
+
"""Parse duration string (HH:MM:SS) or numeric to seconds."""
|
| 12 |
+
if pd.isna(x):
|
| 13 |
+
return 0.0
|
| 14 |
+
try:
|
| 15 |
+
if isinstance(x, str):
|
| 16 |
+
parts = x.split(':')
|
| 17 |
+
if len(parts) == 3:
|
| 18 |
+
h, m, s = map(int, parts)
|
| 19 |
+
return h * 3600 + m * 60 + s
|
| 20 |
+
return float(x)
|
| 21 |
+
return float(x)
|
| 22 |
+
except (ValueError, AttributeError):
|
| 23 |
+
return 0.0
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def engineer_features(df: pd.DataFrame) -> pd.DataFrame:
|
| 27 |
+
"""
|
| 28 |
+
Engineer features from raw job data.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
df: DataFrame with columns: zone, job_nm, tasksgroup_nm, round_time,
|
| 32 |
+
job_start_time, job_end_time, duration, status, err_msg, zeppelin,
|
| 33 |
+
ictrl_dt, start_ictrl_dt, end_ictrl_dt
|
| 34 |
+
|
| 35 |
+
Returns:
|
| 36 |
+
DataFrame with engineered features
|
| 37 |
+
"""
|
| 38 |
+
df = df.copy()
|
| 39 |
+
|
| 40 |
+
# Parse timestamps
|
| 41 |
+
df['job_start_time'] = pd.to_datetime(df['job_start_time'], errors='coerce')
|
| 42 |
+
df['job_end_time'] = pd.to_datetime(df['job_end_time'], errors='coerce')
|
| 43 |
+
|
| 44 |
+
# Parse duration to seconds
|
| 45 |
+
if 'duration' in df.columns:
|
| 46 |
+
df['duration_sec'] = df['duration'].apply(parse_duration)
|
| 47 |
+
else:
|
| 48 |
+
df['duration_sec'] = 0.0
|
| 49 |
+
|
| 50 |
+
df['duration_sec'] = df['duration_sec'].fillna(0.0)
|
| 51 |
+
|
| 52 |
+
# Ground truth label
|
| 53 |
+
# Handle various success statuses: 'SUCCESS', 'SUCCEED', 'SUCCEEDED'
|
| 54 |
+
# Everything else (FAILED, ABORT-AUTO, RUNNING, etc.) is considered a failure
|
| 55 |
+
status_upper = df['status'].fillna('').str.upper()
|
| 56 |
+
success_statuses = ['SUCCESS', 'SUCCEED', 'SUCCEEDED']
|
| 57 |
+
df['is_failed'] = (~status_upper.isin(success_statuses)).astype(int)
|
| 58 |
+
|
| 59 |
+
# Time features
|
| 60 |
+
df['run_hour'] = df['job_start_time'].dt.hour.fillna(0).astype(int)
|
| 61 |
+
df['run_dow'] = df['job_start_time'].dt.dayofweek.fillna(0).astype(int) # 0=Mon, 6=Sun
|
| 62 |
+
df['is_weekend'] = (df['run_dow'] >= 5).astype(int)
|
| 63 |
+
|
| 64 |
+
# Cyclical encoding for hour
|
| 65 |
+
df['hour_sin'] = np.sin(2 * np.pi * df['run_hour'] / 24)
|
| 66 |
+
df['hour_cos'] = np.cos(2 * np.pi * df['run_hour'] / 24)
|
| 67 |
+
|
| 68 |
+
# Error message features
|
| 69 |
+
df['err_msg_len'] = df['err_msg'].fillna('').str.len()
|
| 70 |
+
df['has_err_msg'] = (df['err_msg_len'] > 0).astype(int)
|
| 71 |
+
|
| 72 |
+
# Zeppelin flag
|
| 73 |
+
df['is_zeppelin'] = df['zeppelin'].notna().astype(int)
|
| 74 |
+
|
| 75 |
+
# Job-level rolling statistics (per job_nm)
|
| 76 |
+
df = df.sort_values(['job_nm', 'job_start_time']).reset_index(drop=True)
|
| 77 |
+
|
| 78 |
+
df['failure_rate_7'] = df.groupby('job_nm')['is_failed'].transform(
|
| 79 |
+
lambda s: s.rolling(7, min_periods=1).mean()
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
df['avg_duration_7'] = df.groupby('job_nm')['duration_sec'].transform(
|
| 83 |
+
lambda s: s.rolling(7, min_periods=1).mean()
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# Duration z-score (relative to rolling average)
|
| 87 |
+
df['duration_zscore'] = (
|
| 88 |
+
(df['duration_sec'] - df['avg_duration_7']) /
|
| 89 |
+
df['avg_duration_7'].replace(0, 1)
|
| 90 |
+
)
|
| 91 |
+
df['duration_zscore'] = df['duration_zscore'].fillna(0.0)
|
| 92 |
+
|
| 93 |
+
return df
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def get_feature_columns() -> Dict[str, List[str]]:
|
| 97 |
+
"""Return feature column definitions."""
|
| 98 |
+
return {
|
| 99 |
+
'numeric': [
|
| 100 |
+
'duration_sec',
|
| 101 |
+
'duration_zscore',
|
| 102 |
+
'avg_duration_7',
|
| 103 |
+
'failure_rate_7',
|
| 104 |
+
'err_msg_len',
|
| 105 |
+
'hour_sin',
|
| 106 |
+
'hour_cos'
|
| 107 |
+
],
|
| 108 |
+
'categorical': [
|
| 109 |
+
'job_nm',
|
| 110 |
+
'tasksgroup_nm',
|
| 111 |
+
'zone',
|
| 112 |
+
'is_zeppelin',
|
| 113 |
+
'is_weekend'
|
| 114 |
+
],
|
| 115 |
+
'anomaly_numeric': [
|
| 116 |
+
'duration_sec',
|
| 117 |
+
'duration_zscore',
|
| 118 |
+
'avg_duration_7',
|
| 119 |
+
'failure_rate_7',
|
| 120 |
+
'err_msg_len',
|
| 121 |
+
'hour_sin',
|
| 122 |
+
'hour_cos'
|
| 123 |
+
]
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def save_feature_schema(output_path: str = 'models/feature_schema.json'):
|
| 128 |
+
"""Save feature schema to JSON file."""
|
| 129 |
+
import os
|
| 130 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 131 |
+
|
| 132 |
+
schema = {
|
| 133 |
+
'feature_columns': get_feature_columns(),
|
| 134 |
+
'required_fields': [
|
| 135 |
+
'zone', 'job_nm', 'tasksgroup_nm', 'job_start_time',
|
| 136 |
+
'duration', 'status', 'err_msg', 'zeppelin'
|
| 137 |
+
]
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
with open(output_path, 'w') as f:
|
| 141 |
+
json.dump(schema, f, indent=2)
|
| 142 |
+
|
| 143 |
+
return schema
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def align_schema_df(df: pd.DataFrame, schema_path: str = 'models/feature_schema.json') -> pd.DataFrame:
|
| 147 |
+
"""
|
| 148 |
+
Align DataFrame to feature schema, filling missing columns with defaults.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
df: Input DataFrame
|
| 152 |
+
schema_path: Path to feature schema JSON
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
Aligned DataFrame
|
| 156 |
+
"""
|
| 157 |
+
try:
|
| 158 |
+
with open(schema_path, 'r') as f:
|
| 159 |
+
schema = json.load(f)
|
| 160 |
+
except FileNotFoundError:
|
| 161 |
+
# If schema doesn't exist, engineer features and create it
|
| 162 |
+
df = engineer_features(df)
|
| 163 |
+
schema = save_feature_schema(schema_path)
|
| 164 |
+
|
| 165 |
+
# Ensure all required numeric and categorical columns exist
|
| 166 |
+
all_features = schema['feature_columns']['numeric'] + schema['feature_columns']['categorical']
|
| 167 |
+
|
| 168 |
+
for col in all_features:
|
| 169 |
+
if col not in df.columns:
|
| 170 |
+
if col in schema['feature_columns']['numeric']:
|
| 171 |
+
df[col] = 0.0
|
| 172 |
+
else:
|
| 173 |
+
df[col] = ''
|
| 174 |
+
|
| 175 |
+
return df
|