Разделить проверенные route candidates в domain-loop
This commit is contained in:
parent
ed44c6428a
commit
584edb7e5b
|
|
@ -4592,6 +4592,22 @@ def compact_route_candidate_handoff(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def route_candidate_requires_handoff(step_output: dict[str, Any]) -> bool:
|
||||||
|
status = str(step_output.get("mcp_discovery_route_candidate_status") or "").strip()
|
||||||
|
if status in {"needs_route_enablement", "needs_user_scope"}:
|
||||||
|
return True
|
||||||
|
if status != "ready_for_reviewed_execution":
|
||||||
|
return False
|
||||||
|
acceptance_status = str(step_output.get("acceptance_status") or step_output.get("status") or "").strip()
|
||||||
|
if acceptance_status not in {"validated", "accepted"}:
|
||||||
|
return True
|
||||||
|
if normalize_string_list(step_output.get("violated_invariants")):
|
||||||
|
return True
|
||||||
|
if normalize_string_list(step_output.get("warnings")):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def build_route_candidate_focus_signature(candidate: dict[str, Any]) -> str:
|
def build_route_candidate_focus_signature(candidate: dict[str, Any]) -> str:
|
||||||
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
||||||
selected_chain_id = str(candidate.get("selected_chain_id") or "no_chain").strip() or "no_chain"
|
selected_chain_id = str(candidate.get("selected_chain_id") or "no_chain").strip() or "no_chain"
|
||||||
|
|
@ -4810,6 +4826,7 @@ def build_deterministic_repair_targets(
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
targets: list[dict[str, Any]] = []
|
targets: list[dict[str, Any]] = []
|
||||||
route_candidates: list[dict[str, Any]] = []
|
route_candidates: list[dict[str, Any]] = []
|
||||||
|
reviewed_route_candidates: list[dict[str, Any]] = []
|
||||||
step_validation_index: dict[str, dict[str, Any]] = {}
|
step_validation_index: dict[str, dict[str, Any]] = {}
|
||||||
for scenario_artifact in scenario_artifacts:
|
for scenario_artifact in scenario_artifacts:
|
||||||
scenario_id = str(scenario_artifact.get("scenario_id") or "").strip()
|
scenario_id = str(scenario_artifact.get("scenario_id") or "").strip()
|
||||||
|
|
@ -4832,7 +4849,10 @@ def build_deterministic_repair_targets(
|
||||||
step_output=raw_step_output,
|
step_output=raw_step_output,
|
||||||
)
|
)
|
||||||
if route_candidate:
|
if route_candidate:
|
||||||
route_candidates.append(route_candidate)
|
if route_candidate_requires_handoff(raw_step_output):
|
||||||
|
route_candidates.append(route_candidate)
|
||||||
|
else:
|
||||||
|
reviewed_route_candidates.append(route_candidate)
|
||||||
target = build_step_repair_target(
|
target = build_step_repair_target(
|
||||||
scenario_id=scenario_id,
|
scenario_id=scenario_id,
|
||||||
scenario_title=scenario_title,
|
scenario_title=scenario_title,
|
||||||
|
|
@ -4887,6 +4907,11 @@ def build_deterministic_repair_targets(
|
||||||
for candidate in route_candidates:
|
for candidate in route_candidates:
|
||||||
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
||||||
route_candidate_status_counts[status] = route_candidate_status_counts.get(status, 0) + 1
|
route_candidate_status_counts[status] = route_candidate_status_counts.get(status, 0) + 1
|
||||||
|
reviewed_route_candidate_groups = build_route_candidate_handoff_groups(reviewed_route_candidates)
|
||||||
|
reviewed_route_candidate_status_counts: dict[str, int] = {}
|
||||||
|
for candidate in reviewed_route_candidates:
|
||||||
|
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
||||||
|
reviewed_route_candidate_status_counts[status] = reviewed_route_candidate_status_counts.get(status, 0) + 1
|
||||||
return {
|
return {
|
||||||
"schema_version": "domain_pack_repair_targets_v1",
|
"schema_version": "domain_pack_repair_targets_v1",
|
||||||
"pack_id": pack_state.get("pack_id"),
|
"pack_id": pack_state.get("pack_id"),
|
||||||
|
|
@ -4908,6 +4933,9 @@ def build_deterministic_repair_targets(
|
||||||
"route_candidate_handoff_count": len(route_candidates),
|
"route_candidate_handoff_count": len(route_candidates),
|
||||||
"route_candidate_status_counts": route_candidate_status_counts,
|
"route_candidate_status_counts": route_candidate_status_counts,
|
||||||
"route_candidate_groups": route_candidate_groups,
|
"route_candidate_groups": route_candidate_groups,
|
||||||
|
"reviewed_route_candidate_count": len(reviewed_route_candidates),
|
||||||
|
"reviewed_route_candidate_status_counts": reviewed_route_candidate_status_counts,
|
||||||
|
"reviewed_route_candidate_groups": reviewed_route_candidate_groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5555,6 +5583,8 @@ def build_repair_targets_summary(repair_targets: dict[str, Any]) -> str:
|
||||||
f"- severity_counts: `{dump_json(repair_targets.get('severity_counts') or {})}`",
|
f"- severity_counts: `{dump_json(repair_targets.get('severity_counts') or {})}`",
|
||||||
f"- route_candidate_handoff_count: `{repair_targets.get('route_candidate_handoff_count') if 'route_candidate_handoff_count' in repair_targets else 0}`",
|
f"- route_candidate_handoff_count: `{repair_targets.get('route_candidate_handoff_count') if 'route_candidate_handoff_count' in repair_targets else 0}`",
|
||||||
f"- route_candidate_status_counts: `{dump_json(repair_targets.get('route_candidate_status_counts') or {})}`",
|
f"- route_candidate_status_counts: `{dump_json(repair_targets.get('route_candidate_status_counts') or {})}`",
|
||||||
|
f"- reviewed_route_candidate_count: `{repair_targets.get('reviewed_route_candidate_count') if 'reviewed_route_candidate_count' in repair_targets else 0}`",
|
||||||
|
f"- reviewed_route_candidate_status_counts: `{dump_json(repair_targets.get('reviewed_route_candidate_status_counts') or {})}`",
|
||||||
]
|
]
|
||||||
route_candidate_groups = repair_targets.get("route_candidate_groups") or []
|
route_candidate_groups = repair_targets.get("route_candidate_groups") or []
|
||||||
if isinstance(route_candidate_groups, list) and route_candidate_groups:
|
if isinstance(route_candidate_groups, list) and route_candidate_groups:
|
||||||
|
|
@ -5573,6 +5603,23 @@ def build_repair_targets_summary(repair_targets: dict[str, Any]) -> str:
|
||||||
f" target_ids: `{', '.join(normalize_string_list(group.get('target_ids'))) or 'n/a'}`",
|
f" target_ids: `{', '.join(normalize_string_list(group.get('target_ids'))) or 'n/a'}`",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
reviewed_route_candidate_groups = repair_targets.get("reviewed_route_candidate_groups") or []
|
||||||
|
if isinstance(reviewed_route_candidate_groups, list) and reviewed_route_candidate_groups:
|
||||||
|
lines.extend(["", "## Reviewed Route Candidates (No Handoff)"])
|
||||||
|
for group in reviewed_route_candidate_groups:
|
||||||
|
if not isinstance(group, dict):
|
||||||
|
continue
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"- group_id: `{group.get('group_id') or 'n/a'}`",
|
||||||
|
f" candidate_count: `{group.get('candidate_count') or 0}`",
|
||||||
|
f" status: `{group.get('candidate_status') or 'n/a'}`",
|
||||||
|
f" selected_chain_id: `{group.get('selected_chain_id') or 'n/a'}`",
|
||||||
|
f" missing_axes: `{', '.join(normalize_string_list(group.get('missing_axes'))) or 'n/a'}`",
|
||||||
|
f" executable_now: `{group.get('executable_now')}`",
|
||||||
|
f" target_ids: `{', '.join(normalize_string_list(group.get('target_ids'))) or 'n/a'}`",
|
||||||
|
]
|
||||||
|
)
|
||||||
priority_foci = repair_targets.get("priority_foci") or []
|
priority_foci = repair_targets.get("priority_foci") or []
|
||||||
if isinstance(priority_foci, list) and priority_foci:
|
if isinstance(priority_foci, list) and priority_foci:
|
||||||
lines.extend(
|
lines.extend(
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,86 @@ class DomainCaseLoopStepStateTests(unittest.TestCase):
|
||||||
self.assertIn("## Route Candidate Handoff Groups", markdown)
|
self.assertIn("## Route Candidate Handoff Groups", markdown)
|
||||||
self.assertIn("route_candidate_demo:step_01", markdown)
|
self.assertIn("route_candidate_demo:step_01", markdown)
|
||||||
|
|
||||||
|
def test_validated_ready_route_candidates_are_reviewed_without_repair_handoff(self) -> None:
|
||||||
|
repair_targets = dcl.build_deterministic_repair_targets(
|
||||||
|
{"pack_id": "route_candidate_pack", "domain": "inventory_stock", "final_status": "accepted"},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"scenario_id": "inventory_sale_trace",
|
||||||
|
"title": "Sale trace",
|
||||||
|
"artifact_dir": "artifacts/domain_runs/inventory_sale_trace",
|
||||||
|
"scenario_state": {
|
||||||
|
"step_outputs": {
|
||||||
|
"step_04": {
|
||||||
|
"status": "validated",
|
||||||
|
"acceptance_status": "validated",
|
||||||
|
"execution_status": "exact",
|
||||||
|
"question_resolved": "trace purchase to sale",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"mcp_discovery_selected_chain_id": "metadata_inspection",
|
||||||
|
"mcp_discovery_route_candidate_status": "ready_for_reviewed_execution",
|
||||||
|
"mcp_discovery_route_candidate_fact_family": "schema_surface",
|
||||||
|
"mcp_discovery_route_candidate_action_family": "inspect_surface",
|
||||||
|
"mcp_discovery_route_candidate_missing_axes": ["metadata_scope"],
|
||||||
|
"mcp_discovery_route_candidate_executable_now": True,
|
||||||
|
"violated_invariants": [],
|
||||||
|
"warnings": [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(repair_targets["target_count"], 0)
|
||||||
|
self.assertEqual(repair_targets["route_candidate_handoff_count"], 0)
|
||||||
|
self.assertEqual(repair_targets["route_candidate_status_counts"], {})
|
||||||
|
self.assertEqual(repair_targets["route_candidate_groups"], [])
|
||||||
|
self.assertEqual(repair_targets["reviewed_route_candidate_count"], 1)
|
||||||
|
self.assertEqual(repair_targets["reviewed_route_candidate_status_counts"], {"ready_for_reviewed_execution": 1})
|
||||||
|
self.assertEqual(
|
||||||
|
repair_targets["reviewed_route_candidate_groups"][0]["group_id"],
|
||||||
|
"ready_for_reviewed_execution|metadata_inspection|metadata_scope",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_nonvalidated_ready_route_candidates_stay_in_repair_handoff(self) -> None:
|
||||||
|
repair_targets = dcl.build_deterministic_repair_targets(
|
||||||
|
{"pack_id": "route_candidate_pack", "domain": "inventory_stock", "final_status": "partial"},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"scenario_id": "inventory_sale_trace",
|
||||||
|
"title": "Sale trace",
|
||||||
|
"artifact_dir": "artifacts/domain_runs/inventory_sale_trace",
|
||||||
|
"scenario_state": {
|
||||||
|
"step_outputs": {
|
||||||
|
"step_04": {
|
||||||
|
"status": "rejected",
|
||||||
|
"acceptance_status": "rejected",
|
||||||
|
"execution_status": "partial",
|
||||||
|
"question_resolved": "trace purchase to sale",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"mcp_discovery_selected_chain_id": "metadata_inspection",
|
||||||
|
"mcp_discovery_route_candidate_status": "ready_for_reviewed_execution",
|
||||||
|
"mcp_discovery_route_candidate_fact_family": "schema_surface",
|
||||||
|
"mcp_discovery_route_candidate_action_family": "inspect_surface",
|
||||||
|
"mcp_discovery_route_candidate_missing_axes": ["metadata_scope"],
|
||||||
|
"mcp_discovery_route_candidate_executable_now": True,
|
||||||
|
"violated_invariants": ["answer_shape_mismatch"],
|
||||||
|
"warnings": [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(repair_targets["route_candidate_handoff_count"], 1)
|
||||||
|
self.assertEqual(repair_targets["reviewed_route_candidate_count"], 0)
|
||||||
|
self.assertEqual(
|
||||||
|
repair_targets["route_candidate_groups"][0]["group_id"],
|
||||||
|
"ready_for_reviewed_execution|metadata_inspection|metadata_scope",
|
||||||
|
)
|
||||||
|
|
||||||
def test_analysis_context_date_is_not_implicit_business_filter(self) -> None:
|
def test_analysis_context_date_is_not_implicit_business_filter(self) -> None:
|
||||||
step_state = dcl.build_scenario_step_state(
|
step_state = dcl.build_scenario_step_state(
|
||||||
scenario_id="stage_pack_demo",
|
scenario_id="stage_pack_demo",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue