""" 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, }