feat: add 5 deterministic rules (IMU outliers/watchdog, USBL SNR/spike, battery_low)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
src/cosma_log_analyzer/rules/usbl_snr_low.py
Normal file
52
src/cosma_log_analyzer/rules/usbl_snr_low.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from ..ingest import TOPIC_USBL
|
||||
from ..models import Anomaly
|
||||
from .base import Rule
|
||||
|
||||
|
||||
class UsblSnrLowRule(Rule):
|
||||
"""Fire when SNR stays below threshold for `consec` consecutive samples."""
|
||||
|
||||
name = "usbl_snr_low"
|
||||
topic = TOPIC_USBL
|
||||
severity = "warn"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
subject: str = "AUV000",
|
||||
min_snr_db: float | None = None,
|
||||
consec: int = 3,
|
||||
) -> None:
|
||||
super().__init__(subject)
|
||||
if min_snr_db is None:
|
||||
min_snr_db = float(os.environ.get("USBL_SNR_LOW", 5.0))
|
||||
self.min_snr_db = min_snr_db
|
||||
self.consec = consec
|
||||
|
||||
def detect(self, df: pd.DataFrame) -> list[Anomaly]:
|
||||
if df.empty or len(df) < self.consec:
|
||||
return []
|
||||
low = df["snr_db"] < self.min_snr_db
|
||||
run = low.astype(int).groupby((~low).cumsum()).cumsum()
|
||||
anomalies: list[Anomaly] = []
|
||||
fired_run = -1
|
||||
runs_id = (~low).cumsum()
|
||||
for idx in df.index:
|
||||
if run.iloc[idx] >= self.consec and runs_id.iloc[idx] != fired_run:
|
||||
fired_run = int(runs_id.iloc[idx])
|
||||
anomalies.append(
|
||||
self._make(
|
||||
ts=float(df.at[idx, "ts"]),
|
||||
value=float(df.at[idx, "snr_db"]),
|
||||
context={
|
||||
"min_snr_db": self.min_snr_db,
|
||||
"consec": self.consec,
|
||||
},
|
||||
)
|
||||
)
|
||||
return anomalies
|
||||
Reference in New Issue
Block a user