""" Automated audit script for Inframat-X RAG chatbot. Evaluates Hit Rate@8 (At least one correct document found). """ import os import re import json import time import pandas as pd from datetime import datetime from typing import Tuple, Optional, Callable def load_sources_map(csv_path="sources.csv"): if not os.path.exists(csv_path): return {} df = pd.read_csv(csv_path).fillna("") df.columns = df.columns.str.strip() src_map = {} for _, r in df.iterrows(): raw_key = str(r.get("source_key", "")).strip().lower() fname = os.path.basename(raw_key).lower().strip() raw_name = str(r.get("name", "")).strip().lower() raw_id = str(r.get("id", "")).strip() clean_id = raw_id.replace("PAPER_", "").replace("paper_", "").lstrip("0") if not clean_id: clean_id = "0" if fname: src_map[fname.replace('.pdf', '')] = clean_id if raw_name: src_map[raw_name.replace('.pdf', '')] = clean_id src_map[raw_id.lower()] = clean_id return src_map def extract_retrieved_ids(full_output: str) -> list: if not full_output: return [] sources_match = re.search(r'\*\*Sources:\*\*(.*)', full_output) if sources_match: ids = re.findall(r'\[(\d+)\]', sources_match.group(1)) return list(set(ids)) ref_section = re.search(r'### References\s*\n(.*?)(?:\n\s*\n|$)', full_output, re.DOTALL) if ref_section: ids = re.findall(r'\[(\d+)\]', ref_section.group(1)) return list(set(ids)) return [] def calculate_hit_rate(retrieved_ids: list, gold_docs: list, sources_map: dict) -> float: """ Checks if AT LEAST ONE expected document was successfully retrieved. Returns 1.0 (Success) or 0.0 (Fail). """ if not gold_docs: return 0.0 expected_ids = set() for g in gold_docs: g_clean = g.lower().replace('.pdf', '').strip() if g_clean in sources_map: expected_ids.add(sources_map[g_clean]) else: nums = re.findall(r'\d+', g_clean) if nums: expected_ids.add(nums[-1].lstrip('0') or '0') # YOUR LOGIC: Did we find at least one? for e in expected_ids: if e in retrieved_ids: return 1.0 # 100% Success for this question return 0.0 # 0% Success def run_audit( rag_reply_func, gold_csv_path: str = "gold.csv", output_base_dir: Optional[str] = None, progress_callback: Optional[Callable[[str, int, int], None]] = None, k_retrieval: int = 10 ) -> Tuple[str, str]: if not os.path.exists(gold_csv_path): return f"❌ Error: Could not find {gold_csv_path}.", "" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") if output_base_dir is None: output_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"Audit_{timestamp}") os.makedirs(output_base_dir, exist_ok=True) df = pd.read_csv(gold_csv_path) total_questions = len(df) jsonl_path = os.path.join(output_base_dir, "rag_logs.jsonl") sources_map = load_sources_map("sources.csv") total_hit_rate = 0.0 processed_count = 0 if progress_callback: progress_callback("Gold Set Benchmark", 0, total_questions) with open(jsonl_path, "w", encoding="utf-8") as log_file: for idx, row in df.iterrows(): question = row['question'] raw_gold = str(row['relevant_docs']).split(';') gold_docs = [p.strip() for p in raw_gold if p.strip()] raw_output = rag_reply_func(question, k=k_retrieval) retrieved_ids = extract_retrieved_ids(raw_output) # Use the new Hit Rate logic hit_score = calculate_hit_rate(retrieved_ids, gold_docs, sources_map) total_hit_rate += hit_score processed_count += 1 log_entry = { "question_id": idx + 1, "question": question, "gold_documents_raw": gold_docs, "retrieved_ids": retrieved_ids, "hit_score": hit_score } log_file.write(json.dumps(log_entry) + "\n") if progress_callback: progress_callback("Gold Set Benchmark", processed_count, total_questions) time.sleep(3) average_hit_rate = total_hit_rate / processed_count if processed_count > 0 else 0.0 summary_path = os.path.join(output_base_dir, "benchmark_summary.txt") with open(summary_path, "w", encoding="utf-8") as f: f.write("INFRAMAT-X RAG BENCHMARK REPORT\n") f.write(f"Run completed at: {timestamp}\n") f.write(f"Questions processed: {processed_count}\n") f.write(f"Average Hit Rate@10: {average_hit_rate:.4f}\n") summary_str = ( f"✅ Benchmark finished!\n" f"📁 Logs saved to: {jsonl_path}\n" f"📊 Average Hit Rate@10: {average_hit_rate:.4f}\n" ) import shutil zip_path = shutil.make_archive(output_base_dir, 'zip', output_base_dir) return summary_str, zip_path