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
This commit is contained in:
2026-05-04 10:32:26 +09:00
parent 02a6d44944
commit 425a3054c1

View File

@@ -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