feat(#77): IMP-48 composition planner re-split on all-reject (u1~u9)
Add resplit_all_reject_merges() helper in phase_z2_composition.py that
detects parent_merged / parent_merged_inferred units with label=reject
and rebuilds them as per-section single units using each section's own
rank-1 V4 evidence (no frame swap, MDX raw_content preserved).
Pipeline hook fires once after Step 6 settling chain (u12/u4/empty-shell)
and section_assignment_plan resolution, before Step 6 artifact write.
Guards: beneficial-split rule (>=1 non-reject), coverage equality, layout
cap (>4 abort), max_retry=1, section_assignment_override short-circuit.
Audit: comp_debug["imp48_resplit"] additive payload (applied, split_units,
skipped_units, post_split_unit_count, post_split_layout_preset);
selection_path="resplit_from_merge" telemetry on rebuilt singles;
layout_preset re-derived via select_layout_preset(new_units).
Tests: 39/39 PASS (composition u1~u6: 14 cases; pipeline u7~u9: 25 cases).
Scoped regression 720/6 with 6 failures isolated as pre-existing on
baseline 79f9ea5 (independent of IMP-48). mdx03 golden lock preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ from phase_z2_composition import (
|
||||
CompositionUnit,
|
||||
derive_parent_id,
|
||||
plan_composition,
|
||||
resplit_all_reject_merges,
|
||||
select_display_strategy_candidates,
|
||||
select_layout_candidates,
|
||||
select_region_layout_candidates,
|
||||
@@ -3966,6 +3967,52 @@ def run_phase_z2_mvp1(
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# IMP-48 (#77) — re-split merged-reject units into per-section singles.
|
||||
# One-shot, deterministic (AI=0) post-pass. Fires AFTER all Step 6 settling
|
||||
# chains (initial plan_composition / u12 mixed admission / u4 provisional
|
||||
# retry / empty-shell) and AFTER section_assignment_plan is known, but
|
||||
# BEFORE the Step 6 artifact write below — so the artifact reflects the
|
||||
# post-resplit unit list. SKIPS when --override-section-assignments is
|
||||
# active (IMP-06 / #6 is the ground truth). Helper guardrails (coverage
|
||||
# equality / beneficial split / layout cap ≤ 4) keep mdx03 byte-identical
|
||||
# (no-op on use_as_is / light_edit slides). u5 re-derives layout_preset
|
||||
# below using the audit payload.
|
||||
units, _imp48_audit = resplit_all_reject_merges(
|
||||
units,
|
||||
sections,
|
||||
lookup_fn,
|
||||
V4_LABEL_TO_PHASE_Z_STATUS,
|
||||
MVP1_ALLOWED_STATUSES,
|
||||
capacity_fit_fn=compute_capacity_fit,
|
||||
v4_candidates_lookup_fn=candidates_lookup_fn,
|
||||
section_assignment_override=section_assignment_plan is not None,
|
||||
)
|
||||
comp_debug["imp48_resplit"] = _imp48_audit
|
||||
# u5 — re-derive layout_preset from helper audit (post-split count via
|
||||
# select_layout_preset(out_units)). Helper guarantees post_split_unit_count
|
||||
# ≤ 4 (layout cap abort), so the derived preset is always renderable by
|
||||
# LAYOUT_PRESETS. Respect --override-layout when present (user's explicit
|
||||
# choice wins over auto-redrive; mirrors the override gate above at L3697).
|
||||
if _imp48_audit.get("applied"):
|
||||
_imp48_post_preset = _imp48_audit.get("post_split_layout_preset")
|
||||
if _imp48_post_preset and not layout_override_applied:
|
||||
if _imp48_post_preset != layout_preset:
|
||||
print(
|
||||
f" [IMP-48] layout_preset re-derived: {layout_preset} → "
|
||||
f"{_imp48_post_preset} (post-split unit count="
|
||||
f"{_imp48_audit.get('post_split_unit_count')})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
layout_preset = _imp48_post_preset
|
||||
print(
|
||||
f" [IMP-48] re-split applied — "
|
||||
f"split={len(_imp48_audit.get('split_units', []))} "
|
||||
f"skipped={len(_imp48_audit.get('skipped_units', []))} "
|
||||
f"post_count={_imp48_audit.get('post_split_unit_count')} "
|
||||
f"post_preset={_imp48_audit.get('post_split_layout_preset')!r}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
print(f" preset : {layout_preset} ({len(units)} units, composition v0 count-based)")
|
||||
for u in units:
|
||||
print(f" unit : {u.source_section_ids} merge={u.merge_type} → "
|
||||
@@ -4011,6 +4058,15 @@ def run_phase_z2_mvp1(
|
||||
}
|
||||
for u in units
|
||||
],
|
||||
# IMP-48 (#77) — re-split audit. Additive field. AI=0 deterministic
|
||||
# one-shot post-pass on Step 6 settling result. applied=True means
|
||||
# ≥1 parent_merged / parent_merged_inferred reject unit was split
|
||||
# into per-section singles; selected_units already reflects the
|
||||
# post-split list. Skipped reasons (incomplete_rebuild /
|
||||
# no_beneficial_split / layout_cap_exceeded) keep the merged unit
|
||||
# for IMP-47B (#76) AI handoff. section_assignment_override skip
|
||||
# honors IMP-06 (#6) zoneSections ground truth.
|
||||
"imp48_resplit": _imp48_audit,
|
||||
},
|
||||
step_status="done",
|
||||
pipeline_path_connected=True,
|
||||
@@ -4020,6 +4076,11 @@ def run_phase_z2_mvp1(
|
||||
"composition v0 count-based — sections → candidates → score → greedy select. "
|
||||
"Step 6-A (사용자 lock 2026-05-08): selected_units[i].v4_candidates 추가 "
|
||||
"(non-reject max-6 후보 list, candidates[0] = 단일 frame_* 와 일관). "
|
||||
"IMP-48 (#77, 2026-05-22): merged-reject 자동 분리 post-pass — "
|
||||
"parent_merged / parent_merged_inferred + label=reject + ≥2 sections "
|
||||
"→ per-section singles (each own rank-1 V4 evidence + raw_content 보존). "
|
||||
"guardrails: coverage equality / beneficial split (≥1 non-reject) / "
|
||||
"layout cap (≤4 units). imp48_resplit audit additive. "
|
||||
"logic 무변 — runtime 결과 동일. Step 9 application_plan input."
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user