From 08d1c43799c66cd0206a570d7935bf9266ddf97c79d13c7af44866ed22297023 Mon Sep 17 00:00:00 2001 From: larssand Date: Mon, 22 Jun 2026 22:21:02 +0200 Subject: [PATCH] Implemented the feedback foundation. --- src/fgai/feedback.py | 27 +++++++++++++++++++++++++++ src/fgai/llm.py | 1 + src/fgai/monitor.py | 10 ++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/fgai/feedback.py diff --git a/src/fgai/feedback.py b/src/fgai/feedback.py new file mode 100644 index 0000000..3c02283 --- /dev/null +++ b/src/fgai/feedback.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import json +import time +from pathlib import Path + + +class FeedbackStore: + def __init__(self, path: str = "state/signalscope-feedback.json") -> None: + self.path = Path(path) + + def entries(self) -> list[dict[str, object]]: + try: + items = json.loads(self.path.read_text(encoding="utf-8")) + except (FileNotFoundError, json.JSONDecodeError): + items = [] + now = int(time.time()) + return [item for item in items if not item.get("expires_at") or int(item["expires_at"]) > now] + + def add(self, item: dict[str, object]) -> dict[str, object]: + status = str(item.get("status", "")).lower() + if status not in {"false_positive", "expected", "confirmed"}: + raise ValueError("invalid feedback status") + entry = {"stream_id": str(item.get("stream_id", "")), "entity": str(item.get("entity", "")), "field": str(item.get("field", "")), "status": status, "note": str(item.get("note", "")), "created_at": int(time.time()), "expires_at": int(item.get("expires_at", 0) or 0)} + entries = [value for value in self.entries() if (value.get("stream_id"), value.get("entity"), value.get("field")) != (entry["stream_id"], entry["entity"], entry["field"])] + entries.append(entry); self.path.parent.mkdir(parents=True, exist_ok=True); self.path.write_text(json.dumps(entries, indent=2), encoding="utf-8") + return entry diff --git a/src/fgai/llm.py b/src/fgai/llm.py index 211800c..d51026b 100644 --- a/src/fgai/llm.py +++ b/src/fgai/llm.py @@ -64,6 +64,7 @@ def ollama_dashboard_assessment(analysis: dict[str, object], model: str | None = "capabilities": analysis.get("capabilities", {}), "cross_source_correlations": analysis.get("cross_source_correlations", [])[:20], "field_deviations": analysis.get("field_deviations", {}), + "feedback": analysis.get("feedback", []), } return ollama_summary( [], diff --git a/src/fgai/monitor.py b/src/fgai/monitor.py index 255457b..ec42567 100644 --- a/src/fgai/monitor.py +++ b/src/fgai/monitor.py @@ -9,6 +9,7 @@ from .baseline import BaselineStore from .config import ConfigStore from .correlation import correlate_source_ips from .event_context import build_event_context +from .feedback import FeedbackStore from .graylog_mcp import GraylogMcpClient from .graylog_source import GraylogStreamSource from .history import HistoryStore @@ -65,6 +66,14 @@ def build_status( baseline = BaselineStore(baseline_path) if baseline_path else None profiles = baseline.profiles({event.src_ip for event in events if event.src_ip}) if baseline else {} field_deviations = baseline.profile_deviations(events, stream_profiles) if baseline else {} + feedback = FeedbackStore().entries() + for entity, deviations in field_deviations.items(): + for deviation in deviations: + match = next((item for item in feedback if item.get("entity") == entity and item.get("stream_id") == deviation.get("stream_id") and item.get("field") == deviation.get("field")), None) + if match: + deviation["feedback"] = match["status"] + if match["status"] in {"false_positive", "expected"}: + deviation["score"] = 0 anomalies = detect_source_anomalies(events, limit=anomaly_limit, baselines=profiles, field_deviations=field_deviations) baseline_events = baseline.ingest(events) if baseline else 0 profile_baseline_fields = baseline.ingest_profile_fields(events, stream_profiles) if baseline else 0 @@ -118,6 +127,7 @@ def build_status( }, "event_context": build_event_context(events), "field_deviations": field_deviations, + "feedback": feedback, "cross_source_correlations": correlate_source_ips(events), "anomalies": [ {