"""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 ]