"""Declare a fresh query-group holdout using raw sources only, before retrieval. All 1,042 previously exposed questions become DEV. No profile is new; this is a new-combination holdout over a known corpus, not an unseen-person experiment. """ import copy import json import random from collections import Counter from .benchmark_v2 import ( BASE, audit_dataset, make_case, validate_case, write_json, binding_source_evidence, ) from .benchmark_v4 import build_pool, logical_group, refresh_titles from .source_oracle import load_source_oracle, norm QUOTAS = { "shared_keyword_pair": 10, "same_academic_year_pair": 10, "same_start_year_pair": 10, "person_title_year": 10, "person_keyword_title": 10, "document_author_category_multi_hop": 2, "person_multiple_categories": 7, "candidate_set_aggregation": 7, "conjunctive_disambiguation": 6, "negative_lookup": 8, } SEED = 20261026 def main(): output = BASE / "graphrag_questions_v7.json" artifacts = BASE / "results/v7_preparation" if output.exists(): raise RuntimeError("Refusing to overwrite declared questions.") oracle = load_source_oracle() old = json.loads((BASE / "graphrag_questions_v6.json").read_text()) dev = [] changes = [] exposed = {logical_group(q) for q in old} for original in old: case = copy.deepcopy(original) case["split"] = "dev" names = [r["value"] for r in case["graph"]["start_entities"] if r["type"] == "person"] if any( b.get("start_year") is not None and oracle["people"][b["person"]]["start_year"] is None for b in case["graph"]["expected_bindings"] ): case = make_case( case["id"], "dev", case["category"], case["question"], names, oracle, [], no_answer=True, reason="The original Markdown does not explicitly document every requested study-start year.", negative_constraints={"missing_start_year": True}, ) changes.append( { "id": case["id"], "old_bindings": original["graph"]["expected_bindings"], "new_status": "no_answer", "reason": "Directory cohort is not a documented study-start year.", } ) elif case["should_answer"]: case["source_evidence"] = [ e for b in case["graph"]["expected_bindings"] for e in binding_source_evidence(b, oracle) ] case["graph"]["partition_group"] = logical_group(case) dev.append(case) # Fixed audit regressions; these are explicitly DEV, not extra easy TEST negatives. for year in [1999, 1899, 2100, 9999]: case = make_case( f"v7-dev-year-{year}", "dev", "negative_lookup", f"Aký je názov práce osoby Ján Holp v roku {year}?", ["Ján Holp"], oracle, [], no_answer=True, negative_constraints={"year": year}, ) case["graph"]["partition_group"] = logical_group(case) dev.append(case) exposed.update(logical_group(q) for q in dev) texts = {norm(q["question"]) for q in dev} rng = random.Random(SEED) holdout = [] capacity = {} for category, pool in build_pool(oracle).items(): unique = {} for candidate in pool: case = refresh_titles(candidate, oracle) group = logical_group(case) if group not in exposed and norm(case["question"]) not in texts: unique[group] = case capacity[category] = len(unique) candidates = [unique[key] for key in sorted(unique)] rng.shuffle(candidates) quota = QUOTAS[category] if len(candidates) < quota: raise RuntimeError(f"Insufficient source capacity: {category}") for case in candidates[:quota]: case["id"] = f"fresh7-{len(holdout)+1:03d}" case["split"] = "test" case["graph"]["partition_group"] = logical_group(case) holdout.append(case) questions = dev + holdout for case in questions: issues = validate_case(case, oracle) if issues: raise RuntimeError(f'{case["id"]}: {issues}') write_json(output, questions) audit = audit_dataset(questions, oracle, output) audit["partition_policy"] = { "method": "disjoint logical query groups", "shared_source_profiles": True, "seed": SEED, "previously_exposed_groups": sorted(exposed), "limitations": [ "Known corpus, persons and template families; only the query/fact combinations are fresh.", "Two remaining document-author/category groups and seven category-list groups constrain category coverage.", "AI source annotation, not human annotation. No tuning on this TEST.", ], } write_json(artifacts / "source_audit.json", audit) write_json( artifacts / "source_corrections.json", { "questions": changes, "policy": "Only explicit study-start text is a fact. Cohort remains separate metadata; missing means unknown.", }, ) write_json(artifacts / "regression_ids.json", [q["id"] for q in old if q["split"] == "test"]) ids = set(json.loads((BASE / "results/v6_preparation/generation_dev_ids.json").read_text())) ids.update(f"v7-dev-year-{year}" for year in [1999, 1899, 2100, 9999]) write_json(artifacts / "generation_dev_ids.json", sorted(ids)) write_json( artifacts / "protocol.json", { "seed": SEED, "dev_count": len(dev), "fresh_test_count": len(holdout), "test_categories": dict(Counter(q["category"] for q in holdout)), "test_positives": sum(q["should_answer"] for q in holdout), "test_negatives": sum(not q["should_answer"] for q in holdout), "available_groups": capacity, "old_gold_corrections": len(changes), "regression_count": 320, "llm_dev_count": len(ids), "target": 0.92, "policy": "Freeze after complete DEV; run fresh TEST once, retain every failure. Regression remains separate.", "annotation": "Independent raw-source oracle plus AI source review; not human review.", }, ) print( json.dumps( { "dev": len(dev), "test": len(holdout), "source_corrections": len(changes), "quotas": QUOTAS, } ) ) if __name__ == "__main__": main()