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>
411 lines
14 KiB
Python
411 lines
14 KiB
Python
"""
|
|
API Routes für Alert-Templates (Playbooks).
|
|
|
|
Endpoints für Guided Mode:
|
|
- GET /templates - Liste aller verfügbaren Templates
|
|
- GET /templates/{template_id} - Template-Details
|
|
- POST /templates/{template_id}/preview - Vorschau generieren
|
|
- GET /templates/by-role/{role} - Templates für eine Rolle
|
|
"""
|
|
|
|
from typing import Optional, List
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy.orm import Session as DBSession
|
|
|
|
from ..db.database import get_db
|
|
from ..db.models import AlertTemplateDB, UserRoleEnum
|
|
|
|
|
|
router = APIRouter(prefix="/templates", tags=["templates"])
|
|
|
|
|
|
# ============================================================================
|
|
# Request/Response Models
|
|
# ============================================================================
|
|
|
|
class TemplateListItem(BaseModel):
|
|
"""Kurzinfo für Template-Liste."""
|
|
id: str
|
|
slug: str
|
|
name: str
|
|
description: str
|
|
icon: str
|
|
category: str
|
|
target_roles: List[str]
|
|
is_premium: bool
|
|
max_cards_per_day: int
|
|
sort_order: int
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class TemplateDetail(BaseModel):
|
|
"""Vollständige Template-Details."""
|
|
id: str
|
|
slug: str
|
|
name: str
|
|
description: str
|
|
icon: str
|
|
category: str
|
|
target_roles: List[str]
|
|
topics_config: List[dict]
|
|
rules_config: List[dict]
|
|
profile_config: dict
|
|
importance_config: dict
|
|
max_cards_per_day: int
|
|
digest_enabled: bool
|
|
digest_day: str
|
|
is_premium: bool
|
|
is_active: bool
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class TemplateListResponse(BaseModel):
|
|
"""Response für Template-Liste."""
|
|
templates: List[TemplateListItem]
|
|
total: int
|
|
|
|
|
|
class PreviewRequest(BaseModel):
|
|
"""Request für Template-Vorschau."""
|
|
sample_count: int = Field(default=3, ge=1, le=5)
|
|
|
|
|
|
class PreviewItem(BaseModel):
|
|
"""Ein Vorschau-Item."""
|
|
title: str
|
|
snippet: str
|
|
importance_level: str
|
|
why_relevant: str
|
|
source_name: str
|
|
|
|
|
|
class PreviewResponse(BaseModel):
|
|
"""Response für Template-Vorschau."""
|
|
template_name: str
|
|
sample_items: List[PreviewItem]
|
|
estimated_daily_count: str
|
|
message: str
|
|
|
|
|
|
# ============================================================================
|
|
# Endpoints
|
|
# ============================================================================
|
|
|
|
@router.get("", response_model=TemplateListResponse)
|
|
async def list_templates(
|
|
category: Optional[str] = Query(None, description="Filter nach Kategorie"),
|
|
role: Optional[str] = Query(None, description="Filter nach Zielrolle"),
|
|
include_premium: bool = Query(True, description="Premium-Templates einschließen"),
|
|
db: DBSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Liste alle verfügbaren Alert-Templates.
|
|
|
|
Templates sind vorkonfigurierte Playbooks für bestimmte Themen
|
|
(Förderprogramme, Datenschutz, IT-Security, etc.).
|
|
"""
|
|
query = db.query(AlertTemplateDB).filter(AlertTemplateDB.is_active == True)
|
|
|
|
if category:
|
|
query = query.filter(AlertTemplateDB.category == category)
|
|
|
|
if not include_premium:
|
|
query = query.filter(AlertTemplateDB.is_premium == False)
|
|
|
|
templates = query.order_by(AlertTemplateDB.sort_order).all()
|
|
|
|
# Filter nach Rolle (JSON-Feld)
|
|
if role:
|
|
templates = [t for t in templates if role in (t.target_roles or [])]
|
|
|
|
return TemplateListResponse(
|
|
templates=[
|
|
TemplateListItem(
|
|
id=t.id,
|
|
slug=t.slug,
|
|
name=t.name,
|
|
description=t.description,
|
|
icon=t.icon or "",
|
|
category=t.category or "",
|
|
target_roles=t.target_roles or [],
|
|
is_premium=t.is_premium or False,
|
|
max_cards_per_day=t.max_cards_per_day or 10,
|
|
sort_order=t.sort_order or 0,
|
|
)
|
|
for t in templates
|
|
],
|
|
total=len(templates)
|
|
)
|
|
|
|
|
|
@router.get("/by-role/{role}", response_model=TemplateListResponse)
|
|
async def get_templates_by_role(
|
|
role: str,
|
|
db: DBSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Empfohlene Templates für eine bestimmte Rolle.
|
|
|
|
Rollen:
|
|
- lehrkraft: Fokus auf Unterricht, Fortbildungen, Wettbewerbe
|
|
- schulleitung: Fokus auf Administration, Fördermittel, Recht
|
|
- it_beauftragte: Fokus auf IT-Security, Datenschutz
|
|
"""
|
|
# Validiere Rolle
|
|
valid_roles = ["lehrkraft", "schulleitung", "it_beauftragte"]
|
|
if role not in valid_roles:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Ungültige Rolle. Erlaubt: {', '.join(valid_roles)}"
|
|
)
|
|
|
|
templates = db.query(AlertTemplateDB).filter(
|
|
AlertTemplateDB.is_active == True,
|
|
AlertTemplateDB.is_premium == False # Nur kostenlose für Empfehlungen
|
|
).order_by(AlertTemplateDB.sort_order).all()
|
|
|
|
# Filter nach Rolle
|
|
filtered = [t for t in templates if role in (t.target_roles or [])]
|
|
|
|
return TemplateListResponse(
|
|
templates=[
|
|
TemplateListItem(
|
|
id=t.id,
|
|
slug=t.slug,
|
|
name=t.name,
|
|
description=t.description,
|
|
icon=t.icon or "",
|
|
category=t.category or "",
|
|
target_roles=t.target_roles or [],
|
|
is_premium=t.is_premium or False,
|
|
max_cards_per_day=t.max_cards_per_day or 10,
|
|
sort_order=t.sort_order or 0,
|
|
)
|
|
for t in filtered
|
|
],
|
|
total=len(filtered)
|
|
)
|
|
|
|
|
|
@router.get("/{template_id}", response_model=TemplateDetail)
|
|
async def get_template(
|
|
template_id: str,
|
|
db: DBSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Vollständige Details eines Templates abrufen.
|
|
|
|
Enthält alle Konfigurationen (Topics, Rules, Profile).
|
|
"""
|
|
template = db.query(AlertTemplateDB).filter(
|
|
AlertTemplateDB.id == template_id
|
|
).first()
|
|
|
|
if not template:
|
|
# Versuche nach Slug zu suchen
|
|
template = db.query(AlertTemplateDB).filter(
|
|
AlertTemplateDB.slug == template_id
|
|
).first()
|
|
|
|
if not template:
|
|
raise HTTPException(status_code=404, detail="Template nicht gefunden")
|
|
|
|
return TemplateDetail(
|
|
id=template.id,
|
|
slug=template.slug,
|
|
name=template.name,
|
|
description=template.description,
|
|
icon=template.icon or "",
|
|
category=template.category or "",
|
|
target_roles=template.target_roles or [],
|
|
topics_config=template.topics_config or [],
|
|
rules_config=template.rules_config or [],
|
|
profile_config=template.profile_config or {},
|
|
importance_config=template.importance_config or {},
|
|
max_cards_per_day=template.max_cards_per_day or 10,
|
|
digest_enabled=template.digest_enabled if template.digest_enabled is not None else True,
|
|
digest_day=template.digest_day or "monday",
|
|
is_premium=template.is_premium or False,
|
|
is_active=template.is_active if template.is_active is not None else True,
|
|
)
|
|
|
|
|
|
@router.post("/{template_id}/preview", response_model=PreviewResponse)
|
|
async def preview_template(
|
|
template_id: str,
|
|
request: PreviewRequest = None,
|
|
db: DBSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Generiere eine Vorschau, wie Alerts für dieses Template aussehen würden.
|
|
|
|
Zeigt Beispiel-Alerts mit Wichtigkeitsstufen und "Warum relevant?"-Erklärungen.
|
|
"""
|
|
template = db.query(AlertTemplateDB).filter(
|
|
AlertTemplateDB.id == template_id
|
|
).first()
|
|
|
|
if not template:
|
|
template = db.query(AlertTemplateDB).filter(
|
|
AlertTemplateDB.slug == template_id
|
|
).first()
|
|
|
|
if not template:
|
|
raise HTTPException(status_code=404, detail="Template nicht gefunden")
|
|
|
|
# Generiere Beispiel-Alerts basierend auf Template-Konfiguration
|
|
sample_items = _generate_preview_items(template)
|
|
|
|
return PreviewResponse(
|
|
template_name=template.name,
|
|
sample_items=sample_items[:request.sample_count if request else 3],
|
|
estimated_daily_count=f"Ca. {template.max_cards_per_day} Meldungen pro Tag",
|
|
message=f"Diese Vorschau zeigt, wie Alerts für '{template.name}' aussehen würden."
|
|
)
|
|
|
|
|
|
@router.post("/seed")
|
|
async def seed_templates(
|
|
force_update: bool = Query(False, description="Bestehende Templates aktualisieren"),
|
|
db: DBSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Fügt die vordefinierten Templates in die Datenbank ein.
|
|
|
|
Nur für Entwicklung/Setup.
|
|
"""
|
|
from ..data.templates import seed_templates as do_seed
|
|
|
|
count = do_seed(db, force_update=force_update)
|
|
|
|
return {
|
|
"status": "success",
|
|
"templates_created": count,
|
|
"message": f"{count} Templates wurden eingefügt/aktualisiert."
|
|
}
|
|
|
|
|
|
# ============================================================================
|
|
# Helper Functions
|
|
# ============================================================================
|
|
|
|
def _generate_preview_items(template: AlertTemplateDB) -> List[PreviewItem]:
|
|
"""
|
|
Generiere Beispiel-Alerts für Template-Vorschau.
|
|
|
|
Diese sind statisch/exemplarisch, nicht aus echten Daten.
|
|
"""
|
|
# Template-spezifische Beispiele
|
|
examples = {
|
|
"foerderprogramme": [
|
|
PreviewItem(
|
|
title="DigitalPakt 2.0: Neue Antragsphase startet am 1. April",
|
|
snippet="Das BMBF hat die zweite Phase des DigitalPakt Schule angekündigt...",
|
|
importance_level="DRINGEND",
|
|
why_relevant="Frist endet in 45 Tagen. Betrifft alle Schulen mit Förderbedarf.",
|
|
source_name="Bundesministerium für Bildung"
|
|
),
|
|
PreviewItem(
|
|
title="Landesförderung: 50.000€ für innovative Schulprojekte",
|
|
snippet="Das Kultusministerium fördert Schulen, die digitale Lernkonzepte...",
|
|
importance_level="WICHTIG",
|
|
why_relevant="Passende Förderung für Ihr Bundesland. Keine Eigenbeteiligung erforderlich.",
|
|
source_name="Kultusministerium"
|
|
),
|
|
PreviewItem(
|
|
title="Erasmus+ Schulpartnerschaften: Jetzt bewerben",
|
|
snippet="Für das Schuljahr 2026/27 können Schulen EU-Förderung beantragen...",
|
|
importance_level="PRUEFEN",
|
|
why_relevant="EU-Programm mit hoher Fördersumme. Bewerbungsfrist in 3 Monaten.",
|
|
source_name="EU-Kommission"
|
|
),
|
|
],
|
|
"abitur-updates": [
|
|
PreviewItem(
|
|
title="Neue Operatoren für Abitur Deutsch ab 2027",
|
|
snippet="Die KMK hat überarbeitete Operatoren für das Fach Deutsch beschlossen...",
|
|
importance_level="WICHTIG",
|
|
why_relevant="Betrifft die Oberstufenplanung. Anpassung der Klausuren erforderlich.",
|
|
source_name="KMK"
|
|
),
|
|
PreviewItem(
|
|
title="Abiturtermine 2026: Prüfungsplan veröffentlicht",
|
|
snippet="Das Kultusministerium hat die Termine für das Abitur 2026 bekannt gegeben...",
|
|
importance_level="INFO",
|
|
why_relevant="Planungsgrundlage für Schuljahreskalender.",
|
|
source_name="Kultusministerium"
|
|
),
|
|
],
|
|
"datenschutz-recht": [
|
|
PreviewItem(
|
|
title="LfDI: Neue Handreichung zu Schülerfotos",
|
|
snippet="Der Landesbeauftragte für Datenschutz hat eine aktualisierte...",
|
|
importance_level="DRINGEND",
|
|
why_relevant="Handlungsbedarf: Bestehende Einwilligungen müssen geprüft werden.",
|
|
source_name="Datenschutzbeauftragter"
|
|
),
|
|
PreviewItem(
|
|
title="Microsoft 365 an Schulen: Neue Bewertung",
|
|
snippet="Die Datenschutzkonferenz hat ihre Position zu Microsoft 365 aktualisiert...",
|
|
importance_level="WICHTIG",
|
|
why_relevant="Betrifft Schulen mit Microsoft-Lizenzen. Dokumentationspflicht.",
|
|
source_name="DSK"
|
|
),
|
|
],
|
|
"it-security": [
|
|
PreviewItem(
|
|
title="CVE-2026-1234: Kritische Lücke in Moodle",
|
|
snippet="Eine schwerwiegende Sicherheitslücke wurde in Moodle 4.x gefunden...",
|
|
importance_level="KRITISCH",
|
|
why_relevant="Sofortiges Update erforderlich. Exploit bereits aktiv.",
|
|
source_name="BSI CERT-Bund"
|
|
),
|
|
PreviewItem(
|
|
title="Phishing-Welle: Gefälschte Schulportal-Mails",
|
|
snippet="Aktuell werden vermehrt Phishing-Mails an Lehrkräfte versendet...",
|
|
importance_level="DRINGEND",
|
|
why_relevant="Warnung an Kollegium empfohlen. Erkennungsmerkmale beachten.",
|
|
source_name="BSI"
|
|
),
|
|
],
|
|
"fortbildungen": [
|
|
PreviewItem(
|
|
title="Kostenlose Fortbildung: KI im Unterricht",
|
|
snippet="Das Landesinstitut bietet eine Online-Fortbildung zu KI-Tools...",
|
|
importance_level="PRUEFEN",
|
|
why_relevant="Passt zu Ihrem Interessenprofil. Online-Format, 4 Stunden.",
|
|
source_name="Landesinstitut"
|
|
),
|
|
],
|
|
"wettbewerbe-projekte": [
|
|
PreviewItem(
|
|
title="Jugend forscht: Anmeldung bis 30. November",
|
|
snippet="Der größte deutsche MINT-Wettbewerb sucht wieder junge Forscher...",
|
|
importance_level="WICHTIG",
|
|
why_relevant="Frist in 60 Tagen. Für Schüler ab Klasse 4.",
|
|
source_name="Jugend forscht e.V."
|
|
),
|
|
],
|
|
}
|
|
|
|
# Hole Beispiele für dieses Template oder generische
|
|
slug = template.slug
|
|
if slug in examples:
|
|
return examples[slug]
|
|
|
|
# Generische Beispiele
|
|
return [
|
|
PreviewItem(
|
|
title=f"Beispiel-Meldung für {template.name}",
|
|
snippet=f"Dies ist eine Vorschau, wie Alerts für das Thema '{template.name}' aussehen würden.",
|
|
importance_level="INFO",
|
|
why_relevant="Passend zu Ihren ausgewählten Themen.",
|
|
source_name="Beispielquelle"
|
|
)
|
|
]
|