""" app/services/feedback_service.py Service zum Loggen von Suchanfragen und Feedback (WP-04c). Speichert Daten als JSONL für späteres Self-Tuning (WP-08). """ import json import os import time from pathlib import Path from typing import Dict, Any, List from app.models.dto import QueryRequest, QueryResponse, FeedbackRequest # Pfad für Logs (lokal auf dem Beelink/PC) LOG_DIR = Path("data/logs") SEARCH_LOG_FILE = LOG_DIR / "search_history.jsonl" FEEDBACK_LOG_FILE = LOG_DIR / "feedback.jsonl" def _ensure_log_dir(): if not LOG_DIR.exists(): os.makedirs(LOG_DIR, exist_ok=True) def log_search(req: QueryRequest, res: QueryResponse): """ Speichert den "Snapshot" der Suche. WICHTIG: Wir speichern die Scores (Breakdown), damit wir später wissen, warum das System so entschieden hat. """ _ensure_log_dir() # Wir reduzieren die Datenmenge etwas (z.B. keine vollen Texte) hits_summary = [] for hit in res.results: # Falls Explanation an war, speichern wir den Breakdown, sonst die Scores breakdown = None if hit.explanation and hit.explanation.breakdown: breakdown = hit.explanation.breakdown.model_dump() hits_summary.append({ "node_id": hit.node_id, "note_id": hit.note_id, "total_score": hit.total_score, "breakdown": breakdown, # Wichtig für Training! "rank_semantic": hit.semantic_score, "rank_edge": hit.edge_bonus }) entry = { "timestamp": time.time(), "query_id": res.query_id, "query_text": req.query, "mode": req.mode, "top_k": req.top_k, "hits": hits_summary } try: with open(SEARCH_LOG_FILE, "a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") except Exception as e: print(f"ERROR logging search: {e}") def log_feedback(fb: FeedbackRequest): """ Speichert das User-Feedback. """ _ensure_log_dir() entry = { "timestamp": time.time(), "query_id": fb.query_id, "node_id": fb.node_id, "score": fb.score, "comment": fb.comment } try: with open(FEEDBACK_LOG_FILE, "a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") except Exception as e: print(f"ERROR logging feedback: {e}")