Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
263 lines
8.9 KiB
Python
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
|