diff --git a/scripts/smoke_frame_render.py b/scripts/smoke_frame_render.py index bfe9926..0463869 100644 --- a/scripts/smoke_frame_render.py +++ b/scripts/smoke_frame_render.py @@ -281,15 +281,29 @@ SELF_CHECK_FIXTURES: dict[str, dict] = { "emphasis_3_body": [{"text": "특수성 반영 어려움", "indent": 0}], }, "bim_current_problems_paired": { + # F17 schema-correction — 8 atomic issues per source texts.md (round 55~73 lock). + # paired_rows_4x2_alternating_pills : 4 rows × 2 cells, row 1/3 pill top, row 2/4 pill bottom. "title": "현황 및 문제점", - "issue_1_label": "개념 부재", - "issue_1_body": [{"text": "CAD 확장판 오인", "indent": 0}], - "issue_2_label": "잘못된 접근방식", - "issue_2_body": [{"text": "도구로만 인식", "indent": 0}], - "issue_3_label": "방향성 상실", - "issue_3_body": [{"text": "대형 S/W 의존", "indent": 0}], - "issue_4_label": "전제조건 오류", - "issue_4_body": [{"text": "건축·토목 혼용", "indent": 0}], + # row 1 : BIM 의미 인식 오류 (개념 부재 + 잘못된 접근방식) + "row_1_left_label": "개념 부재", + "row_1_left_body": [{"text": "BIM을 CAD 확장판으로 오인, 3D 도구 정도로만 인식", "indent": 0}], + "row_1_right_label": "잘못된 접근방식", + "row_1_right_body": [{"text": "단순 업무효율 도구로만 인식, 교육으로 해결될 것으로 판단", "indent": 0}], + # row 2 : 기술 방향 의존 (방향성 상실 + 전제조건 오류) + "row_2_left_label": "방향성 상실", + "row_2_left_body": [{"text": "대형 S/W 회사 제시 내용 추종, 자체 목표설정 기능 상실", "indent": 0}], + "row_2_right_label": "전제조건 오류", + "row_2_right_body": [{"text": "건축·토목 동일 전제로 건축 방식을 토목에 그대로 적용", "indent": 0}], + # row 3 : 실행 주체 혼란 (수행주체 혼란 + 수행방식 무지) + "row_3_left_label": "수행주체 혼란", + "row_3_left_body": [{"text": "학자·발주처 주도, 실행주체 기업·기술자는 기존 방식 고수", "indent": 0}], + "row_3_right_label": "수행방식 무지", + "row_3_right_body": [{"text": "2D 결과 전제, 3D 수행 경험 부재, 비용·시간 증가·품질 미흡", "indent": 0}], + # row 4 : 외부 의존성 (외산S/W 기술예속 + H/W 미비) + "row_4_left_label": "외산S/W 기술예속", + "row_4_left_body": [{"text": "외산 범용 S/W 만으로 BIM 가능 인식, 기술예속 가속", "indent": 0}], + "row_4_right_label": "H/W 미비", + "row_4_right_body": [{"text": "탁상용 PC·Monitor 수준, 고품질 모델 표출 한계", "indent": 0}], }, } diff --git a/src/phase_z2_mapper.py b/src/phase_z2_mapper.py index 69cffe0..ae71405 100644 --- a/src/phase_z2_mapper.py +++ b/src/phase_z2_mapper.py @@ -577,12 +577,73 @@ def _build_compare_table_2col(section, units, contract) -> dict: return payload +def _build_paired_rows_4x2_slots(section, units, contract) -> dict: + """F17-style — paired_rows_4x2_alternating_pills. top_bullets 8 units → 2-axis keyed slots. + + 1-axis (quadrant_flat_slots = TL/TR/BL/BR) vs 2-axis (row × side) : + - quadrant : index 1..4 → quadrant_N_{label,body} + - paired_rows_4x2 : index 1..8 → row_R_SIDE_{label,body} where R = ceil(i/2), SIDE = left|right + + deterministic index mapping per Codex round 60 §Q3 answer + round 70 §1 : + unit 1 → row_1_left unit 2 → row_1_right + unit 3 → row_2_left unit 4 → row_2_right + unit 5 → row_3_left unit 6 → row_3_right + unit 7 → row_4_left unit 8 → row_4_right + + strict 8 : under/over → FitError before render (Codex round 60 §3, round 62 acceptance + criterion "no pad_to/truncate_at fallback hides cardinality mismatch"). + parser = quadrant_item (label + body heading-less) — F17 atomic issue = single label + single body. + + builder_options : + item_parser : ITEM_PARSERS key (default = "quadrant_item") + label_key_pattern : "row_{r}_{side}_label" + body_key_pattern : "row_{r}_{side}_body" + rows : 4 + sides : ["left", "right"] + """ + options = contract["payload"]["builder_options"] + parser_name = options["item_parser"] + parser = ITEM_PARSERS.get(parser_name) + if parser is None: + raise ValueError( + f"Contract '{contract['template_id']}' references item_parser='{parser_name}' " + f"but ITEM_PARSERS has no such entry." + ) + + label_key = options.get("label_key_pattern", "row_{r}_{side}_label") + body_key = options.get("body_key_pattern", "row_{r}_{side}_body") + rows = options.get("rows", 4) + sides = options.get("sides", ["left", "right"]) + expected = rows * len(sides) + + if len(units) != expected: + raise ValueError( + f"Contract '{contract['template_id']}' requires strict {expected} units " + f"(rows={rows} × sides={len(sides)}), got {len(units)}. " + f"silent pad/truncate is disabled for paired_rows_4x2_slots." + ) + + payload: dict = {} + payload.update(_resolve_title(section, contract["payload"], contract)) + + parsed = [parser(u) for u in units] + idx = 0 + for r in range(1, rows + 1): + for side in sides: + payload[label_key.format(r=r, side=side)] = parsed[idx]["label"] + payload[body_key.format(r=r, side=side)] = parsed[idx]["body"] + idx += 1 + + return payload + + PAYLOAD_BUILDERS: dict[str, Callable] = { "items_with_role": _build_items_with_role, "process_product_pair": _build_process_product_pair, "quadrant_flat_slots": _build_quadrant_flat_slots, "cycle_intersect_3": _build_cycle_intersect_3, "compare_table_2col": _build_compare_table_2col, + "paired_rows_4x2_slots": _build_paired_rows_4x2_slots, } diff --git a/templates/phase_z2/catalog/frame_contracts.yaml b/templates/phase_z2/catalog/frame_contracts.yaml index 46723cb..f86d64b 100644 --- a/templates/phase_z2/catalog/frame_contracts.yaml +++ b/templates/phase_z2/catalog/frame_contracts.yaml @@ -635,68 +635,95 @@ sw_reality_three_emphasis: bim_current_problems_paired: - # Reason : V4 RS=1 (4 MDX sample 안 restructure 1). 32-frame all-in scope — - # **catalog-completeness activation** (NOT V4 endorsement — V4 신호 유효 단 restructure - # tier 이며 직접 use_as_is/light_edit 추천 없음). - # Pattern : cards/paired-rows-2x2 — 4 problem cards in 2x2 grid (BIM 수행 4 문제). - # Track A frame 8. Builder 재사용 = `quadrant_flat_slots` (F16 pattern, pad_to=4). + # Reason : V4 RS=1 (4 MDX sample 안 restructure 1). source 가 8 atomic issues 임을 confirm. + # **schema-correction** — round 55~73 의 review-loop 의 calibration frame. + # 자체 round 71 §4 F17 done-definition lock per Codex round 70/72 + Claude round 63 §5. + # Pattern : cards/paired-rows-4x2 — 4 paired rows × 2 issue cells (top/bottom pill alternation). + # Track A frame 8. 신규 builder = `paired_rows_4x2_slots` (quadrant_flat_slots → 2-axis). template_id: bim_current_problems_paired frame_id: 1171281194 family: cards - source_shape: top_bullets + source_shape: top_bullets # mapper split_source allow-list 정합 (Codex round 60) + layout_variant: paired_rows_4x2_alternating_pills # runtime projection model cardinality: - strict: 4 # 4 issues (2x2 grid) + strict: 8 # 8 atomic issues (4 rows × 2 cells per source texts.md) overflow_policy: abort_or_review + # row × side deterministic mapping per Codex round 70 §1. role_order: - - issue_1 - - issue_2 - - issue_3 - - issue_4 + - row_1_left + - row_1_right + - row_2_left + - row_2_right + - row_3_left + - row_3_right + - row_4_left + - row_4_right - # min_height_px : F16 (quadrant 4) 와 동등 class. - # 2 row × 2 col × (label 24 + body 60) + title 30 + gaps + padding = ~280 + buffer 70 = **350**. + # min_height_px : provisional 380 (8 cells + title + gaps), smoke 후 1 회 조정 가능. + # pill_positions : source index.html line 161~225 의 row-별 pill 상/하 alternation (top/bottom/top/bottom). + # row_gap_after : source line 199-200 의 `