175 lines
5.8 KiB
Python
175 lines
5.8 KiB
Python
import json
|
|
import sys
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
if str(SCRIPT_DIR) not in sys.path:
|
|
sys.path.insert(0, str(SCRIPT_DIR))
|
|
|
|
from run_selector_demo import recommend # noqa: E402
|
|
|
|
|
|
REPORT_NAME = "Round04_blind_routing_evaluation_report_2026-06-18.md"
|
|
|
|
|
|
def read_json(path):
|
|
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
|
|
|
|
def load_blind_inputs(root):
|
|
return read_json(root / "selector" / "round04_blind_inputs.json")
|
|
|
|
|
|
def selected_model_ids(result):
|
|
return [item["model_id"] for item in result.get("selected_models", [])]
|
|
|
|
|
|
def rejected_model_ids(result):
|
|
return [item["model_id"] for item in result.get("rejected_models", [])]
|
|
|
|
|
|
def evaluate_blind_input(root, item):
|
|
selector_result = recommend(root, item["input_text"])
|
|
return {
|
|
"input_id": item["input_id"],
|
|
"category_for_owner_review": item["category_for_owner_review"],
|
|
"control_case_type": item.get("control_case_type"),
|
|
"input_text": item["input_text"],
|
|
"why_included": item.get("why_included", ""),
|
|
"selected_models": selected_model_ids(selector_result),
|
|
"rejected_models": rejected_model_ids(selector_result),
|
|
"no_call": selector_result.get("no_call"),
|
|
"routing_notes": selector_result.get("routing_notes", ""),
|
|
"selected_model_details": selector_result.get("selected_models", []),
|
|
"rejected_model_details": selector_result.get("rejected_models", []),
|
|
}
|
|
|
|
|
|
def format_model_details(details):
|
|
if not details:
|
|
return [" - none"]
|
|
lines = []
|
|
for detail in details:
|
|
lines.append(
|
|
f" - `{detail['model_id']}` score={detail['score']} decision={detail['decision']}"
|
|
)
|
|
if detail.get("reasons"):
|
|
lines.append(" - reasons: " + "; ".join(detail["reasons"]))
|
|
if detail.get("penalties"):
|
|
lines.append(" - penalties: " + "; ".join(detail["penalties"]))
|
|
return lines
|
|
|
|
|
|
def render_report(input_set, results):
|
|
selected_counter = Counter()
|
|
category_counter = Counter()
|
|
no_call_count = 0
|
|
for result in results:
|
|
category_counter[result["category_for_owner_review"]] += 1
|
|
if result["no_call"]:
|
|
no_call_count += 1
|
|
if not result["selected_models"]:
|
|
selected_counter["none"] += 1
|
|
for model_id in result["selected_models"]:
|
|
selected_counter[model_id] += 1
|
|
|
|
lines = [
|
|
"# Round 04 Blind Routing Evaluation Report",
|
|
"",
|
|
"Date: 2026-06-18",
|
|
"",
|
|
f"Blind input set: `{input_set['blind_input_set_id']}`",
|
|
"",
|
|
"Expected routing labels: `not included before first blind run`",
|
|
"",
|
|
"This report records current selector routing only. It does not score correctness, does not generate final answers, does not modify selector rules, and does not upgrade model status.",
|
|
"",
|
|
"## Scope",
|
|
"",
|
|
"- Round 04 target: test routing stability on new inputs.",
|
|
"- Runtime surface: current rule-based selector plus model-level QPI / IA gates.",
|
|
"- QPI contract status: represented here by QPI selection and routing details; no full QPI answer generation is performed.",
|
|
"- IA status: represented here by selector selection / rejection only; no Intellectual Archaeology answer generation is performed.",
|
|
"",
|
|
"## Summary",
|
|
"",
|
|
f"Inputs evaluated: {len(results)}",
|
|
f"No-call results: {no_call_count}",
|
|
"",
|
|
"### Selected Model Distribution",
|
|
"",
|
|
]
|
|
|
|
for key, count in sorted(selected_counter.items()):
|
|
lines.append(f"- `{key}`: {count}")
|
|
|
|
lines.extend([
|
|
"",
|
|
"### Coverage Categories",
|
|
"",
|
|
])
|
|
for key, count in sorted(category_counter.items()):
|
|
lines.append(f"- `{key}`: {count}")
|
|
|
|
lines.extend([
|
|
"",
|
|
"## Case Results",
|
|
"",
|
|
])
|
|
|
|
for result in results:
|
|
lines.extend([
|
|
f"### {result['input_id']}",
|
|
"",
|
|
f"- category_for_owner_review: `{result['category_for_owner_review']}`",
|
|
])
|
|
if result.get("control_case_type"):
|
|
lines.append(f"- control_case_type: `{result['control_case_type']}`")
|
|
lines.extend([
|
|
f"- input_text: `{result['input_text']}`",
|
|
f"- selected_models: `{result['selected_models']}`",
|
|
f"- rejected_models: `{result['rejected_models']}`",
|
|
f"- no_call: `{result['no_call']}`",
|
|
f"- routing_notes: {result['routing_notes']}",
|
|
"",
|
|
"Selected model details:",
|
|
])
|
|
lines.extend(format_model_details(result["selected_model_details"]))
|
|
lines.extend(["", "Rejected model details:"])
|
|
lines.extend(format_model_details(result["rejected_model_details"]))
|
|
lines.append("")
|
|
|
|
lines.extend([
|
|
"## Review Reminder",
|
|
"",
|
|
"This is the first blind routing output. Failure labels, expected routing, and rule changes should be added only after owner / GPT / CCRA review of these observed results.",
|
|
"",
|
|
])
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def write_report(root, input_set, results):
|
|
report_path = root / "reports" / REPORT_NAME
|
|
report_path.write_text(render_report(input_set, results), encoding="utf-8")
|
|
return report_path
|
|
|
|
|
|
def main():
|
|
root = Path(__file__).resolve().parents[1]
|
|
input_set = load_blind_inputs(root)
|
|
results = [
|
|
evaluate_blind_input(root, item)
|
|
for item in input_set.get("inputs", [])
|
|
]
|
|
report_path = write_report(root, input_set, results)
|
|
print(f"round04 blind routing report written to {report_path}")
|
|
print(f"inputs evaluated: {len(results)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|