Implemented the feedback foundation.
This commit is contained in:
27
src/fgai/feedback.py
Normal file
27
src/fgai/feedback.py
Normal file
@@ -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
|
||||
@@ -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(
|
||||
[],
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user