Files
breakpilot-lehrer/backend-lehrer/alerts_agent/actions/dispatcher.py
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

233 lines
6.4 KiB
Python

"""
Action Dispatcher für Alerts Agent.
Verteilt Aktionen an die entsprechenden Handler.
"""
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
from .base import ActionHandler, ActionResult, ActionType, AlertContext
from .email_action import EmailAction
from .webhook_action import WebhookAction
from .slack_action import SlackAction
logger = logging.getLogger(__name__)
class ActionDispatcher:
"""
Zentrale Verteilung von Aktionen an Handler.
Registriert Handler für verschiedene Aktionstypen und
führt Aktionen basierend auf Regel-Konfigurationen aus.
"""
def __init__(self):
"""Initialisiert den Dispatcher mit Standard-Handlern."""
self._handlers: Dict[ActionType, ActionHandler] = {}
# Standard-Handler registrieren
self.register_handler(EmailAction())
self.register_handler(WebhookAction())
self.register_handler(SlackAction())
def register_handler(self, handler: ActionHandler) -> None:
"""
Registriert einen Action-Handler.
Args:
handler: Handler-Instanz
"""
self._handlers[handler.action_type] = handler
logger.debug(f"Registered action handler: {handler.action_type.value}")
def get_handler(self, action_type: ActionType) -> Optional[ActionHandler]:
"""
Gibt den Handler für einen Aktionstyp zurück.
Args:
action_type: Aktionstyp
Returns:
Handler oder None
"""
return self._handlers.get(action_type)
def list_handlers(self) -> List[str]:
"""Gibt Liste der registrierten Handler zurück."""
return [at.value for at in self._handlers.keys()]
async def dispatch(
self,
action_type: str,
context: AlertContext,
config: Dict[str, Any],
) -> ActionResult:
"""
Führt eine Aktion aus.
Args:
action_type: Aktionstyp als String (email, webhook, slack)
context: Alert-Kontext
config: Aktionsspezifische Konfiguration
Returns:
ActionResult
"""
try:
# ActionType aus String
at = ActionType(action_type.lower())
except ValueError:
return ActionResult(
success=False,
action_type=ActionType.WEBHOOK, # Fallback
message=f"Unbekannter Aktionstyp: {action_type}",
error="Unknown action type",
)
handler = self.get_handler(at)
if not handler:
return ActionResult(
success=False,
action_type=at,
message=f"Kein Handler für {action_type} registriert",
error="No handler registered",
)
# Konfiguration validieren
if not handler.validate_config(config):
required = handler.get_required_config_fields()
return ActionResult(
success=False,
action_type=at,
message=f"Ungültige Konfiguration für {action_type}",
error=f"Required fields: {required}",
)
# Aktion ausführen
logger.info(f"Dispatching {action_type} action for alert {context.alert_id[:8]}")
result = await handler.execute(context, config)
return result
async def dispatch_multiple(
self,
actions: List[Dict[str, Any]],
context: AlertContext,
) -> List[ActionResult]:
"""
Führt mehrere Aktionen aus.
Args:
actions: Liste von Aktionen [{type, config}, ...]
context: Alert-Kontext
Returns:
Liste von ActionResults
"""
results = []
for action in actions:
action_type = action.get("type", action.get("action_type", ""))
config = action.get("config", action.get("action_config", {}))
result = await self.dispatch(action_type, context, config)
results.append(result)
return results
# Singleton-Instanz
_dispatcher: Optional[ActionDispatcher] = None
def get_dispatcher() -> ActionDispatcher:
"""Gibt den globalen ActionDispatcher zurück."""
global _dispatcher
if _dispatcher is None:
_dispatcher = ActionDispatcher()
return _dispatcher
async def execute_action(
action_type: str,
alert_id: str,
title: str,
url: str,
snippet: str,
topic_name: str,
config: Dict[str, Any],
relevance_score: Optional[float] = None,
relevance_decision: Optional[str] = None,
matched_rule: Optional[str] = None,
tags: Optional[List[str]] = None,
) -> ActionResult:
"""
Convenience-Funktion zum Ausführen einer Aktion.
Erstellt den Kontext und ruft den Dispatcher auf.
"""
context = AlertContext(
alert_id=alert_id,
title=title,
url=url,
snippet=snippet,
topic_name=topic_name,
relevance_score=relevance_score,
relevance_decision=relevance_decision,
matched_rule=matched_rule,
tags=tags or [],
)
dispatcher = get_dispatcher()
return await dispatcher.dispatch(action_type, context, config)
async def execute_rule_actions(
alert_id: str,
title: str,
url: str,
snippet: str,
topic_name: str,
rule_action: str,
rule_config: Dict[str, Any],
rule_name: str,
) -> ActionResult:
"""
Führt die Aktion einer gematschten Regel aus.
Args:
alert_id: Alert-ID
title: Alert-Titel
url: Alert-URL
snippet: Alert-Snippet
topic_name: Topic-Name
rule_action: Aktionstyp der Regel
rule_config: Aktions-Konfiguration
rule_name: Name der Regel
Returns:
ActionResult
"""
# Nur externe Aktionen (email, webhook, slack) hier behandeln
# keep/drop/tag werden direkt von der Rule Engine behandelt
if rule_action not in ["email", "webhook", "slack"]:
return ActionResult(
success=True,
action_type=ActionType.TAG, # Dummy
message=f"Interne Aktion {rule_action} von Rule Engine behandelt",
)
return await execute_action(
action_type=rule_action,
alert_id=alert_id,
title=title,
url=url,
snippet=snippet,
topic_name=topic_name,
config=rule_config,
matched_rule=rule_name,
)