| import json |
|
|
| from server.retriever import retrieve_with_scores |
| from server.utils import setup_logger |
|
|
| logger = setup_logger(__name__) |
|
|
|
|
| def compute_precision_at_k(query: str, retrieved_chunks: list[dict], ground_truth: dict, k: int = 5) -> float: |
| """ |
| Precision@K = (relevant chunks in top-K) / K |
| |
| A chunk is "relevant" if: |
| - Its source matches ground_truth["relevant_sources"], OR |
| - Its content contains any keyword from ground_truth["relevant_chunk_keywords"] |
| |
| Return float between 0 and 1. |
| """ |
| relevant_sources = ground_truth.get("relevant_sources", []) |
| keywords = ground_truth.get("relevant_chunk_keywords", []) |
|
|
| top_k = retrieved_chunks[:k] |
| relevant_count = 0 |
|
|
| for chunk in top_k: |
| source_match = chunk.get("source", "") in relevant_sources |
| keyword_match = any( |
| kw.lower() in chunk.get("content", "").lower() for kw in keywords |
| ) |
| if source_match or keyword_match: |
| relevant_count += 1 |
|
|
| precision = relevant_count / k if k > 0 else 0.0 |
| return round(precision, 4) |
|
|
|
|
| def run_batch_precision_eval(eval_pairs_path: str, k: int = 5) -> dict: |
| """ |
| Run precision@K for all queries in eval_pairs.json. |
| Return dict with mean_precision_at_k and per_query_results. |
| """ |
| with open(eval_pairs_path, "r") as f: |
| eval_pairs = json.load(f) |
|
|
| per_query_results = [] |
|
|
| for pair in eval_pairs: |
| query = pair["query"] |
| retrieved = retrieve_with_scores(query, k=k) |
| precision = compute_precision_at_k(query, retrieved, pair, k=k) |
| retrieved_sources = [c["source"] for c in retrieved] |
|
|
| per_query_results.append({ |
| "query": query, |
| "precision_at_k": precision, |
| "retrieved_sources": retrieved_sources, |
| }) |
|
|
| logger.info(f"P@{k}={precision:.2f} | {query[:60]}...") |
|
|
| mean_precision = sum(r["precision_at_k"] for r in per_query_results) / len(per_query_results) |
|
|
| return { |
| "mean_precision_at_k": round(mean_precision, 4), |
| "per_query_results": per_query_results, |
| } |
|
|
|
|
| def run_batch_precision_eval_multi_k(eval_pairs_path: str, ks: list[int]) -> dict: |
| """ |
| Run precision evaluation for multiple K values. |
| Return a dict keyed by precision@K. |
| """ |
| results = {} |
| for k in ks: |
| results[f"precision@{k}"] = run_batch_precision_eval(eval_pairs_path, k=k) |
| return results |
|
|