website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
150 lines
6.3 KiB
Python
150 lines
6.3 KiB
Python
"""
|
|
Dual-Mode System Models: Templates, Subscriptions, Sources, Digests.
|
|
|
|
These are additional ORM models for the Guided/Expert dual-mode alert system.
|
|
"""
|
|
from datetime import datetime
|
|
from sqlalchemy import (
|
|
Column, String, Integer, DateTime, JSON,
|
|
Boolean, Text, Enum as SQLEnum, ForeignKey,
|
|
)
|
|
from sqlalchemy.orm import relationship
|
|
import uuid
|
|
|
|
from classroom_engine.database import Base
|
|
from .enums import (
|
|
FeedTypeEnum,
|
|
MigrationModeEnum,
|
|
AlertModeEnum,
|
|
UserRoleEnum,
|
|
DigestStatusEnum,
|
|
)
|
|
|
|
|
|
class AlertTemplateDB(Base):
|
|
"""
|
|
Vorkonfigurierte Alert-Templates (Playbooks).
|
|
Fuer Guided Mode: Lehrer waehlen 1-3 Templates statt RSS-Feeds zu konfigurieren.
|
|
"""
|
|
__tablename__ = 'alert_templates'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
slug = Column(String(100), unique=True, nullable=False)
|
|
name = Column(String(255), nullable=False)
|
|
description = Column(Text, default="")
|
|
icon = Column(String(50), default="")
|
|
category = Column(String(100), default="")
|
|
target_roles = Column(JSON, default=list)
|
|
topics_config = Column(JSON, default=list)
|
|
rules_config = Column(JSON, default=list)
|
|
profile_config = Column(JSON, default=dict)
|
|
importance_config = Column(JSON, default=dict)
|
|
max_cards_per_day = Column(Integer, default=10)
|
|
digest_enabled = Column(Boolean, default=True)
|
|
digest_day = Column(String(20), default="monday")
|
|
language = Column(String(10), default="de")
|
|
is_active = Column(Boolean, default=True)
|
|
is_premium = Column(Boolean, default=False)
|
|
sort_order = Column(Integer, default=0)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
subscriptions = relationship("UserAlertSubscriptionDB", back_populates="template")
|
|
|
|
def __repr__(self):
|
|
return f"<AlertTemplate {self.slug}: {self.name}>"
|
|
|
|
|
|
class AlertSourceDB(Base):
|
|
"""
|
|
Alert-Quelle fuer Migration bestehender Alerts.
|
|
Unterstuetzt: E-Mail-Weiterleitung, RSS-Import, Rekonstruktion.
|
|
"""
|
|
__tablename__ = 'alert_sources'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
tenant_id = Column(String(36), nullable=True, index=True)
|
|
user_id = Column(String(36), nullable=True, index=True)
|
|
source_type = Column(SQLEnum(FeedTypeEnum), default=FeedTypeEnum.RSS, nullable=False)
|
|
original_label = Column(String(255), nullable=True)
|
|
inbound_address = Column(String(255), nullable=True, unique=True)
|
|
rss_url = Column(String(2000), nullable=True)
|
|
migration_mode = Column(SQLEnum(MigrationModeEnum), default=MigrationModeEnum.IMPORT, nullable=False)
|
|
topic_id = Column(String(36), ForeignKey('alert_topics.id', ondelete='SET NULL'), nullable=True)
|
|
is_active = Column(Boolean, default=True)
|
|
items_received = Column(Integer, default=0)
|
|
last_item_at = Column(DateTime, nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<AlertSource {self.source_type.value}: {self.original_label}>"
|
|
|
|
|
|
class UserAlertSubscriptionDB(Base):
|
|
"""
|
|
User-Subscription fuer Alert-Templates oder Expert-Profile.
|
|
Speichert Modus-Wahl, Template-Verknuepfung und Wizard-Zustand.
|
|
"""
|
|
__tablename__ = 'user_alert_subscriptions'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
user_id = Column(String(36), nullable=False, index=True)
|
|
school_id = Column(String(36), nullable=True, index=True)
|
|
mode = Column(SQLEnum(AlertModeEnum), default=AlertModeEnum.GUIDED, nullable=False)
|
|
user_role = Column(SQLEnum(UserRoleEnum), nullable=True)
|
|
template_id = Column(String(36), ForeignKey('alert_templates.id', ondelete='SET NULL'), nullable=True)
|
|
selected_template_ids = Column(JSON, default=list)
|
|
profile_id = Column(String(36), ForeignKey('alert_profiles.id', ondelete='SET NULL'), nullable=True)
|
|
is_active = Column(Boolean, default=True)
|
|
notification_email = Column(String(255), nullable=True)
|
|
digest_enabled = Column(Boolean, default=True)
|
|
digest_frequency = Column(String(20), default="weekly")
|
|
digest_day = Column(String(20), default="monday")
|
|
last_digest_sent_at = Column(DateTime, nullable=True)
|
|
wizard_step = Column(Integer, default=0)
|
|
wizard_completed = Column(Boolean, default=False)
|
|
wizard_state = Column(JSON, default=dict)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
template = relationship("AlertTemplateDB", back_populates="subscriptions")
|
|
profile = relationship("AlertProfileDB")
|
|
digests = relationship("AlertDigestDB", back_populates="subscription", cascade="all, delete-orphan")
|
|
|
|
def __repr__(self):
|
|
return f"<UserAlertSubscription {self.user_id} ({self.mode.value})>"
|
|
|
|
|
|
class AlertDigestDB(Base):
|
|
"""
|
|
Woechentliche Digest-Zusammenfassung.
|
|
Enthaelt gerenderte Zusammenfassung + Statistiken.
|
|
"""
|
|
__tablename__ = 'alert_digests'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
subscription_id = Column(String(36), ForeignKey('user_alert_subscriptions.id', ondelete='CASCADE'), nullable=False, index=True)
|
|
user_id = Column(String(36), nullable=False, index=True)
|
|
period_start = Column(DateTime, nullable=False)
|
|
period_end = Column(DateTime, nullable=False)
|
|
title = Column(String(255), default="")
|
|
summary_html = Column(Text, default="")
|
|
summary_pdf_url = Column(String(500), nullable=True)
|
|
total_alerts = Column(Integer, default=0)
|
|
kritisch_count = Column(Integer, default=0)
|
|
dringend_count = Column(Integer, default=0)
|
|
wichtig_count = Column(Integer, default=0)
|
|
pruefen_count = Column(Integer, default=0)
|
|
info_count = Column(Integer, default=0)
|
|
alert_ids = Column(JSON, default=list)
|
|
status = Column(SQLEnum(DigestStatusEnum), default=DigestStatusEnum.PENDING, nullable=False)
|
|
sent_at = Column(DateTime, nullable=True)
|
|
error_message = Column(Text, nullable=True)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
subscription = relationship("UserAlertSubscriptionDB", back_populates="digests")
|
|
|
|
def __repr__(self):
|
|
return f"<AlertDigest {self.title} ({self.status.value})>"
|