"""Système d'apprentissage par feedback utilisateur.""" import json import os from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from backend.database.models import AIDecision, LearningFeedback import structlog log = structlog.get_logger() class FeedbackProcessor: """ Traite les retours utilisateur pour améliorer l'IA. Workflow: 1. Utilisateur évalue une décision (bon/mauvais/neutre) 2. Le feedback est persisté en DB 3. Un buffer accumule les feedbacks 4. Quand le buffer est plein → export JSON pour entraînement futur """ def __init__(self, settings): self.settings = settings self._buffer: list[dict] = [] self._trained_count = 0 async def record_feedback( self, db: AsyncSession, decision_id: int, rating: str, better_action: dict | None = None, comment: str | None = None, ) -> LearningFeedback: """Enregistre un feedback et met à jour la décision associée.""" decision = await db.get(AIDecision, decision_id) if not decision: raise ValueError(f"Décision {decision_id} introuvable") # Créer le feedback fb = LearningFeedback( decision_id=decision_id, rating=rating, better_action=better_action, comment=comment, ) db.add(fb) # Mettre à jour la décision avec le résultat decision.outcome_rating = {"good": 1, "neutral": 0, "bad": -1}.get(rating, 0) decision.user_feedback = comment if better_action: decision.better_decision = better_action await db.flush() # Buffer pour entraînement self._buffer.append({ "decision_id": decision_id, "game_state": decision.game_state, "recommendation": decision.recommendation, "rating": rating, "better_action": better_action, "ts": datetime.utcnow().isoformat(), }) log.info("feedback.recorded", id=decision_id, rating=rating, buffer=len(self._buffer)) # Auto-flush si buffer plein if (self.settings.learning_auto_save and len(self._buffer) >= self.settings.learning_batch_size): await self._flush_buffer() return fb async def _flush_buffer(self): """Exporte le buffer en JSON pour entraînement.""" if not self._buffer: return os.makedirs("data/learning/feedback", exist_ok=True) fname = f"data/learning/feedback/batch_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json" try: import aiofiles async with aiofiles.open(fname, "w") as f: await f.write(json.dumps(self._buffer, indent=2, ensure_ascii=False)) self._trained_count += len(self._buffer) log.info("feedback.batch_saved", count=len(self._buffer), file=fname) except Exception as e: log.error("feedback.flush_failed", error=str(e)) finally: self._buffer.clear() async def force_flush(self): """Flush manuel du buffer.""" await self._flush_buffer() async def get_stats(self, db: AsyncSession) -> dict: """Statistiques globales du système d'apprentissage.""" result = await db.execute(select(LearningFeedback)) feedbacks = result.scalars().all() total = len(feedbacks) good = sum(1 for f in feedbacks if f.rating == "good") bad = sum(1 for f in feedbacks if f.rating == "bad") neutral = total - good - bad return { "total": total, "good": good, "bad": bad, "neutral": neutral, "good_rate": round(good / total * 100, 1) if total > 0 else 0.0, "trained": self._trained_count, "buffer_pending": len(self._buffer), "learning_enabled": self.settings.learning_enabled, }