refactor(#41): IMP-32 Step 9 application_plan helper extraction (u1~u5)

Pure refactor — extract inline Step 9 per-unit application_plan dict
assembly into module-level private helpers for testability. Replaces
IMP-05 Case 7 inspect.getsource() literal guard with direct helper-call
shape test. Behavior preserved: key set/order, candidate_evidence +
fallback_chain compat alias identity, IMP-06 additive plan fields,
IMP-11 D-2 markers (single _contract = get_contract(c.template_id)
bind + catalog_registered + min_height_px chain).

- u1 _application_candidates_for_unit(unit) at src/phase_z2_pipeline.py
  :2829-2853 — APPLICATION_MODE_BY_V4_LABEL mapping (pure extraction)
- u2 _v4_all_judgments_for_unit(v4_all_for_unit) at :2855-2882 —
  IMP-11 D-2 chain preserved literally
- u3 _build_application_plan_unit(unit, zone_plan, selection_trace,
  plan_record, v4_all_for_unit, layout_preset, layout_candidates_list)
  at :2885-2995 — byte-identical per-unit dict (key set + order +
  value identity), candidate_evidence / fallback_chain compat alias,
  v4_candidates list, v4_all_judgments, application_candidates, IMP-06
  additive plan fields
- u4 Step 9 inline loop body at :4620-4658 replaced with helper call;
  per-index/per-id lookups (zone_region_plans[i], v4_fallback_traces
  .get(...), plan_record_by_unit_id.get(id(unit)), section_alias_by_id,
  lookup_v4_all_judgments(...)) stay at call-site
- u5 tests/test_phase_z2_v4_fallback.py Case 7 rewritten to
  test_build_application_plan_unit_emits_candidate_evidence_and_alias
  — direct helper call with SimpleNamespace duck-typed input; asserts
  candidate_evidence list identity (is), fallback_chain compat-alias
  identity (is), key order (candidate_evidence before fallback_chain),
  and compat-alias comment scoped to inspect.getsource(_build_
  application_plan_unit)

Verification: targeted 22 passed, full pytest 408 passed (0 fail/skip),
smoke 11/11 PASS (2 pre-existing baseline SKIPs unchanged).

Cross-ref: IMP-05 (#5) commit 23d1b25 Case 7 temporary source guard
(replaced) / Codex #20 + #21 / IMP-11 D-2 marker preserved.
This commit is contained in:
2026-05-21 03:17:27 +09:00
parent 182aa7c47f
commit c412f1ea75
2 changed files with 253 additions and 112 deletions

View File

@@ -295,25 +295,79 @@ def test_existing_trace_shape_does_not_regress(patch_selector_deps):
assert trace["selection_path"] == "rank_1"
# ─── Case 7 : Step 9 production-source guard (Codex #20 blocker fix) ───
# ─── Case 7 : Step 9 helper-call shape test (IMP-32 u5 — replaces source guard) ───
def test_step9_production_emits_candidate_evidence_and_alias():
"""Temporary production-source guard for IMP-05 Step 9 evidence fields.
def test_build_application_plan_unit_emits_candidate_evidence_and_alias():
"""IMP-32 u5 — direct helper-call shape test for Step 9 evidence fields.
Step 9 application-plan unit assembly is currently inline, so this test
checks the exact production assignments until IMP-32 extracts a helper.
Once that helper exists, replace this source-string guard with a direct
helper-call test.
Replaces the IMP-05 Case 7 `inspect.getsource(phase_z2_pipeline)` literal
guard (introduced at commit `23d1b25` while Step 9 unit assembly was
inline) with a direct call to `_build_application_plan_unit`, the helper
extracted in IMP-32 u3. Verification axes preserved:
- candidate_evidence list identity sourced from `selection_trace["candidates"]`
- fallback_chain compat-alias identity (same list object as candidate_evidence)
- key order: candidate_evidence before fallback_chain
- compat-alias comment preserved on the helper's fallback_chain line
"""
source = inspect.getsource(phase_z2_pipeline)
candidate_line = '"candidate_evidence": selection_trace.get("candidates", [])'
alias_line = '"fallback_chain": selection_trace.get("candidates", [])'
from types import SimpleNamespace
assert candidate_line in source
assert alias_line in source
assert source.index(candidate_line) < source.index(alias_line)
assert "compat alias; prefer candidate_evidence" in source
from src.phase_z2_pipeline import _build_application_plan_unit
candidates_list = [
{"rank": 1, "template_id": "MOCK_template_direct_a", "label": "use_as_is"},
]
selection_trace = {"candidates": candidates_list}
# Synthetic CompositionUnit-shape duck-typed input — matches V4Match attrs
# used inside the helper (template_id / frame_id / frame_number / v4_rank /
# confidence / label per src/phase_z2_pipeline.py V4Match dataclass).
v4_candidate = SimpleNamespace(
template_id="MOCK_template_direct_a",
frame_id="MOCK_frame_001",
frame_number=1,
v4_rank=1,
confidence=0.9,
label="use_as_is",
)
unit = SimpleNamespace(
source_section_ids=["S1"],
v4_candidates=[v4_candidate],
v4_rank=1,
selection_path="rank_1",
fallback_reason=None,
frame_template_id="MOCK_template_direct_a",
)
result = _build_application_plan_unit(
unit=unit,
zone_plan={},
selection_trace=selection_trace,
plan_record=None,
v4_all_for_unit=[],
layout_preset="Type A",
layout_candidates_list=[],
)
# IMP-05 L2 — candidate_evidence is the primary field, identity-bound to
# selection_trace["candidates"] (not a copy).
assert "candidate_evidence" in result
assert result["candidate_evidence"] is candidates_list
# compat alias — fallback_chain references the SAME list object as
# candidate_evidence (verified by `is` identity, not equality).
assert "fallback_chain" in result
assert result["fallback_chain"] is candidates_list
# key order — candidate_evidence MUST precede fallback_chain in the
# returned dict to preserve documented L2 ordering.
keys = list(result.keys())
assert keys.index("candidate_evidence") < keys.index("fallback_chain")
# compat-alias comment preserved on the helper's fallback_chain line.
helper_source = inspect.getsource(_build_application_plan_unit)
assert "compat alias; prefer candidate_evidence" in helper_source
# ─── Case 8 : Step 20 slide-status qualifier fields presence + defensive default