53 lines
1.5 KiB
Python
53 lines
1.5 KiB
Python
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
|