[split-required] Split 500-1000 LOC files across all services
backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
226
backend-lehrer/alerts_agent/db/profile_repository.py
Normal file
226
backend-lehrer/alerts_agent/db/profile_repository.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
Repository für Alert Profiles (Nutzer-Profile für Relevanz-Scoring).
|
||||
"""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
from sqlalchemy.orm import Session as DBSession
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from .models import AlertProfileDB
|
||||
|
||||
|
||||
class ProfileRepository:
|
||||
"""Repository für Alert Profiles (Nutzer-Profile für Relevanz-Scoring)."""
|
||||
|
||||
def __init__(self, db: DBSession):
|
||||
self.db = db
|
||||
|
||||
# ==================== CREATE / GET-OR-CREATE ====================
|
||||
|
||||
def get_or_create(self, user_id: str = None) -> AlertProfileDB:
|
||||
"""Holt oder erstellt ein Profil."""
|
||||
profile = self.get_by_user_id(user_id)
|
||||
if profile:
|
||||
return profile
|
||||
|
||||
# Neues Profil erstellen
|
||||
profile = AlertProfileDB(
|
||||
id=str(uuid.uuid4()),
|
||||
user_id=user_id,
|
||||
name="Default" if not user_id else f"Profile {user_id[:8]}",
|
||||
)
|
||||
self.db.add(profile)
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
def create_default_education_profile(self, user_id: str = None) -> AlertProfileDB:
|
||||
"""Erstellt ein Standard-Profil für Bildungsthemen."""
|
||||
profile = AlertProfileDB(
|
||||
id=str(uuid.uuid4()),
|
||||
user_id=user_id,
|
||||
name="Bildung Default",
|
||||
priorities=[
|
||||
{
|
||||
"label": "Inklusion",
|
||||
"weight": 0.9,
|
||||
"keywords": ["inklusiv", "Förderbedarf", "Behinderung", "Barrierefreiheit"],
|
||||
"description": "Inklusive Bildung, Förderschulen, Nachteilsausgleich"
|
||||
},
|
||||
{
|
||||
"label": "Datenschutz Schule",
|
||||
"weight": 0.85,
|
||||
"keywords": ["DSGVO", "Schülerfotos", "Einwilligung", "personenbezogene Daten"],
|
||||
"description": "DSGVO in Schulen, Datenschutz bei Klassenfotos"
|
||||
},
|
||||
{
|
||||
"label": "Schulrecht Bayern",
|
||||
"weight": 0.8,
|
||||
"keywords": ["BayEUG", "Schulordnung", "Kultusministerium", "Bayern"],
|
||||
"description": "Bayerisches Schulrecht, Verordnungen"
|
||||
},
|
||||
{
|
||||
"label": "Digitalisierung Schule",
|
||||
"weight": 0.7,
|
||||
"keywords": ["DigitalPakt", "Tablet-Klasse", "Lernplattform"],
|
||||
"description": "Digitale Medien im Unterricht"
|
||||
},
|
||||
],
|
||||
exclusions=["Stellenanzeige", "Praktikum gesucht", "Werbung", "Pressemitteilung"],
|
||||
policies={
|
||||
"prefer_german_sources": True,
|
||||
"max_age_days": 30,
|
||||
"min_content_length": 100,
|
||||
}
|
||||
)
|
||||
self.db.add(profile)
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
# ==================== READ ====================
|
||||
|
||||
def get_by_id(self, profile_id: str) -> Optional[AlertProfileDB]:
|
||||
"""Holt ein Profil nach ID."""
|
||||
return self.db.query(AlertProfileDB).filter(
|
||||
AlertProfileDB.id == profile_id
|
||||
).first()
|
||||
|
||||
def get_by_user_id(self, user_id: str) -> Optional[AlertProfileDB]:
|
||||
"""Holt ein Profil nach User-ID."""
|
||||
if not user_id:
|
||||
# Default-Profil ohne User
|
||||
return self.db.query(AlertProfileDB).filter(
|
||||
AlertProfileDB.user_id.is_(None)
|
||||
).first()
|
||||
|
||||
return self.db.query(AlertProfileDB).filter(
|
||||
AlertProfileDB.user_id == user_id
|
||||
).first()
|
||||
|
||||
# ==================== UPDATE ====================
|
||||
|
||||
def update_priorities(
|
||||
self,
|
||||
profile_id: str,
|
||||
priorities: List[Dict],
|
||||
) -> Optional[AlertProfileDB]:
|
||||
"""Aktualisiert die Prioritäten eines Profils."""
|
||||
profile = self.get_by_id(profile_id)
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
profile.priorities = priorities
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
def update_exclusions(
|
||||
self,
|
||||
profile_id: str,
|
||||
exclusions: List[str],
|
||||
) -> Optional[AlertProfileDB]:
|
||||
"""Aktualisiert die Ausschlüsse eines Profils."""
|
||||
profile = self.get_by_id(profile_id)
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
profile.exclusions = exclusions
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
def add_feedback(
|
||||
self,
|
||||
profile_id: str,
|
||||
title: str,
|
||||
url: str,
|
||||
is_relevant: bool,
|
||||
reason: str = "",
|
||||
) -> Optional[AlertProfileDB]:
|
||||
"""Fügt Feedback als Beispiel hinzu."""
|
||||
profile = self.get_by_id(profile_id)
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
example = {
|
||||
"title": title,
|
||||
"url": url,
|
||||
"reason": reason,
|
||||
"added_at": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
if is_relevant:
|
||||
examples = list(profile.positive_examples or [])
|
||||
examples.append(example)
|
||||
profile.positive_examples = examples[-20:] # Max 20
|
||||
profile.total_kept += 1
|
||||
flag_modified(profile, "positive_examples")
|
||||
else:
|
||||
examples = list(profile.negative_examples or [])
|
||||
examples.append(example)
|
||||
profile.negative_examples = examples[-20:] # Max 20
|
||||
profile.total_dropped += 1
|
||||
flag_modified(profile, "negative_examples")
|
||||
|
||||
profile.total_scored += 1
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
def update_stats(
|
||||
self,
|
||||
profile_id: str,
|
||||
kept: int = 0,
|
||||
dropped: int = 0,
|
||||
) -> Optional[AlertProfileDB]:
|
||||
"""Aktualisiert die Statistiken eines Profils."""
|
||||
profile = self.get_by_id(profile_id)
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
profile.total_scored += kept + dropped
|
||||
profile.total_kept += kept
|
||||
profile.total_dropped += dropped
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(profile)
|
||||
return profile
|
||||
|
||||
# ==================== DELETE ====================
|
||||
|
||||
def delete(self, profile_id: str) -> bool:
|
||||
"""Löscht ein Profil."""
|
||||
profile = self.get_by_id(profile_id)
|
||||
if not profile:
|
||||
return False
|
||||
|
||||
self.db.delete(profile)
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
# ==================== CONVERSION ====================
|
||||
|
||||
def to_dict(self, profile: AlertProfileDB) -> Dict[str, Any]:
|
||||
"""Konvertiert DB-Model zu Dictionary."""
|
||||
return {
|
||||
"id": profile.id,
|
||||
"user_id": profile.user_id,
|
||||
"name": profile.name,
|
||||
"priorities": profile.priorities,
|
||||
"exclusions": profile.exclusions,
|
||||
"policies": profile.policies,
|
||||
"examples": {
|
||||
"positive": len(profile.positive_examples or []),
|
||||
"negative": len(profile.negative_examples or []),
|
||||
},
|
||||
"stats": {
|
||||
"total_scored": profile.total_scored,
|
||||
"total_kept": profile.total_kept,
|
||||
"total_dropped": profile.total_dropped,
|
||||
"accuracy_estimate": profile.accuracy_estimate,
|
||||
},
|
||||
"created_at": profile.created_at.isoformat() if profile.created_at else None,
|
||||
"updated_at": profile.updated_at.isoformat() if profile.updated_at else None,
|
||||
}
|
||||
Reference in New Issue
Block a user