From 425a3054c170f36844d939616c43adad0ff516cc Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Mon, 4 May 2026 10:32:26 +0900 Subject: [PATCH] Add Phase Z placement trace telemetry - run B1/B2/B4 placement planning in trace-only mode - record placement_trace per rendered zone in debug output - preserve existing render output and visual routing behavior --- src/phase_z2_pipeline.py | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/phase_z2_pipeline.py b/src/phase_z2_pipeline.py index 5d46e9e..5f9f57f 100644 --- a/src/phase_z2_pipeline.py +++ b/src/phase_z2_pipeline.py @@ -30,7 +30,7 @@ import re import shutil import sys import time -from dataclasses import dataclass +from dataclasses import asdict, dataclass from pathlib import Path from typing import Optional @@ -46,6 +46,7 @@ from phase_z2_mapper import ( FitError, compute_capacity_fit, get_contract, + load_frame_contracts, map_with_contract, ) from phase_z2_classifier import classify_visual_runtime_check @@ -57,6 +58,11 @@ from phase_z2_retry import ( ) from phase_z2_failure_router import enrich_retry_trace_with_failure_classification +# trace-only runtime 연결 v0 — B1 → B4 chain. +# final.html / mapper / render path 미영향. debug_zones[i].placement_trace 만 기록. +from phase_z2_content_extractor import extract_content_objects +from phase_z2_placement_planner import plan_placement + # ─── Constants ────────────────────────────────────────────────── @@ -1051,6 +1057,38 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path: min_height_px = visual_hints.get("min_height_px", DEFAULT_ZONE_MIN_HEIGHT_PX) contract_frame_id = contract.get("frame_id") + # ─── trace-only runtime 연결 v0 — B1 → B4 chain (final.html 영향 X) ─── + # B1~B4 의 dormant chain 을 *real MDX runtime data* 로 처음 호출. + # 결과 (PlacementPlan) = debug_zones[i].placement_trace 로 *기록만*. + # render path / mapper output / final.html 모두 미변경 — B5 baseline SHA 유지. + # B4 frame selection = catalog declaration order (V4 evidence 미사용 — 별 axis). + content_objects = extract_content_objects(synth_section) + placement_plan = plan_placement( + content_objects=content_objects, + frame_contracts=list(load_frame_contracts().values()), + section_id=synth_section.section_id, + ) + mapper_frame_template_id = unit.frame_template_id + matches_mapper = ( + placement_plan.selected_template_id == mapper_frame_template_id + ) + match_note: Optional[str] = None + if not matches_mapper: + if placement_plan.selected_template_id is None: + match_note = "no_frame_covers_content_types" + else: + match_note = ( + f"B4 selected '{placement_plan.selected_template_id}' but " + f"mapper uses '{mapper_frame_template_id}' (composition V4 rank-1)" + ) + placement_trace = { + **asdict(placement_plan), + "mapper_frame_template_id": mapper_frame_template_id, + "frame_selection_matches_mapper": matches_mapper, + "frame_selection_match_note": match_note, + } + # ─── end trace-only runtime 연결 v0 ─── + # mapper 시도 — 실패 (FitError) 시 zone 을 adapter_needed 로 표시하고 skip try: slot_payload = map_mdx_to_slots(synth_section, unit.frame_template_id) @@ -1102,6 +1140,8 @@ def run_phase_z2_mvp1(mdx_path: Path, run_id: Optional[str] = None) -> Path: "content_truncated_count": truncated_count, # None / N (builder 가 N 개 자름) "assets_dir": str(assets_dir.relative_to(run_dir)) if assets_dir else None, "content_weight": content_weight, + # trace-only runtime 연결 v0 — B1 → B2 → B4 chain 결과 (render 미영향). + "placement_trace": placement_trace, }) # 6. Build layout CSS — horizontal-2 = dynamic heights (regression preserve), 그 외 = fr default