""" التعامل مع MongoDB Atlas: تسجيل المقيّمين، حفظ التقييمات، وقراءة الإحصائيات. """ from __future__ import annotations import logging from datetime import datetime, timezone from functools import lru_cache import certifi from pymongo import MongoClient from pymongo.collection import Collection from pymongo.database import Database from app import config logger = logging.getLogger(__name__) @lru_cache(maxsize=1) def get_database() -> Database: if not config.MONGODB_URI: raise RuntimeError( "متغير البيئة MONGODB_URI غير مُعرَّف." ) uri = config.MONGODB_URI use_tls = "mongodb+srv" in uri.lower() kwargs = {"tlsCAFile": certifi.where()} if use_tls else {} client = MongoClient(uri, serverSelectionTimeoutMS=5000, **kwargs) client.admin.command("ping") return client[config.MONGODB_DB_NAME] def _evaluators_collection() -> Collection: return get_database()[config.EVALUATORS_COLLECTION] def _evaluations_collection() -> Collection: return get_database()[config.EVALUATIONS_COLLECTION] # ============================================================ # المقيّمون # ============================================================ def register_evaluator(name: str, email: str) -> None: """تسجيل/تحديث بيانات المقيّم (upsert حسب البريد الإلكتروني).""" _evaluators_collection().update_one( {"email": email}, { "$set": {"name": name, "email": email}, "$setOnInsert": {"created_at": datetime.now(timezone.utc)}, }, upsert=True, ) # ============================================================ # التقييمات # ============================================================ def save_evaluation( evaluator_email: str, query_definition: str, results: list[dict], ) -> None: """ حفظ نتيجة استعلام واحد مع تقييم المقيّم لكل نتيجة. شكل `results` المتوقع: [ { "word": "...", "definition": "...", "retrieval_score": 0.83, "rerank_score": 4.12, "rank": 1, "is_correct": True, }, ... ] """ _evaluations_collection().insert_one( { "evaluator_email": evaluator_email, "query_definition": query_definition, "results": results, "created_at": datetime.now(timezone.utc), } ) # ============================================================ # الإحصائيات # ============================================================ def get_stats(evaluator_email: str | None = None) -> dict: """ إحصائيات مجمَّعة: عدد الاستعلامات، عدد النتائج الصحيحة، وعدد الخاطئة. لو تم تمرير `evaluator_email`، تُحسب الإحصائيات لذلك المقيّم فقط. """ query: dict = {} if evaluator_email: query["evaluator_email"] = evaluator_email cursor = _evaluations_collection().find(query, {"results": 1}) total_queries = 0 correct = 0 incorrect = 0 for doc in cursor: total_queries += 1 for r in doc.get("results", []): if r.get("is_correct"): correct += 1 else: incorrect += 1 return { "total_queries": total_queries, "correct": correct, "incorrect": incorrect, } # ============================================================ # سجلّ الاستعلامات # ============================================================ def log_search(query: str, path: str, results: list[dict]) -> None: """ يُخزِّن سجل بحث بعد ظهور النتائج للمستخدم مباشرة. path: "ROOT" أو "DEFINITION" results: قائمة بالنتائج الظاهرة فعلاً للمستخدم، كل عنصر: {"rank": int, "word": str, "definition": str} يعمل بصمت — لو فشل الاتصال بـMongoDB لا يوقف التطبيق. """ try: db = get_database() db["search_logs"].insert_one({ "timestamp": datetime.now(timezone.utc), "query": query, "path": path, "results": results, }) except Exception as exc: logger.warning("MongoDB log_search failed: %s", exc)