Initial commit
This commit is contained in:
117
hsbg_ai/backend/ai/learning/feedback_processor.py
Normal file
117
hsbg_ai/backend/ai/learning/feedback_processor.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""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,
|
||||
}
|
||||
Reference in New Issue
Block a user