""" Feedback storage for the Vineyard Advisor chatbot. Logs user feedback (thumbs up/down, flags) to a JSON-lines file. Each entry captures the query, response, tool results, rules applied, and the user's feedback action. """ from __future__ import annotations import json import logging from datetime import datetime, timezone from pathlib import Path from typing import Optional from config.settings import DATA_DIR logger = logging.getLogger(__name__) FEEDBACK_FILE = DATA_DIR / "advisor_feedback.jsonl" def log_feedback( query: str, response: str, feedback: str, confidence: str = "", sources: Optional[list[str]] = None, tool_calls: Optional[list[dict]] = None, rule_violations: Optional[list[dict]] = None, response_mode: str = "", comment: str = "", ) -> None: """Append a feedback entry to the JSONL file. Parameters ---------- query : str The user's original question. response : str The chatbot's response text. feedback : str One of: "thumbs_up", "thumbs_down", "flag_incorrect". confidence, sources, tool_calls, rule_violations, response_mode : Metadata from the ChatResponse. comment : str Optional free-text comment from the user. """ entry = { "timestamp": datetime.now(tz=timezone.utc).isoformat(), "query": query, "response": response[:500], # truncate for storage "feedback": feedback, "confidence": confidence, "sources": sources or [], "tool_calls": [ {"name": tc.get("name", ""), "args": tc.get("args", {})} for tc in (tool_calls or []) ], "rule_violations": rule_violations or [], "response_mode": response_mode, "comment": comment, } try: FEEDBACK_FILE.parent.mkdir(parents=True, exist_ok=True) with open(FEEDBACK_FILE, "a") as f: f.write(json.dumps(entry, default=str) + "\n") logger.info("Feedback logged: %s for query: %s", feedback, query[:50]) except Exception as exc: logger.warning("Failed to log feedback: %s", exc) def load_feedback(limit: int = 100) -> list[dict]: """Load recent feedback entries.""" if not FEEDBACK_FILE.exists(): return [] entries = [] try: with open(FEEDBACK_FILE) as f: for line in f: line = line.strip() if line: entries.append(json.loads(line)) except Exception as exc: logger.warning("Failed to load feedback: %s", exc) return entries[-limit:] def feedback_summary() -> dict: """Return a summary of feedback stats.""" entries = load_feedback(limit=10000) if not entries: return {"total": 0} return { "total": len(entries), "thumbs_up": sum(1 for e in entries if e.get("feedback") == "thumbs_up"), "thumbs_down": sum(1 for e in entries if e.get("feedback") == "thumbs_down"), "flagged": sum(1 for e in entries if e.get("feedback") == "flag_incorrect"), }