HNU-Final / scripts /run_audit_diagnostics.py
Mokhtar
feat(production): complete phase 1-4 refactor with hybrid retrieval CAG architecture, E5 dense embeddings, grounding validator, precomputed cold-start index, and 24/24 passing system tests
d54db1a
Raw History Blame Contribute Delete
19 kB
# scripts/run_audit_diagnostics.py โ€” Comprehensive Audit & Verification Script
import os
import sys
import json
import time
from pathlib import Path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from knowledge.loader import KnowledgeLoader
from knowledge.chunker import MarkdownChunker
from knowledge.index import HybridIndex
from knowledge.retrieval import HybridRetriever
from knowledge.context import ContextBuilder
from routing.query_router import QueryRouter
from routing.provider_router import ProviderRouter
from policies.university_rules import PolicyEngine, COORDINATION_DISCLAIMER
from policies.language import LanguageEngine
from team.developers import DeveloperRegistry
from cache.manager import CacheManager
from cache.keys import HNU_KNOWLEDGE_VERSION
BASE_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = BASE_DIR / "university_data_arabic"
def audit_section_2_inventory():
print("\n==================================================")
print("2. VERIFY DOCUMENT COVERAGE (INVENTORY)")
print("==================================================")
loader = KnowledgeLoader(data_dir=str(DATA_DIR))
docs = loader.load_documents()
summary = loader.get_summary()
md_files = list(DATA_DIR.rglob("*.md"))
txt_files = list(DATA_DIR.rglob("*.txt"))
other_files = [p for p in DATA_DIR.rglob("*") if p.is_file() and p.suffix not in [".md", ".txt"]]
print(f"MD files found: {len(md_files)}")
print(f"TXT files found: {len(txt_files)}")
print(f"Other supported/ignored files: {len(other_files)}")
print(f"Total source files: {len(md_files) + len(txt_files) + len(other_files)}")
print(f"Total indexed: {summary['indexed_count']}")
print(f"Total skipped: {summary['skipped_count']}")
print(f"Reasons for skipped files: {summary['skipped_files']}")
print("\nExact indexed filenames list:")
indexed_names = sorted([d.metadata.filename for d in docs])
for idx, name in enumerate(indexed_names, 1):
print(f" {idx:02d}. {name}")
data_dir_files = list((DATA_DIR / "data").glob("*"))
print(f"\nuniversity_data_arabic/data/ files count: {len(data_dir_files)}")
assert "extracted_table.md" in indexed_names, "extracted_table.md MUST be indexed!"
assert "website_developers.md" in indexed_names, "website_developers.md MUST be indexed!"
print("โœ… Full recursive document coverage verified!")
return docs, indexed_names
def audit_section_3_chunking(docs):
print("\n==================================================")
print("3. VERIFY CHUNK COVERAGE")
print("==================================================")
chunker = MarkdownChunker()
chunks = chunker.chunk_all(docs)
chunk_sizes = [len(c.text) for c in chunks]
avg_size = sum(chunk_sizes) / len(chunk_sizes) if chunk_sizes else 0
min_size = min(chunk_sizes) if chunk_sizes else 0
max_size = max(chunk_sizes) if chunk_sizes else 0
print(f"Total documents: {len(docs)}")
print(f"Total chunks: {len(chunks)}")
print(f"Average chunk size: {round(avg_size, 1)} chars")
print(f"Minimum chunk size: {min_size} chars")
print(f"Maximum chunk size: {max_size} chars")
print("\nRandom Chunk Samples:")
samples = [chunks[0], chunks[len(chunks)//2], chunks[-1]]
for idx, sample in enumerate(samples, 1):
print(f"\n--- Sample {idx} ---")
print(f"Document: {sample.filename}")
print(f"Section: {sample.section}")
print(f"Heading Context: {sample.heading_context}")
print(f"Chunk Text Preview:\n{sample.text[:200]}...")
return chunks
def audit_section_4_multi_fact(retriever):
print("\n==================================================")
print("4. TEST MULTI-FACT RETRIEVAL")
print("==================================================")
test_queries = [
("A. single fact", "ู…ุตุงุฑูŠู ูƒู„ูŠุฉ ุทุจ ุงู„ุฃุณู†ุงู† ูƒุงู…ุŸ"),
("B. two facts from same doc", "ู…ุตุงุฑูŠู ุทุจ ุงู„ุฃุณู†ุงู† ูƒุงู… ูˆู‡ู„ ูŠู†ูุน ุชู‚ุณูŠุทุŸ"),
("C. two facts from diff docs", "ู…ุง ู‡ูŠ ู…ุตุงุฑูŠู ุงู„ุทุจ ุงู„ุจุดุฑูŠ ูˆู…ุง ู‡ูŠ ุงู„ุฃูˆุฑุงู‚ ุงู„ู…ุทู„ูˆุจุฉ ู„ู„ุชู‚ุฏูŠู…ุŸ"),
("D. list questions", "ู…ุง ู‡ูŠ ุงู„ุจุฑุงู…ุฌ ุงู„ุฏุฑุงุณูŠุฉ ุงู„ู…ุชุงุญุฉ ููŠ ูƒู„ูŠุฉ ุงู„ู‡ู†ุฏุณุฉุŸ"),
("E. 'all' questions", "ู‡ุงุช ูƒู„ ุงู„ู…ุตุงุฑูŠู ู„ูƒู„ ุงู„ูƒู„ูŠุงุช ุจุงู„ูƒุงู…ู„"),
("F. comparison questions", "ู‚ุงุฑู† ุจูŠู† ู…ุตุงุฑูŠู ุทุจ ุงู„ุฃุณู†ุงู† ูˆู…ุตุงุฑูŠู ุงู„ู‡ู†ุฏุณุฉ"),
("G. procedural questions", "ู…ุง ู‡ูŠ ุฎุทูˆุงุช ูˆุณุฏุงุฏ ุงู„ู…ุตุฑูˆูุงุช ุงู„ุฏุฑุงุณูŠุฉ ู„ู„ุทู„ุงุจ ุงู„ุฌุฏุฏุŸ")
]
for cat, q in test_queries:
print(f"\nCategory: {cat}")
print(f"Query: '{q}'")
chunks, diag = retriever.retrieve(q, intent="multi_fact" if "all" in cat or "two" in cat else "general_information")
unique_docs = set([c.filename for c in chunks])
print(f"Retrieved chunk IDs: {[c.chunk_id for c in chunks[:5]]}")
print(f"Document names: {list(unique_docs)}")
print(f"Sections: {list(set([c.section for c in chunks[:5]]))}")
print(f"Unique relevant sources: {len(unique_docs)}")
print(f"Latency: {diag['latency_ms']}ms")
def audit_section_5_developers():
print("\n==================================================")
print("5. TEST DEVELOPER ENTITY COMPLETENESS")
print("==================================================")
devs = DeveloperRegistry.get_all()
print(f"Total developer entities: {len(devs)}")
assert len(devs) == 7, "Must be exactly 7 developers!"
for idx, d in enumerate(devs, 1):
links = ", ".join([f"{c['type']}: {c['url']}" for c in d.contact_methods]) if d.contact_methods else "None"
print(f" {idx}. {d.name} ({d.name_ar}) | Role: {d.role} | Contacts: {links}")
test_dev_queries = [
"ู…ูŠู† ุงู„ู…ุทูˆุฑูŠู† ุงู„ุณุจุนุฉุŸ",
"ู…ูŠู† ู…ุณุคูˆู„ ุนู† backendุŸ",
"ู…ูŠู† ุนู…ู„ ุงู„ุดุงุช ุจูˆุชุŸ",
"ู…ูŠู† ู…ุณุคูˆู„ ุนู† deploymentุŸ",
"ู…ูŠู† ู…ุณุคูˆู„ ุนู† frontendุŸ",
"ุฅุฒุงูŠ ุฃุชูˆุงุตู„ ู…ุน Mohammed MokhtarุŸ"
]
print("\nDeveloper Queries Evaluation:")
for q in test_dev_queries:
if "ุงู„ุณุจุนุฉ" in q or "ุงู„ู…ุทูˆุฑูŠู†" in q and "ู…ูŠู†" in q:
res = DeveloperRegistry.get_all()
elif "Mokhtar" in q:
res = DeveloperRegistry.search_by_name("Mokhtar")
elif "backend" in q:
res = DeveloperRegistry.search_by_responsibility("backend")
elif "frontend" in q:
res = DeveloperRegistry.search_by_responsibility("frontend")
elif "deployment" in q or "cloud" in q:
res = DeveloperRegistry.search_by_responsibility("cloud")
elif "ุงู„ุดุงุช ุจูˆุช" in q or "chatbot" in q:
res = DeveloperRegistry.search_by_responsibility("chatbot")
else:
res = DeveloperRegistry.get_all()
dev_names = [d.name for d in res]
print(f"Query: '{q}' -> Matched Devs: {dev_names}")
def audit_section_6_coordination():
print("\n==================================================")
print("6. TEST COORDINATION POLICY")
print("==================================================")
coord_queries = [
"ุงู„ุญุฏ ุงู„ุฃุฏู†ู‰ ู„ูƒู„ูŠุฉ ุงู„ุทุจ ูƒุงู…ุŸ",
"ุทุจ ู…ุญุชุงุฌ ู…ุฌู…ูˆุน ูƒุงู…ุŸ",
"What is the minimum score for medicine?",
"ู‡ู„ ุฏู‡ ุชู†ุณูŠู‚ ุฌุงู…ุนุฉ ุญู„ูˆุงู† ุงู„ุฃู‡ู„ูŠุฉ ุงู„ุฏุงุฎู„ูŠุŸ"
]
for q in coord_queries:
is_coord = PolicyEngine.is_coordination_query(q)
formatted = PolicyEngine.apply_coordination_rules("ุจูŠุงู† ุงู„ุชู†ุณูŠู‚ ุงู„ุฎุงุต ุจุงู„ุทุจ ู‡ูˆ 80%.", q)
disclaimer_present = COORDINATION_DISCLAIMER in formatted
print(f"Query: '{q}' | Is Coord Query: {is_coord} | Disclaimer Present: {disclaimer_present}")
def audit_section_7_language():
print("\n==================================================")
print("7. TEST LANGUAGE BEHAVIOR")
print("==================================================")
lang_queries = [
"What are the fees for dentistry?",
"ู…ุง ู‡ูŠ ู…ุตุฑูˆูุงุช ูƒู„ูŠุฉ ุทุจ ุงู„ุฃุณู†ุงู†ุŸ",
"ู…ุตุงุฑูŠู ุทุจ ุงู„ุฃุณู†ุงู† ูƒุงู…ุŸ",
"ุงูŠู‡ minimum score for medicine?"
]
for q in lang_queries:
detected = LanguageEngine.detect_language(q)
print(f"Query: '{q}' -> Lang: {detected['language']} | Style: {detected['style']}")
def audit_section_8_cache():
print("\n==================================================")
print("8. CACHE AUDIT")
print("==================================================")
cache = CacheManager(cache_file_path=BASE_DIR / "cache" / "test_responses.json")
# Store query
q1 = "ู…ุตุงุฑูŠู ูƒู„ูŠุฉ ุงู„ุทุจ"
q2_en = "What are the fees for medicine?"
cache.put(q1, "ุฅุฌุงุจุฉ ุนุฑุจูŠุฉ", language="ar")
cache.put(q2_en, "English response", language="en")
hit_ar = cache.get(q1, language="ar")
hit_en = cache.get(q2_en, language="en")
print(f"Arabic Query Hit: {hit_ar is not None}")
print(f"English Query Hit: {hit_en is not None}")
print(f"Knowledge Version: {HNU_KNOWLEDGE_VERSION}")
def build_section_11_evaluation_questions():
print("\n==================================================")
print("11. BUILD REALISTIC EVALUATION SET (40 QUESTIONS)")
print("==================================================")
questions = [
# 10 Simple Factual
{"question": "ุฃูŠู† ุชู‚ุน ุฌุงู…ุนุฉ ุญู„ูˆุงู† ุงู„ุฃู‡ู„ูŠุฉุŸ", "category": "simple_factual", "expected_topics": ["location", "address"]},
{"question": "ู…ุง ู‡ูˆ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุงู„ุฑุณู…ูŠ ู„ู„ุชูˆุงุตู„ ู…ุน ุงู„ุฌุงู…ุนุฉุŸ", "category": "simple_factual", "expected_topics": ["contact", "email"]},
{"question": "ู…ุง ู‡ูŠ ุงู„ุฃูˆุฑุงู‚ ุงู„ู…ุทู„ูˆุจุฉ ู„ู„ุชู‚ุฏูŠู… ููŠ ุงู„ุฌุงู…ุนุฉุŸ", "category": "simple_factual", "expected_topics": ["documents", "admission"]},
{"question": "ู…ุง ู‡ูˆ ุฑุงุจุท ู…ูˆู‚ุน ุงู„ุชู‚ุฏูŠู… ุงู„ุฅู„ูƒุชุฑูˆู†ูŠุŸ", "category": "simple_factual", "expected_topics": ["link", "admission"]},
{"question": "ู‡ู„ ุชู…ู†ุญ ุงู„ุฌุงู…ุนุฉ ุฏุฑุฌุงุช ุนู„ู…ูŠุฉ ู…ุนุชู…ุฏุฉุŸ", "category": "simple_factual", "expected_topics": ["accreditation"]},
{"question": "ู…ุง ู‡ูŠ ูƒู„ูŠุงุช ุงู„ู‚ุทุงุน ุงู„ุทุจูŠ ููŠ ุงู„ุฌุงู…ุนุฉุŸ", "category": "simple_factual", "expected_topics": ["faculties", "medicine"]},
{"question": "ู…ุง ู‡ูˆ ู†ุธุงู… ุงู„ุฏุฑุงุณุฉ ุจุฌุงู…ุนุฉ ุญู„ูˆุงู† ุงู„ุฃู‡ู„ูŠุฉุŸ", "category": "simple_factual", "expected_topics": ["credit_hours"]},
{"question": "ู…ุง ู‡ูŠ ุงู„ุฃูˆุฑุงู‚ ุงู„ู…ุทู„ูˆุจุฉ ู„ู„ุทู„ุงุจ ุงู„ูˆุงูุฏูŠู†ุŸ", "category": "simple_factual", "expected_topics": ["foreign_students"]},
{"question": "ู‡ู„ ุชุชูˆูุฑ ูˆุณุงุฆู„ ู…ูˆุงุตู„ุงุช ู„ู„ุฌุงู…ุนุฉุŸ", "category": "simple_factual", "expected_topics": ["transportation"]},
{"question": "ู…ุง ู‡ูŠ ุงู„ุณุนุฉ ุงู„ุทู„ุงุจูŠุฉ ู„ูƒู„ูŠุฉ ุงู„ู‡ู†ุฏุณุฉุŸ", "category": "simple_factual", "expected_topics": ["capacity"]},
# 8 Multi-fact
{"question": "ู…ุง ู‡ูŠ ู…ุตุงุฑูŠู ูƒู„ูŠุฉ ุทุจ ุงู„ุฃุณู†ุงู† ูˆู‡ู„ ูŠู†ูุน ุชู‚ุณูŠุทุŸ", "category": "multi_fact", "expected_topics": ["tuition", "installment"]},
{"question": "ู…ุง ู‡ูŠ ุดุฑูˆุท ุงู„ุชู‚ุฏูŠู… ูˆู…ุง ุงู„ุฃูˆุฑุงู‚ ุงู„ู…ุทู„ูˆุจุฉ ู„ู„ุทู„ุงุจ ุงู„ุฌุฏุฏุŸ", "category": "multi_fact", "expected_topics": ["conditions", "documents"]},
{"question": "ู…ุง ู‡ูŠ ู…ุตุงุฑูŠู ุงู„ู‡ู†ุฏุณุฉ ูˆุงู„ุญุฏ ุงู„ุฃุฏู†ู‰ ู„ู„ุชู†ุณูŠู‚ุŸ", "category": "multi_fact", "expected_topics": ["tuition", "coordination"]},
{"question": "ู…ุง ู‡ูŠ ุจุฑุงู…ุฌ ูƒู„ูŠุฉ ุนู„ูˆู… ุงู„ุญุงุณุจ ูˆู…ุง ู‡ูŠ ู…ุตุฑูˆูุงุชู‡ุงุŸ", "category": "multi_fact", "expected_topics": ["programs", "tuition"]},
{"question": "ู…ุง ู‡ูŠ ุฎุทูˆุงุช ูˆุณุฏุงุฏ ุงู„ู…ุตุฑูˆูุงุช ูˆูƒูŠููŠุฉ ุชุฃูƒูŠุฏ ุงู„ุชู‚ุฏูŠู…ุŸ", "category": "multi_fact", "expected_topics": ["payment", "confirmation"]},
{"question": "ู…ุง ู‡ูŠ ุงู„ู…ู†ุญ ุงู„ู…ุชุงุญุฉ ูˆู…ุง ู‡ูŠ ู†ุณุจุฉ ุงู„ุฎุตู… ู„ู„ุทู„ุงุจ ุงู„ู…ุชููˆู‚ูŠู†ุŸ", "category": "multi_fact", "expected_topics": ["scholarships", "discounts"]},
{"question": "ู…ุง ู‡ูŠ ู…ุตุงุฑูŠู ุงู„ุทุจ ุงู„ุจุดุฑูŠ ูˆูƒูŠููŠุฉ ุงู„ุชูˆุงุตู„ ู…ุน ุดุคูˆู† ุงู„ุทู„ุงุจุŸ", "category": "multi_fact", "expected_topics": ["medicine_tuition", "student_affairs"]},
{"question": "ู…ุง ู‡ูŠ ุงู„ูƒู„ูŠุงุช ุงู„ู…ุชุงุญุฉ ูˆู…ุง ู‡ูŠ ู…ุตุฑูˆูุงุช ูƒู„ ูƒู„ูŠุฉุŸ", "category": "multi_fact", "expected_topics": ["faculties", "all_tuition"]},
# 5 Tuition
{"question": "ู…ุตุงุฑูŠู ูƒู„ูŠุฉ ุงู„ู‡ู†ุฏุณุฉ ูƒุงู…ุŸ", "category": "tuition", "expected_topics": ["engineering_tuition"]},
{"question": "ู…ุตุฑูˆูุงุช ูƒู„ูŠุฉ ุงู„ุนู„ุงุฌ ุงู„ุทุจูŠุนูŠ ูƒุงู…ุŸ", "category": "tuition", "expected_topics": ["physical_therapy_tuition"]},
{"question": "ูƒู… ุชูƒู„ูุฉ ุฏุฑุงุณุฉ ุนู„ูˆู… ุงู„ุญุงุณุจ ูˆุชูƒู†ูˆู„ูˆุฌูŠุง ุงู„ู…ุนู„ูˆู…ุงุชุŸ", "category": "tuition", "expected_topics": ["cs_tuition"]},
{"question": "ู…ุง ู‡ูŠ ู…ุตุงุฑูŠู ูƒู„ูŠุฉ ุงู„ุทุจ ูˆุงู„ุฌุฑุงุญุฉุŸ", "category": "tuition", "expected_topics": ["medicine_tuition"]},
{"question": "ู…ุง ู‡ูŠ ู…ุตุงุฑูŠู ูƒู„ูŠุฉ ุงู„ุนู„ูˆู…ุŸ", "category": "tuition", "expected_topics": ["science_tuition"]},
# 5 Admission/Coordination
{"question": "ุงู„ุญุฏ ุงู„ุฃุฏู†ู‰ ู„ูƒู„ูŠุฉ ุงู„ุทุจ ูƒุงู…ุŸ", "category": "admission_coordination", "expected_topics": ["medicine_minimum"]},
{"question": "ุชู†ุณูŠู‚ ูƒู„ูŠุฉ ุงู„ู‡ู†ุฏุณุฉ ูƒู… ุจุงู„ู…ุฆุฉุŸ", "category": "admission_coordination", "expected_topics": ["engineering_coordination"]},
{"question": "ู…ุง ู‡ูˆ ุงู„ุญุฏ ุงู„ุฃุฏู†ู‰ ู„ู„ู‚ุจูˆู„ ุจูƒู„ูŠุงุช ุงู„ู‚ุทุงุน ุงู„ุทุจูŠุŸ", "category": "admission_coordination", "expected_topics": ["medical_sector_minimum"]},
{"question": "ู…ุง ู‡ูˆ ุชู†ุณูŠู‚ ูƒู„ูŠุฉ ุนู„ุงุฌ ุทุจูŠุนูŠุŸ", "category": "admission_coordination", "expected_topics": ["physical_therapy_minimum"]},
{"question": "ู‡ู„ ุชู†ุณูŠู‚ ุงู„ุฌุงู…ุนุฉ ุงู„ุฃู‡ู„ูŠุฉ ูŠุฎุชู„ู ุนู† ุงู„ุชู†ุณูŠู‚ ุงู„ุญูƒูˆู…ูŠุŸ", "category": "admission_coordination", "expected_topics": ["coordination_disclaimer"]},
# 5 Developer/Team
{"question": "ู…ูŠู† ุงู„ู…ุทูˆุฑูŠู† ุงู„ุณุจุนุฉ ุจุชูˆุน ุงู„ู…ูˆู‚ุน ุงู„ุฑุณู…ูŠูŠู†ุŸ", "category": "developer_team", "expected_topics": ["7_developers"]},
{"question": "ู…ูŠู† ู…ุณุคูˆู„ ุนู† ุชุทูˆูŠุฑ ุงู„ู€ backend ูˆุงู„ุดุงุช ุจูˆุชุŸ", "category": "developer_team", "expected_topics": ["backend_chatbot_dev"]},
{"question": "ู…ูŠู† ู…ุณุคูˆู„ ุนู† ุชุตู…ูŠู… ูˆุชุทูˆูŠุฑ ุงู„ู€ frontendุŸ", "category": "developer_team", "expected_topics": ["frontend_dev"]},
{"question": "ู…ูŠู† ู…ุณุคูˆู„ ุนู† ุงู„ุณูŠุฑูุฑุงุช ุงู„ู€ Cloud ูˆุงู„ู€ deploymentุŸ", "category": "developer_team", "expected_topics": ["cloud_deployment_dev"]},
{"question": "ุฅุฒุงูŠ ุฃุชูˆุงุตู„ ู…ุน ุงู„ู…ุทูˆุฑ Mohammed MokhtarุŸ", "category": "developer_team", "expected_topics": ["mokhtar_contact"]},
# 4 English
{"question": "What are the fees for dentistry at Helwan National University?", "category": "english", "expected_topics": ["dentistry_tuition"]},
{"question": "What is the minimum score required for medicine?", "category": "english", "expected_topics": ["medicine_minimum"]},
{"question": "Where is the university located and how to reach it?", "category": "english", "expected_topics": ["location"]},
{"question": "Who are the developers of the official website?", "category": "english", "expected_topics": ["developers"]},
# 3 Egyptian Arabic
{"question": "ู…ุตุงุฑูŠู ุฃุณู†ุงู† ูƒุงู… ูˆู‡ู„ ูŠู†ูุน ุงุฏูุน ุนู„ู‰ ุฃู‚ุณุงุทุŸ", "category": "egyptian_arabic", "expected_topics": ["dentistry_tuition", "installment"]},
{"question": "ู‡ูˆ ุงู„ุทุจ ุงู„ุจุดุฑูŠ ุจูŠุงุฎุฏ ู…ู† ู…ุฌู…ูˆุน ูƒุงู…ุŸ", "category": "egyptian_arabic", "expected_topics": ["medicine_minimum"]},
{"question": "ุงุฒุงูŠ ุงุฑูˆุญ ุงู„ุฌุงู…ุนุฉ ุจุงู„ู…ูˆุงุตู„ุงุชุŸ", "category": "egyptian_arabic", "expected_topics": ["transportation"]}
]
eval_file = BASE_DIR / "tests" / "evaluation_questions.json"
eval_file.parent.mkdir(parents=True, exist_ok=True)
with open(eval_file, "w", encoding="utf-8") as f:
json.dump(questions, f, ensure_ascii=False, indent=2)
print(f"โœ… Saved {len(questions)} evaluation questions to {eval_file}.")
return questions
def audit_section_12_retrieval_diagnostics(retriever, questions):
print("\n==================================================")
print("12. RETRIEVAL DIAGNOSTICS LOGGING")
print("==================================================")
router = QueryRouter()
context_bld = ContextBuilder()
diagnostics_log = []
for item in questions:
q = item["question"]
route = router.classify_query(q)
lang_info = LanguageEngine.detect_language(q)
start_time = time.time()
chunks, diag = retriever.retrieve(q, intent=route["intent"], filter_filename=route["filter_filename"])
latency_ms = diag["latency_ms"]
context_str = context_bld.build_context(chunks)
log_entry = {
"question": q,
"category": item["category"],
"intent": route["intent"],
"language": lang_info["language"],
"retrieval_latency_ms": latency_ms,
"retrieved_documents": list(set([c.filename for c in chunks])),
"retrieved_sections": list(set([c.section for c in chunks])),
"retrieved_chunk_ids": [c.chunk_id for c in chunks],
"context_size_chars": len(context_str)
}
diagnostics_log.append(log_entry)
log_file = BASE_DIR / "tests" / "diagnostics_output.json"
with open(log_file, "w", encoding="utf-8") as f:
json.dump(diagnostics_log, f, ensure_ascii=False, indent=2)
print(f"โœ… Diagnostics logged for {len(diagnostics_log)} questions to {log_file}.")
def main():
docs, filenames = audit_section_2_inventory()
chunks = audit_section_3_chunking(docs)
index = HybridIndex()
index.build_index(chunks, enable_sentence_transformers=False)
retriever = HybridRetriever(index=index)
audit_section_4_multi_fact(retriever)
audit_section_5_developers()
audit_section_6_coordination()
audit_section_7_language()
audit_section_8_cache()
questions = build_section_11_evaluation_questions()
audit_section_12_retrieval_diagnostics(retriever, questions)
if __name__ == "__main__":
main()