import json import sys from pathlib import Path from run_selector_demo import load_selector_rules, model_hard_exclusion_hits, recommend def read_json(path): return json.loads(Path(path).read_text(encoding="utf-8")) def selected_model_ids(result): return [item["model_id"] for item in result.get("selected_models", [])] def has_ia_gate(input_text, selector_rules): global_rules = selector_rules.get("global_rules", {}) heavy_signals = global_rules.get("ia_heavy_signals", []) return any(signal in input_text for signal in heavy_signals) or "QPI 已判断" in input_text def evaluate_input(root, selector_rules, item): result = recommend(root, item["input"]) selected_ids = selected_model_ids(result) expected = item.get("expected_selector_behavior") errors = [] warnings = [] model_rules = { model_rule["model_id"]: model_rule for model_rule in selector_rules.get("models", []) } if expected == "no_call": if selected_ids: errors.append(f"expected no selected models, got {selected_ids}") if result.get("no_call") is not True: errors.append("expected no_call=true") if expected == "no_call_or_low_priority": if "qpi" in selected_ids: errors.append("expected no QPI selection") if "intellectual_archaeology" in selected_ids: errors.append("expected no IA selection") if expected in {"select_qpi", "select_qpi_low_confidence", "select_qpi_reject_ia"}: if "qpi" not in selected_ids: errors.append("expected QPI selection") if expected == "select_qpi_reject_ia" and "intellectual_archaeology" in selected_ids: errors.append("expected IA rejection") if expected == "select_intellectual_archaeology": if "intellectual_archaeology" not in selected_ids: errors.append("expected IA selection") if not has_ia_gate(item["input"], selector_rules): errors.append("IA selected expectation lacks heavy-depth or QPI-completed gate") ia_hard_exclusion_hits = model_hard_exclusion_hits( item["input"], model_rules.get("intellectual_archaeology", {}), ) if ia_hard_exclusion_hits: errors.append("IA selection expected despite IA hard exclusion signal") if expected == "select_qpi" and set(selected_ids) - {"qpi"}: warnings.append("unexpected extra selected models: " + ", ".join(sorted(set(selected_ids) - {"qpi"}))) return { "case_id": item["case_id"], "expected_selector_behavior": expected, "selected_models": selected_ids, "no_call": result.get("no_call"), "errors": errors, "warnings": warnings, } def write_report(root, results): report_path = root / "reports" / "selector_calibration_smoke_report.md" failures = [result for result in results if result["errors"]] warnings = [result for result in results if result["warnings"]] lines = [ "# Selector Calibration Smoke Report", "", f"Status: `{'PASS' if not failures else 'FAIL'}`", "", "Command: `python scripts/run_selector_calibration_smoke.py`", "", f"Calibration inputs checked: {len(results)}", f"Failures: {len(failures)}", f"Warnings: {len(warnings)}", "", "## Cases", "", ] for result in results: status = "PASS" if not result["errors"] else "FAIL" lines.append( f"- `{result['case_id']}`: {status}; expected={result['expected_selector_behavior']}; " f"selected={result['selected_models']}; no_call={result['no_call']}" ) for error in result["errors"]: lines.append(f" - {error}") for warning in result["warnings"]: lines.append(f" - WARNING: {warning}") report_path.write_text("\n".join(lines) + "\n", encoding="utf-8") return report_path def main(): root = Path(__file__).resolve().parents[1] selector_rules = load_selector_rules(root) calibration = read_json(root / "selector" / "selector_calibration_inputs.json") results = [ evaluate_input(root, selector_rules, item) for item in calibration.get("inputs", []) ] report_path = write_report(root, results) print(f"selector calibration smoke report written to {report_path}") failures = [result for result in results if result["errors"]] if failures: for result in failures: print(f"ERROR: {result['case_id']}: {'; '.join(result['errors'])}") return 1 print("selector calibration smoke passed") return 0 if __name__ == "__main__": sys.exit(main())