from __future__ import annotations import math from typing import Any def score_rule(row: dict[str, Any]) -> dict[str, Any]: """Turn alert history into a conservative tuning recommendation. This module never disables signatures automatically. It only proposes thresholding when observed noise is high and incident usefulness is low. """ item = dict(row) hits = max(0, int(item.get("hits") or 0)) rows = max(0, int(item.get("rows") or 0)) unique_src = max(0, int(item.get("unique_src") or 0)) unique_dst = max(0, int(item.get("unique_dst") or 0)) incidents = max(0, int(item.get("incidents") or 0)) blocked = max(0, int(item.get("blocked") or 0)) severity = max(1, min(4, int(item.get("severity") or 4))) incident_ratio = incidents / rows if rows else 0.0 duplicate_ratio = max(0.0, min(1.0, (hits - rows) / hits)) if hits else 0.0 concentration = hits / max(1, unique_src + unique_dst) raw = min(48.0, math.log10(hits + 1) * 16.0) raw += duplicate_ratio * 28.0 raw += min(18.0, math.log2(concentration + 1) * 4.0) raw -= min(32.0, incident_ratio * 55.0) raw -= 10.0 if severity == 1 else 4.0 if severity == 2 else 0.0 raw -= 8.0 if blocked else 0.0 noise_score = max(0, min(100, round(raw))) recommendation = "keep" reason = "Useful/low-volume signature" proposed = None if hits >= 100 and noise_score >= 70 and incidents == 0: recommendation = "limit" count = 1 if hits >= 1000 else 3 if hits >= 300 else 5 seconds = 60 if hits >= 300 else 120 proposed = {"type": "limit", "track": "by_src", "count": count, "seconds": seconds} reason = "High alert volume with no incident correlation" elif hits >= 40 and noise_score >= 55 and incident_ratio < 0.05: recommendation = "review" reason = "Repeated signature with low incident correlation" elif incident_ratio >= 0.25 or severity == 1: recommendation = "keep" reason = "High-value or frequently incident-correlated signature" item.update({ "noise_score": noise_score, "incident_ratio": round(incident_ratio, 4), "duplicate_ratio": round(duplicate_ratio, 4), "recommendation": recommendation, "recommendation_reason": reason, "proposed_threshold": proposed, }) return item