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
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

342 lines
10 KiB
Python

"""
Importance Mapping für Guided Mode.
Konvertiert Relevanz-Scores (0.0-1.0) in 5-stufige Wichtigkeitsstufen:
- KRITISCH (90-100%): Sofortiges Handeln erforderlich
- DRINGEND (75-90%): Wichtig, bald handeln
- WICHTIG (60-75%): Beachtenswert
- PRÜFEN (40-60%): Eventuell relevant
- INFO (0-40%): Zur Kenntnisnahme
Zusätzlich: Generierung von "Warum relevant?"-Erklärungen und nächsten Schritten.
"""
from typing import Optional, List, Dict, Any
from datetime import datetime, timedelta
import re
from ..db.models import ImportanceLevelEnum, AlertItemDB
# Re-export fuer einfacheren Import
__all__ = [
'ImportanceLevelEnum',
'score_to_importance',
'importance_to_label_de',
'importance_to_color',
'extract_deadline',
'generate_why_relevant',
'generate_next_steps',
'enrich_alert_for_guided_mode',
'batch_enrich_alerts',
'filter_by_importance',
]
# Standard-Schwellenwerte für Importance-Mapping
DEFAULT_THRESHOLDS = {
"kritisch": 0.90,
"dringend": 0.75,
"wichtig": 0.60,
"pruefen": 0.40,
}
def score_to_importance(
score: float,
thresholds: Dict[str, float] = None
) -> ImportanceLevelEnum:
"""
Konvertiere Relevanz-Score zu Importance-Level.
Args:
score: Relevanz-Score (0.0 - 1.0)
thresholds: Optionale benutzerdefinierte Schwellenwerte
Returns:
ImportanceLevelEnum
"""
if score is None:
return ImportanceLevelEnum.INFO
thresholds = thresholds or DEFAULT_THRESHOLDS
if score >= thresholds.get("kritisch", 0.90):
return ImportanceLevelEnum.KRITISCH
elif score >= thresholds.get("dringend", 0.75):
return ImportanceLevelEnum.DRINGEND
elif score >= thresholds.get("wichtig", 0.60):
return ImportanceLevelEnum.WICHTIG
elif score >= thresholds.get("pruefen", 0.40):
return ImportanceLevelEnum.PRUEFEN
else:
return ImportanceLevelEnum.INFO
def importance_to_label_de(importance: ImportanceLevelEnum) -> str:
"""Deutsches Label für Importance-Level."""
labels = {
ImportanceLevelEnum.KRITISCH: "Kritisch",
ImportanceLevelEnum.DRINGEND: "Dringend",
ImportanceLevelEnum.WICHTIG: "Wichtig",
ImportanceLevelEnum.PRUEFEN: "Zu prüfen",
ImportanceLevelEnum.INFO: "Info",
}
return labels.get(importance, "Info")
def importance_to_color(importance: ImportanceLevelEnum) -> str:
"""CSS-Farbe für Importance-Level (Tailwind-kompatibel)."""
colors = {
ImportanceLevelEnum.KRITISCH: "red",
ImportanceLevelEnum.DRINGEND: "orange",
ImportanceLevelEnum.WICHTIG: "amber",
ImportanceLevelEnum.PRUEFEN: "blue",
ImportanceLevelEnum.INFO: "slate",
}
return colors.get(importance, "slate")
def extract_deadline(text: str) -> Optional[datetime]:
"""
Extrahiere Deadline/Frist aus Text.
Sucht nach Mustern wie:
- "bis zum 15.03.2026"
- "Frist: 1. April"
- "Anmeldeschluss: 30.11."
"""
# Deutsche Datumsformate
patterns = [
r"bis\s+(?:zum\s+)?(\d{1,2})\.(\d{1,2})\.(\d{4})",
r"Frist[:\s]+(\d{1,2})\.(\d{1,2})\.(\d{4})",
r"(?:Anmelde|Bewerbungs)schluss[:\s]+(\d{1,2})\.(\d{1,2})\.?(?:(\d{4}))?",
r"endet\s+am\s+(\d{1,2})\.(\d{1,2})\.(\d{4})?",
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
day = int(match.group(1))
month = int(match.group(2))
year = int(match.group(3)) if match.group(3) else datetime.now().year
try:
return datetime(year, month, day)
except ValueError:
continue
return None
def generate_why_relevant(
alert: AlertItemDB,
profile_priorities: List[Dict[str, Any]] = None,
matched_keywords: List[str] = None
) -> str:
"""
Generiere "Warum relevant?"-Erklärung für einen Alert.
Args:
alert: Der Alert
profile_priorities: Prioritäten aus dem User-Profil
matched_keywords: Keywords, die gematcht haben
Returns:
Deutsche Erklärung (1-2 Bulletpoints)
"""
reasons = []
# Deadline-basierte Relevanz
deadline = extract_deadline(f"{alert.title} {alert.snippet}")
if deadline:
days_until = (deadline - datetime.now()).days
if days_until <= 0:
reasons.append("Frist abgelaufen oder heute!")
elif days_until <= 7:
reasons.append(f"Frist endet in {days_until} Tagen")
elif days_until <= 30:
reasons.append(f"Frist in ca. {days_until} Tagen")
# Keyword-basierte Relevanz
if matched_keywords and len(matched_keywords) > 0:
keywords_str = ", ".join(matched_keywords[:3])
reasons.append(f"Enthält relevante Begriffe: {keywords_str}")
# Prioritäten-basierte Relevanz
if profile_priorities:
for priority in profile_priorities[:2]:
label = priority.get("label", "")
keywords = priority.get("keywords", [])
text_lower = f"{alert.title} {alert.snippet}".lower()
for kw in keywords:
if kw.lower() in text_lower:
reasons.append(f"Passt zu Ihrem Interesse: {label}")
break
# Score-basierte Relevanz
if alert.relevance_score and alert.relevance_score >= 0.8:
reasons.append("Hohe Übereinstimmung mit Ihrem Profil")
# Fallback
if not reasons:
reasons.append("Passt zu Ihren ausgewählten Themen")
# Formatiere als Bulletpoints
return "".join(reasons[:2])
def generate_next_steps(
alert: AlertItemDB,
template_slug: str = None
) -> List[str]:
"""
Generiere empfohlene nächste Schritte.
Basiert auf Template-Typ und Alert-Inhalt.
"""
steps = []
text = f"{alert.title} {alert.snippet}".lower()
# Template-spezifische Schritte
if template_slug == "foerderprogramme":
if "antrag" in text or "förder" in text:
steps.append("Schulträger über Fördermöglichkeit informieren")
steps.append("Antragsunterlagen prüfen")
if "frist" in text or "deadline" in text:
steps.append("Termin in Kalender eintragen")
elif template_slug == "datenschutz-recht":
if "dsgvo" in text or "datenschutz" in text:
steps.append("Datenschutzbeauftragten informieren")
steps.append("Prüfen, ob Handlungsbedarf besteht")
if "urteil" in text or "gericht" in text:
steps.append("Rechtsfolgen für die Schule prüfen")
elif template_slug == "it-security":
if "cve" in text or "sicherheitslücke" in text:
steps.append("Betroffene Systeme prüfen")
steps.append("Update/Patch einspielen")
if "phishing" in text:
steps.append("Kollegium warnen")
steps.append("Erkennungsmerkmale kommunizieren")
elif template_slug == "abitur-updates":
if "abitur" in text or "prüfung" in text:
steps.append("Fachschaften informieren")
steps.append("Anpassung der Kursplanung prüfen")
elif template_slug == "fortbildungen":
steps.append("Termin und Ort prüfen")
steps.append("Bei Interesse: Anmeldung vornehmen")
elif template_slug == "wettbewerbe-projekte":
steps.append("Passende Schülergruppe identifizieren")
steps.append("Anmeldefrist beachten")
# Allgemeine Schritte als Fallback
if not steps:
steps.append("Quelle öffnen und Details lesen")
if "frist" in text or "bis" in text:
steps.append("Termin notieren")
return steps[:3] # Maximal 3 Schritte
def enrich_alert_for_guided_mode(
alert: AlertItemDB,
profile_priorities: List[Dict[str, Any]] = None,
template_slug: str = None,
importance_thresholds: Dict[str, float] = None
) -> AlertItemDB:
"""
Reichere Alert mit Guided-Mode-spezifischen Feldern an.
Setzt:
- importance_level
- why_relevant
- next_steps
- action_deadline
Args:
alert: Der Alert
profile_priorities: Prioritäten aus dem User-Profil
template_slug: Slug des aktiven Templates
importance_thresholds: Optionale Schwellenwerte
Returns:
Der angereicherte Alert
"""
# Importance Level
alert.importance_level = score_to_importance(
alert.relevance_score,
importance_thresholds
)
# Why Relevant
alert.why_relevant = generate_why_relevant(alert, profile_priorities)
# Next Steps
alert.next_steps = generate_next_steps(alert, template_slug)
# Action Deadline
deadline = extract_deadline(f"{alert.title} {alert.snippet}")
if deadline:
alert.action_deadline = deadline
return alert
def batch_enrich_alerts(
alerts: List[AlertItemDB],
profile_priorities: List[Dict[str, Any]] = None,
template_slug: str = None,
importance_thresholds: Dict[str, float] = None
) -> List[AlertItemDB]:
"""
Reichere mehrere Alerts für Guided Mode an.
"""
return [
enrich_alert_for_guided_mode(
alert,
profile_priorities,
template_slug,
importance_thresholds
)
for alert in alerts
]
def filter_by_importance(
alerts: List[AlertItemDB],
min_level: ImportanceLevelEnum = ImportanceLevelEnum.INFO,
max_count: int = 10
) -> List[AlertItemDB]:
"""
Filtere Alerts nach Mindest-Importance und limitiere Anzahl.
Sortiert nach Importance (höchste zuerst).
"""
# Importance-Ranking (höher = wichtiger)
importance_rank = {
ImportanceLevelEnum.KRITISCH: 5,
ImportanceLevelEnum.DRINGEND: 4,
ImportanceLevelEnum.WICHTIG: 3,
ImportanceLevelEnum.PRUEFEN: 2,
ImportanceLevelEnum.INFO: 1,
}
min_rank = importance_rank.get(min_level, 1)
# Filter
filtered = [
a for a in alerts
if importance_rank.get(a.importance_level, 1) >= min_rank
]
# Sortiere nach Importance (absteigend)
filtered.sort(
key=lambda a: importance_rank.get(a.importance_level, 1),
reverse=True
)
return filtered[:max_count]