from types import SimpleNamespace from src.phase_z2_pipeline import _build_t28_5c_reselection_trace def _candidate(template_id, status="auto_renderable", label="use_as_is"): return SimpleNamespace( template_id=template_id, frame_id=f"frame-{template_id}", label=label, candidate_status=status, ) def test_t28_5c_abort_trace_finds_eligible_frame_alternative(): units = [ SimpleNamespace( source_section_ids=["02-2"], frame_template_id="three_parallel_requirements", v4_candidates=[ _candidate("three_parallel_requirements"), _candidate("three_persona_benefits"), _candidate("pre_construction_model_info_stacked", "ai_adaptation_required", "reject"), ], ), SimpleNamespace(source_section_ids=["02-1"], frame_template_id="construction_goals_three_circle_intersection", v4_candidates=[]), ] trace = _build_t28_5c_reselection_trace( units=units, debug_zones=[ { "position": "right", "source_section_ids": ["02-2"], "template_id": "three_parallel_requirements", } ], layout_preset="vertical-2", router_decision={"proposed_actions_summary": ["abort"]}, presentation_fit={ "passed": False, "zones": [{"position": "right", "overflow_px": 165, "overflow_x_px": 0}], }, ) assert trace["triggered"] is True assert trace["policy"] == "trace_only_no_silent_swap" zone = trace["zones"][0] assert zone["current_template_id"] == "three_parallel_requirements" assert zone["eligible_candidate_count"] == 1 assert zone["selected_candidate"]["template_id"] == "three_persona_benefits" assert zone["candidates"][0]["ineligible_reason"] == "current_template" assert zone["candidates"][2]["coverage_state"] == "requires_adaptation" assert zone["candidates"][2]["eligible"] is False assert trace["next_step"] == "frame_or_layout_candidate_available" def test_t28_5c_manual_geometry_blocks_layout_reselection_only(): trace = _build_t28_5c_reselection_trace( units=[ SimpleNamespace(source_section_ids=["a"], frame_template_id="three_parallel_requirements", v4_candidates=[]), SimpleNamespace(source_section_ids=["b"], frame_template_id="three_parallel_requirements", v4_candidates=[]), ], debug_zones=[ { "position": "right", "source_section_ids": ["b"], "template_id": "three_parallel_requirements", } ], layout_preset="vertical-2", router_decision={"proposed_actions_summary": ["abort"]}, presentation_fit={ "passed": False, "zones": [{"position": "right", "overflow_px": 100, "overflow_x_px": 0}], }, override_zone_geometries={"right": {"x": 0.45, "y": 0, "w": 0.55, "h": 1}}, ) assert trace["triggered"] is True assert trace["manual_zone_positions"] == ["right"] assert trace["zones"][0]["manual_geometry_applied"] is True assert trace["layout_reselection"]["allowed"] is False assert trace["layout_reselection"]["blocked_reason"] == "manual_geometry_present" assert trace["next_step"] == "escalate_to_t28_5d_or_presentation_not_ready" def test_t28_5c_no_abort_no_reselection_trigger(): trace = _build_t28_5c_reselection_trace( units=[], debug_zones=[], layout_preset="single", router_decision={"proposed_actions_summary": ["zone_ratio_retry"]}, presentation_fit={ "passed": False, "zones": [{"position": "primary", "overflow_px": 10, "overflow_x_px": 0}], }, ) assert trace["triggered"] is False assert trace["zones"] assert trace["layout_reselection"]["evaluated"] is False