from __future__ import annotations from dataclasses import dataclass, field from .normalization import canonical_value @dataclass(frozen=True) class LogEvent: raw: str fields: dict[str, str] @property def src_ip(self) -> str | None: return canonical_value(self.fields, "srcip") or None @property def dst_ip(self) -> str | None: return canonical_value(self.fields, "dstip") or None @property def subtype(self) -> str: return canonical_value(self.fields, "subtype").lower() @property def action(self) -> str: return canonical_value(self.fields, "action").lower() @property def severity(self) -> str: return canonical_value(self.fields, "severity").lower() @dataclass(frozen=True) class PolicyRule: policy_id: str settings: dict[str, list[str]] = field(default_factory=dict) def values(self, key: str) -> list[str]: return self.settings.get(key, []) def first(self, key: str, default: str = "") -> str: values = self.values(key) return values[0] if values else default @dataclass(frozen=True) class Finding: severity: str title: str detail: str reference: str | None = None @dataclass(frozen=True) class BlockCandidate: src_ip: str score: int reasons: list[str] sample_events: list[LogEvent] @dataclass(frozen=True) class AnomalyFinding: subject: str score: int severity: str confidence: str reasons: list[str] evidence: dict[str, int | str | float] @dataclass(frozen=True) class Recommendation: subject: str score: int severity: str title: str recommendation: str reasons: list[str] related_policy_ids: list[str] = field(default_factory=list) related_services: list[str] = field(default_factory=list)