Add Phase Z classifier placement diagnostics

- consume debug_zones[i].placement_trace in classify_visual_runtime_check
- surface per-zone diagnostic in fit_classification.placement_diagnostics
- preserve canonical render SHA and existing classifier output schema
This commit is contained in:
2026-05-04 17:40:21 +09:00
parent b6b9173d2b
commit 565e6b092e

View File

@@ -288,6 +288,51 @@ def classify_overflow(
# ─── visual_runtime_check 결과 → 전체 fit_classification trace ────
def _build_placement_diagnostic_for_zone(
zone_position: str,
placement_trace: Optional[dict],
mapper_template_id: Optional[str],
) -> dict:
"""zone 별 placement diagnostic 빌더 — placement_trace from B4 → 진단 dict.
phase_z2_pipeline.py 의 trace-only B1→B2→B4 chain 결과 (debug_zones[i].placement_trace)
를 per-zone surface 한 진단. classifier 는 *consume only* — placement_trace
raw 구조는 미변경.
Args:
zone_position : zone position ("top" / "bottom_l" 등)
placement_trace : phase_z2_pipeline.py 의 placement_trace dict 또는 None
mapper_template_id : 기존 mapper / V4 가 선택한 frame template_id
(placement_trace 에 mapper_frame_template_id 누락 시 fallback)
Returns:
per-zone placement diagnostic dict (shape-stable, missing fields = None / 0).
"""
if placement_trace is None:
return {
"zone_position": zone_position,
"mapper_frame_template_id": mapper_template_id,
"b4_selected_template_id": None,
"frame_selection_matches_mapper": None,
"frame_selection_match_note": "no placement_trace recorded",
"region_count": 0,
"slot_assignment_count": 0,
"rejection_count": 0,
}
return {
"zone_position": zone_position,
"mapper_frame_template_id": (
placement_trace.get("mapper_frame_template_id") or mapper_template_id
),
"b4_selected_template_id": placement_trace.get("selected_template_id"),
"frame_selection_matches_mapper": placement_trace.get("frame_selection_matches_mapper"),
"frame_selection_match_note": placement_trace.get("frame_selection_match_note"),
"region_count": len(placement_trace.get("internal_regions") or []),
"slot_assignment_count": len(placement_trace.get("slot_assignments") or []),
"rejection_count": len(placement_trace.get("rejection") or []),
}
def classify_visual_runtime_check(overflow: dict, debug_zones: list[dict]) -> dict:
"""Selenium overflow + composition 의 zone debug → 전체 fit_classification 산출.
@@ -304,7 +349,21 @@ def classify_visual_runtime_check(overflow: dict, debug_zones: list[dict]) -> di
summary : 텍스트 요약 (n events, categories seen)
categories_seen : 등장한 카테고리 unique list
unclassified_signals : 미분류 신호 (raw Selenium 결과 중 분류 안 된 것)
placement_diagnostics : per-zone placement_trace 진단 (B4 vs mapper
divergence + region / slot_assignment / rejection
count) — passed 여부 무관 항상 surface
"""
# placement_diagnostics — debug_zones[i].placement_trace 를 per-zone diagnostic 으로 surface.
# passed 여부 무관 항상 빌드 (B4 vs mapper divergence 가 passed 에서도 진단 가치).
placement_diagnostics = [
_build_placement_diagnostic_for_zone(
zone_position=dz.get("position", "?"),
placement_trace=dz.get("placement_trace"),
mapper_template_id=dz.get("v4_template_id"),
)
for dz in (debug_zones or [])
]
if overflow.get("passed", False):
return {
"visual_check_passed": True,
@@ -312,6 +371,7 @@ def classify_visual_runtime_check(overflow: dict, debug_zones: list[dict]) -> di
"summary": "visual check passed — no overflow to classify",
"categories_seen": [],
"unclassified_signals": [],
"placement_diagnostics": placement_diagnostics,
}
# zone position → debug_zones 매핑 (capacity_fit_status 추출용)
@@ -392,4 +452,5 @@ def classify_visual_runtime_check(overflow: dict, debug_zones: list[dict]) -> di
),
"categories_seen": categories,
"unclassified_signals": unclassified,
"placement_diagnostics": placement_diagnostics,
}