add rule generalization, sized LLM defaults, ops docs, and UI polish

Feedback rules can now be suggested for merging when a human has already
reviewed the same pattern multiple times (never guesses about new values).
LLM defaults sized for constrained VRAM instead of llama3.3/llama3.1.
Adds systemd units, a state backup script, and reverse-proxy auth guidance
for production deployment. Findings view is collapsible and sorts
suggested-review rows to the top for a quicker overview.
This commit is contained in:
larssand
2026-09-23 13:04:53 +02:00
parent c3e482f9ab
commit bf1109be81
14 changed files with 446 additions and 14 deletions

View File

@@ -3,7 +3,7 @@ import time
import unittest
from pathlib import Path
from fgai.feedback import FeedbackStore, feedback_matches, select_match
from fgai.feedback import FeedbackStore, feedback_matches, select_match, suggest_generalizations
class FeedbackTests(unittest.TestCase):
@@ -120,6 +120,43 @@ class FeedbackTests(unittest.TestCase):
self.assertEqual(select_match(candidates), value_rule)
self.assertEqual(select_match([stream_rule, entity_rule]), entity_rule)
def test_suggest_generalizations_groups_narrow_value_rules_by_stream_entity_field_status(self):
entries = [
{"scope": "value", "status": "false_positive", "entity": "alice", "stream_id": "windows", "field": "username", "value": "bob", "id": "1"},
{"scope": "value", "status": "false_positive", "entity": "alice", "stream_id": "windows", "field": "username", "value": "carol", "id": "2"},
{"scope": "value", "status": "false_positive", "entity": "alice", "stream_id": "windows", "field": "username", "value": "dave", "id": "3"},
{"scope": "value", "status": "confirmed", "entity": "alice", "stream_id": "windows", "field": "username", "value": "eve", "id": "4"},
]
suggestions = suggest_generalizations(entries, min_values=3)
self.assertEqual(len(suggestions), 1)
self.assertEqual(suggestions[0]["value_count"], 3)
self.assertEqual(suggestions[0]["status"], "false_positive")
self.assertEqual(set(suggestions[0]["entry_ids"]), {"1", "2", "3"})
def test_suggest_generalizations_respects_min_values_threshold(self):
entries = [
{"scope": "value", "status": "expected", "entity": "a", "stream_id": "s", "field": "f", "value": "x", "id": "1"},
{"scope": "value", "status": "expected", "entity": "a", "stream_id": "s", "field": "f", "value": "y", "id": "2"},
]
self.assertEqual(suggest_generalizations(entries, min_values=3), [])
def test_suggest_generalizations_ignores_broader_scope_entries(self):
entries = [
{"scope": "field", "status": "false_positive", "entity": "a", "stream_id": "s", "field": "f", "id": "1"},
{"scope": "entity", "status": "expected", "entity": "a", "id": "2"},
]
self.assertEqual(suggest_generalizations(entries, min_values=2), [])
def test_suggest_generalizations_keeps_different_entities_separate(self):
entries = [
{"scope": "value", "status": "false_positive", "entity": "alice", "stream_id": "s", "field": "f", "value": "1", "id": "1"},
{"scope": "value", "status": "false_positive", "entity": "alice", "stream_id": "s", "field": "f", "value": "2", "id": "2"},
{"scope": "value", "status": "false_positive", "entity": "bob", "stream_id": "s", "field": "f", "value": "3", "id": "3"},
]
self.assertEqual(suggest_generalizations(entries, min_values=2), [
{"stream_id": "s", "entity": "alice", "field": "f", "status": "false_positive", "value_count": 2, "sample_values": ["1", "2"], "entry_ids": ["1", "2"]}
])
if __name__ == "__main__":
unittest.main()