File size: 1,857 Bytes
f0fae3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
anomaly_model.py
-----------------
Isolation Forest based anomaly detector for conveyor / crane motor sensor
streams (motor temperature, vibration, current draw, belt speed). This
powers the "Predictive Maintenance" tab -- flags abnormal equipment
behaviour before it causes an unplanned stoppage, which is exactly the kind
of workload Daifuku's intralogistics platforms (e.g. AS/RS, sorters, AGVs)
generate continuously in production.
"""

from dataclasses import dataclass

import joblib
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler

FEATURES = ["motor_temp_c", "vibration_mm_s", "current_amps", "belt_speed_mps"]


@dataclass
class AnomalyResult:
    is_anomaly: bool
    anomaly_score: float  # higher = more anomalous, roughly in [0, 1]
    raw_score: float


def build_model(contamination: float = 0.1, seed: int = 42) -> IsolationForest:
    return IsolationForest(
        n_estimators=200,
        contamination=contamination,
        random_state=seed,
    )


def score_reading(model: IsolationForest, scaler: StandardScaler, reading: dict) -> AnomalyResult:
    x = np.array([[reading[f] for f in FEATURES]])
    x_scaled = scaler.transform(x)
    raw = model.decision_function(x_scaled)[0]  # higher = more normal
    pred = model.predict(x_scaled)[0]  # 1 = normal, -1 = anomaly
    # squash raw decision_function (~[-0.5, 0.5]) into a 0-1 "anomaly score"
    anomaly_score = float(np.clip(0.5 - raw, 0, 1))
    return AnomalyResult(is_anomaly=(pred == -1), anomaly_score=anomaly_score, raw_score=float(raw))


def save_artifacts(model, scaler, model_path: str, scaler_path: str):
    joblib.dump(model, model_path)
    joblib.dump(scaler, scaler_path)


def load_artifacts(model_path: str, scaler_path: str):
    return joblib.load(model_path), joblib.load(scaler_path)