from __future__ import annotations import json import uuid from collections import Counter from datetime import datetime, timezone from pathlib import Path from typing import Any from config import CLARIFICATION_LOG_PATH, USAGE_LOG_PATH def record_usage_event( question: str, answer: str, status: str, clause_eval: dict[str, Any] | None = None, ) -> None: clause_eval = clause_eval or {} record = { "event_id": str(uuid.uuid4()), "created_at": datetime.now(timezone.utc).isoformat(), "question": (question or "").strip(), "status": status, "answer_chars": len(answer or ""), "document_ids": clause_eval.get("document_ids", [])[:8], "source_ids": clause_eval.get("source_ids", [])[:8], "scores": clause_eval.get("scores", [])[:8], "top_source": (clause_eval.get("source_ids", []) or [""])[0] if clause_eval.get("source_ids") else "", "top_document": (clause_eval.get("document_ids", []) or [""])[0] if clause_eval.get("document_ids") else "", } _append_jsonl(USAGE_LOG_PATH, record) def read_usage_events(limit: int | None = None) -> list[dict[str, Any]]: records = _read_jsonl(USAGE_LOG_PATH) if limit is None: return records return records[-limit:] def read_clarification_events(limit: int | None = None) -> list[dict[str, Any]]: records = _read_jsonl(CLARIFICATION_LOG_PATH) if limit is None: return records return records[-limit:] def record_clarification_event( event_type: str, source_question: str = "", selected_key: str = "", resolved_question: str = "", options: dict[str, str] | None = None, raw_message: str = "", ) -> None: record = { "event_id": str(uuid.uuid4()), "created_at": datetime.now(timezone.utc).isoformat(), "event_type": event_type, "source_question": (source_question or "").strip(), "selected_key": str(selected_key or ""), "resolved_question": (resolved_question or "").strip(), "raw_message": (raw_message or "").strip(), "options": options or {}, } _append_jsonl(CLARIFICATION_LOG_PATH, record) def usage_summary() -> dict[str, Any]: records = read_usage_events() statuses = Counter(str(record.get("status", "unknown")) for record in records) top_sources = Counter(str(record.get("top_source", "")) for record in records if record.get("top_source")) top_documents = Counter(str(record.get("top_document", "")) for record in records if record.get("top_document")) recent = records[-10:] return { "total_events": len(records), "status_counts": dict(statuses.most_common()), "top_sources": top_sources.most_common(8), "top_documents": top_documents.most_common(8), "recent": recent, } def _append_jsonl(path: Path, record: dict[str, Any]) -> None: try: path.parent.mkdir(parents=True, exist_ok=True) with path.open("a", encoding="utf-8") as file: file.write(json.dumps(record, ensure_ascii=False) + "\n") except Exception: pass def _read_jsonl(path: Path) -> list[dict[str, Any]]: if not path.exists(): return [] records = [] try: with path.open("r", encoding="utf-8") as file: for line in file: line = line.strip() if not line: continue try: records.append(json.loads(line)) except json.JSONDecodeError: continue except Exception: return [] return records