9cd3170e8a
- Add stability detection and threshold classification features with corresponding tests
42 lines
1.3 KiB
Python
42 lines
1.3 KiB
Python
"""Classify sensor values into three bands around (target, margin)
|
||
|
||
Auto mode derives target=mean, margin=1
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
import math
|
||
from dataclasses import dataclass
|
||
|
||
BAND_IN = "in_range"
|
||
BAND_HIGH = "high"
|
||
BAND_LOW = "low"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ThresholdConfig:
|
||
set_point: float = 149.0 # process target: used as band TARGET when auto=False
|
||
margin: float = 1.0 # used as band MARGIN when auto=False
|
||
auto: bool = True # auto=True: target=frame mean, margin=frame 1σ
|
||
|
||
def resolve_bounds(values: list[float], cfg: ThresholdConfig) -> tuple[float, float]:
|
||
if not cfg.auto:
|
||
return cfg.set_point, cfg.margin
|
||
clean = [v for v in values if not math.isnan(v)]
|
||
if not clean:
|
||
return cfg.set_point, cfg.margin
|
||
mean = sum(clean) / len(clean)
|
||
variance = sum((v - mean) ** 2 for v in clean) / len(clean)
|
||
return mean, math.sqrt(variance)
|
||
|
||
def classify(value: float, target: float, margin: float) -> str:
|
||
if math.isnan(value):
|
||
return BAND_IN
|
||
if value > target + margin:
|
||
return BAND_HIGH
|
||
if value < target - margin:
|
||
return BAND_LOW
|
||
return BAND_IN
|
||
|
||
def classify_all(values: list[float], cfg: ThresholdConfig) -> list[str]:
|
||
target, margin = resolve_bounds(values, cfg)
|
||
return [classify(v, target, margin)for v in values ] |