This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/tests/test_alerts_agent/test_feedback_learning.py
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

263 lines
8.9 KiB
Python

"""
Tests für den Feedback-Learning-Mechanismus.
Testet wie das System aus Nutzer-Feedback lernt und das Profil anpasst.
"""
import pytest
from datetime import datetime
from alerts_agent.models.relevance_profile import RelevanceProfile, PriorityItem
from alerts_agent.models.alert_item import AlertItem
class TestFeedbackLearning:
"""Tests für den Feedback-Learning-Mechanismus."""
def test_positive_feedback_adds_example(self):
"""Test positives Feedback fügt Beispiel hinzu."""
profile = RelevanceProfile()
profile.update_from_feedback(
alert_title="Wichtiger Artikel zur Inklusion",
alert_url="https://example.com/inklusion",
is_relevant=True,
reason="Sehr relevant für meine Arbeit",
)
assert len(profile.positive_examples) == 1
assert profile.positive_examples[0]["title"] == "Wichtiger Artikel zur Inklusion"
assert profile.positive_examples[0]["reason"] == "Sehr relevant für meine Arbeit"
def test_negative_feedback_adds_example(self):
"""Test negatives Feedback fügt Beispiel hinzu."""
profile = RelevanceProfile()
profile.update_from_feedback(
alert_title="Stellenanzeige Lehrer",
alert_url="https://example.com/job",
is_relevant=False,
reason="Nur Werbung",
)
assert len(profile.negative_examples) == 1
assert profile.negative_examples[0]["title"] == "Stellenanzeige Lehrer"
def test_feedback_updates_counters(self):
"""Test Feedback aktualisiert Zähler."""
profile = RelevanceProfile()
# 3 positive, 2 negative
for i in range(3):
profile.update_from_feedback(f"Good {i}", f"url{i}", True)
for i in range(2):
profile.update_from_feedback(f"Bad {i}", f"url{i}", False)
assert profile.total_scored == 5
assert profile.total_kept == 3
assert profile.total_dropped == 2
def test_examples_limited_to_20(self):
"""Test Beispiele werden auf 20 begrenzt."""
profile = RelevanceProfile()
# 25 Beispiele hinzufügen
for i in range(25):
profile.update_from_feedback(
f"Example {i}",
f"https://example.com/{i}",
is_relevant=True,
)
assert len(profile.positive_examples) == 20
# Die neuesten sollten behalten werden
titles = [ex["title"] for ex in profile.positive_examples]
assert "Example 24" in titles
assert "Example 0" not in titles # Ältestes sollte weg sein
def test_examples_in_prompt_context(self):
"""Test Beispiele erscheinen im Prompt-Kontext."""
profile = RelevanceProfile()
profile.update_from_feedback(
"Relevanter Artikel",
"https://example.com/good",
is_relevant=True,
reason="Wichtig",
)
profile.update_from_feedback(
"Irrelevanter Artikel",
"https://example.com/bad",
is_relevant=False,
reason="Spam",
)
context = profile.get_prompt_context()
assert "Relevanter Artikel" in context
assert "Irrelevanter Artikel" in context
assert "relevante Alerts" in context
assert "irrelevante Alerts" in context
class TestProfileEvolution:
"""Tests für die Evolution des Profils über Zeit."""
def test_profile_learns_from_feedback_pattern(self):
"""Test Profil lernt aus Feedback-Mustern."""
profile = RelevanceProfile()
# Simuliere Feedback-Muster: Inklusions-Artikel sind relevant
inklusion_articles = [
("Neue Inklusions-Verordnung", "https://example.com/1"),
("Inklusion in Bayern verstärkt", "https://example.com/2"),
("Förderbedarf: Neue Richtlinien", "https://example.com/3"),
]
for title, url in inklusion_articles:
profile.update_from_feedback(title, url, is_relevant=True, reason="Inklusion")
# Simuliere irrelevante Artikel
spam_articles = [
("Newsletter Dezember", "https://example.com/spam1"),
("Pressemitteilung", "https://example.com/spam2"),
]
for title, url in spam_articles:
profile.update_from_feedback(title, url, is_relevant=False, reason="Spam")
# Prompt-Kontext sollte die Muster reflektieren
context = profile.get_prompt_context()
# Alle positiven Beispiele sollten Inklusions-bezogen sein
for title, _ in inklusion_articles:
assert title in context
# Negative Beispiele sollten auch vorhanden sein
for title, _ in spam_articles:
assert title in context
def test_profile_statistics_reflect_decisions(self):
"""Test Profil-Statistiken reflektieren Entscheidungen."""
profile = RelevanceProfile()
# 70% relevant, 30% irrelevant
for i in range(70):
profile.update_from_feedback(f"Good {i}", f"url{i}", True)
for i in range(30):
profile.update_from_feedback(f"Bad {i}", f"url{i}", False)
assert profile.total_scored == 100
assert profile.total_kept == 70
assert profile.total_dropped == 30
# Keep-Rate sollte 70% sein
keep_rate = profile.total_kept / profile.total_scored
assert keep_rate == 0.7
class TestFeedbackWithPriorities:
"""Tests für Feedback in Kombination mit Prioritäten."""
def test_priority_keywords_in_feedback(self):
"""Test Feedback-Beispiele ergänzen Prioritäts-Keywords."""
profile = RelevanceProfile()
profile.add_priority(
"Inklusion",
weight=0.9,
keywords=["Förderbedarf", "inklusiv"],
)
# Feedback mit zusätzlichem Kontext
profile.update_from_feedback(
"Nachteilsausgleich für Schüler mit Förderbedarf",
"https://example.com/nachteilsausgleich",
is_relevant=True,
reason="Nachteilsausgleich ist wichtig für Inklusion",
)
# Das Feedback-Beispiel sollte im Kontext erscheinen
context = profile.get_prompt_context()
assert "Nachteilsausgleich" in context
def test_exclusion_patterns_from_feedback(self):
"""Test Ausschlüsse werden durch Feedback-Muster erkannt."""
profile = RelevanceProfile()
# Mehrere Stellenanzeigen als irrelevant markieren
for i in range(5):
profile.update_from_feedback(
f"Stellenanzeige: Position {i}",
f"https://example.com/job{i}",
is_relevant=False,
reason="Stellenanzeige",
)
# Das Muster sollte in negativen Beispielen sichtbar sein
assert len(profile.negative_examples) == 5
assert all("Stellenanzeige" in ex["title"] for ex in profile.negative_examples)
class TestDefaultProfileFeedback:
"""Tests für Feedback mit dem Default-Bildungsprofil."""
def test_default_profile_with_feedback(self):
"""Test Default-Profil kann Feedback verarbeiten."""
profile = RelevanceProfile.create_default_education_profile()
# Starte mit Default-Werten
initial_examples = len(profile.positive_examples)
# Füge Feedback hinzu
profile.update_from_feedback(
"Datenschutz an Schulen: Neue DSGVO-Richtlinien",
"https://example.com/dsgvo",
is_relevant=True,
reason="DSGVO-relevant",
)
assert len(profile.positive_examples) == initial_examples + 1
assert profile.total_kept == 1
def test_default_priorities_preserved_after_feedback(self):
"""Test Default-Prioritäten bleiben nach Feedback erhalten."""
profile = RelevanceProfile.create_default_education_profile()
original_priorities = len(profile.priorities)
# Feedback sollte Prioritäten nicht ändern
profile.update_from_feedback("Test", "https://test.com", True)
assert len(profile.priorities) == original_priorities
class TestFeedbackTimestamps:
"""Tests für Feedback-Zeitstempel."""
def test_feedback_has_timestamp(self):
"""Test Feedback-Beispiele haben Zeitstempel."""
profile = RelevanceProfile()
profile.update_from_feedback(
"Test Article",
"https://example.com",
is_relevant=True,
)
example = profile.positive_examples[0]
assert "added_at" in example
# Sollte ein ISO-Format Datum sein
datetime.fromisoformat(example["added_at"])
def test_profile_updated_at_changes(self):
"""Test updated_at ändert sich nach Feedback."""
profile = RelevanceProfile()
original_updated = profile.updated_at
# Kurz warten und Feedback geben
import time
time.sleep(0.01)
profile.update_from_feedback("Test", "https://test.com", True)
assert profile.updated_at > original_updated