diff --git a/scripts/smoke_frame_render.py b/scripts/smoke_frame_render.py index ff34b19..60817a0 100644 --- a/scripts/smoke_frame_render.py +++ b/scripts/smoke_frame_render.py @@ -201,6 +201,23 @@ _MOCK_CONSTRUCTION_BIM_USAGE = { ], } +# Track A frame 4 — bim_dx_comparison_table (frame 18). +# NEW builder = compare_table_2col + NEW parser = compare_row_2col_item. +# slot_payload : title, col_a_label, col_b_label, rows=[{label, col_a, col_b}]. +_MOCK_BIM_DX_COMPARISON = { + "title": "BIM 과 DX 의 이해", + "col_a_label": "BIM", + "col_b_label": "DX", + "rows": [ + {"label": "범위", "col_a": "Only 3D", "col_b": "BIM << DX (ENG. + Mgmt 포함)"}, + {"label": "S/W", "col_a": "상용 S/W (Revit 등)", "col_b": "상용 + 전용 40~80개"}, + {"label": "프로세스", "col_a": "기존 2D 설계방식 유지", "col_b": "근본적 문제의식 통한 개선"}, + {"label": "성과물", "col_a": "3D 모델 중심", "col_b": "공학 정보 + 콘텐츠 연계"}, + {"label": "활용", "col_a": "분야별 단절", "col_b": "전 생애주기 활용 시스템"}, + {"label": "수행개념", "col_a": "수동적 / 집단적", "col_b": "적극·구체적 실현 방안"}, + ], +} + SELF_CHECK_FIXTURES: dict[str, dict] = { "three_parallel_requirements": _MOCK_THREE_PARALLEL, "process_product_two_way": _MOCK_PROCESS_PRODUCT, @@ -208,6 +225,7 @@ SELF_CHECK_FIXTURES: dict[str, dict] = { "three_persona_benefits": _MOCK_THREE_PERSONA_BENEFITS, "construction_goals_three_circle_intersection": _MOCK_CONSTRUCTION_GOALS, "construction_bim_three_usage": _MOCK_CONSTRUCTION_BIM_USAGE, + "bim_dx_comparison_table": _MOCK_BIM_DX_COMPARISON, } diff --git a/src/phase_z2_mapper.py b/src/phase_z2_mapper.py index c57625c..17c0e57 100644 --- a/src/phase_z2_mapper.py +++ b/src/phase_z2_mapper.py @@ -234,9 +234,39 @@ def parse_quadrant_item(unit: tuple[str, list[str]]) -> dict: return {"label": label, "body": body} +def parse_compare_row_2col_item(unit: tuple[str, list[str]]) -> dict: + """F18-style — bold = category label, nested 2 bullets = col_a / col_b values. + + Pattern : top bullet = **카테고리**, nested = `- BIM: ...` / `- DX: ...` 또는 + 단순 ordering (첫 nested = col_a, 두번째 = col_b). prefix "BIM:" / "DX:" 등 + 있으면 stripping. + + Returns: + {label, col_a, col_b} + """ + top_line, nested_lines = unit + label = _extract_bold_or_plain(top_line) + # nested bullets — strip bullet marker, take first 2 + nested = [] + for l in nested_lines: + l_strip = l.strip() + if re.match(r"^[\*\-]\s", l_strip): + txt = re.sub(r"^[\*\-]\s+", "", l_strip) + # strip optional "BIM:" / "DX:" prefix (anything before colon ≤ 5 chars) + m = re.match(r"^[A-Za-z가-힣]{1,8}\s*:\s*(.+)$", txt) + if m: + txt = m.group(1).strip() + txt = re.sub(r"\*\*(.+?)\*\*", r"\1", txt) + nested.append(txt) + col_a = nested[0] if len(nested) > 0 else "" + col_b = nested[1] if len(nested) > 1 else "" + return {"label": label, "col_a": col_a, "col_b": col_b} + + ITEM_PARSERS: dict[str, Callable] = { "pillar_item": parse_pillar_item, "quadrant_item": parse_quadrant_item, + "compare_row_2col_item": parse_compare_row_2col_item, } @@ -485,11 +515,55 @@ def _build_cycle_intersect_3(section, units, contract) -> dict: return payload +def _build_compare_table_2col(section, units, contract) -> dict: + """F18-style — compare table with 2 columns + N category rows. + + payload : + title : section.title + col_a_label : 좌 column header (예: "BIM") + col_b_label : 우 column header (예: "DX") + rows : list[{label, col_a, col_b}] — top_bullets 각각 → row + + builder_options : + item_parser : ITEM_PARSERS key (예: `compare_row_2col_item`) + col_a_label_default : col_a header (MDX 첫 행에 명시 안 되면 사용. default "") + col_b_label_default : col_b header (default "") + max_rows : N (default 999 — practical 한계). 초과 시 _truncated_count + """ + 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." + ) + + col_a_label = options.get("col_a_label_default", "") + col_b_label = options.get("col_b_label_default", "") + max_rows = options.get("max_rows", 999) + + payload: dict = {} + payload.update(_resolve_title(section, contract["payload"], contract)) + payload["col_a_label"] = col_a_label + payload["col_b_label"] = col_b_label + + visible = list(units[:max_rows]) + rows = [parser(u) for u in visible] + payload["rows"] = rows + + if len(units) > max_rows: + payload["_truncated_count"] = len(units) - max_rows + + 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, } diff --git a/templates/phase_z2/catalog/frame_contracts.yaml b/templates/phase_z2/catalog/frame_contracts.yaml index 980df91..87e82c6 100644 --- a/templates/phase_z2/catalog/frame_contracts.yaml +++ b/templates/phase_z2/catalog/frame_contracts.yaml @@ -386,3 +386,63 @@ construction_bim_three_usage: body_key_pattern: "category_{n}_body" empty_label: "" empty_body: [] + + +bim_dx_comparison_table: + # Reason : V4 UAI=1 (01-2 "용어간 상호관계") — UAI tier strongest after F12/F11. + # Pattern : table / compare-rows — 2-column compare table, N category rows. + # Track A frame 4 (Codex round 41 accepted, V4 priority strict). + # NEW builder `compare_table_2col` + NEW parser `compare_row_2col_item`. + template_id: bim_dx_comparison_table + frame_id: 1171281195 + family: table + + source_shape: top_bullets + cardinality: + strict: 2 # 2 columns (BIM vs DX 등) + overflow_policy: abort_or_review + + role_order: + - col_a + - col_b + + # min_height_px derivation : + # Figma frame compare table — N rows × ~40 px row height. + # Phase Z compact (title 30 + header 30 + 6 data rows × 35 + padding 30) = 300 + # + safety buffer (longer cell content 보호) 50 = **350**. F14 와 동등 class. + # Confirm via smoke + R3 artifact. + visual_hints: + min_height_px: 350 + + accepted_content_types: + - text_block + + # Frame Slot 선언 : header (col_a/col_b) + rows[]. + sub_zones: + - id: col_a_header + role: header + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f18b__header > .f18b__header-cell:nth-child(2)" + - id: col_b_header + role: header + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f18b__header > .f18b__header-cell:nth-child(3)" + - id: rows + role: data + accepts: [text_block] + cardinality: { min: 1, max: 12 } # N category rows (typical 4-8) + partial_target_path: ".f18b__rows" + + payload: + title: + source: section.title + # NEW builder `compare_table_2col` (phase_z2_mapper.py) + + # NEW parser `compare_row_2col_item` (ITEM_PARSERS). + builder: compare_table_2col + builder_options: + item_parser: compare_row_2col_item # NEW parser — top_bullet → {label, col_a, col_b} + col_a_label_default: "" # MDX 명시 또는 frame default + col_b_label_default: "" + max_rows: 12 # typical 4-8, overflow 보호 diff --git a/templates/phase_z2/families/bim_dx_comparison_table.html b/templates/phase_z2/families/bim_dx_comparison_table.html new file mode 100644 index 0000000..0cb21b3 --- /dev/null +++ b/templates/phase_z2/families/bim_dx_comparison_table.html @@ -0,0 +1,146 @@ + +{# +───────────────────────────────────────────────────────────────────────────── +Visual Provenance — figma_to_html_agent/blocks/1171281195/ (frame 18) +───────────────────────────────────────────────────────────────────────────── +Frame 18 = "BIM 과 DX 의 이해" (table / compare-rows). 2개 column header (BIM vs DX) + +중앙 category column + N category rows (범위 / S/W / 프로세스 / 성과물 / 활용 / +확장성 / 수행개념 / 주체 등). compare 관점의 다면 비교 표 디자인. + +본 partial = Track A frame 4 (Codex round 41 accepted, V4 UAI tier). NEW builder +`compare_table_2col` + NEW parser `compare_row_2col_item` 도입. + +3-layer architecture (matrix §0) : +- V4 = matching authority — V4 ranked this frame use_as_is for 01-2. +- figma_to_html = source/evidence — analysis/texts/index.html/flat/assets full. +- Phase Z = runtime orchestration — 본 commit 추가 runtime 자원. + +PROMOTED — CSS : + - title gradient (#000 → #883700, F13/F14/F12/F11 zone-title family) + - header row : col_a/col_b background tint + bold typography + - row 별 alternating background (zebra) — readability ↑ + - cell border : subtle divider (#A5BBB4 family — F11 와 통일) + +NOT PROMOTED (P1 case-by-case, compact zone fit) : + - Figma 의 column header 의 *raster icon* (있는 경우) — compact 에서 효과 ↓ + - 다양한 색 강조 / 한자 deco (Figma source 보존, 미 promote) + +ADAPTED : + - Figma 표 absolute positioning + zoom → Phase Z flex/grid (3-column table) + - typography → token-fixed (zone-title / sub-title / caption / body) + - row 별 height auto (content-fit) +───────────────────────────────────────────────────────────────────────────── +min_height_px derivation : + title 30 + header 30 + 6 data rows × 35 (compact) + padding 30 = 300 + + safety buffer (longer cell content) 50 = **350** (F14 와 동등 class). + Confirm via smoke + R3 artifact + V4 use_as_is target sample (MDX 01-2 미 trigger + in MDX 03 regression, 단 future batch verification 가능). +───────────────────────────────────────────────────────────────────────────── +slots : + - title : section.title + - col_a_label : 좌 column header (예: "BIM") + - col_b_label : 우 column header (예: "DX") + - rows : list[{label, col_a, col_b}] — N category rows +───────────────────────────────────────────────────────────────────────────── +4-class failure taxonomy : + - class 1 adapter readiness : NEW builder `compare_table_2col` + NEW parser + `compare_row_2col_item` 등록 필수 (PAYLOAD_BUILDERS / ITEM_PARSERS). 본 commit. + - class 2 content-fit : row 수 max 12 (builder limit). cell content 한 줄 한계 + — 긴 문장 시 truncate 또는 wrap (CSS overflow-hidden). +#} + + + +
+
{{ slot_payload.title }}
+
+
+
구분
+
{{ slot_payload.col_a_label | safe }}
+
{{ slot_payload.col_b_label | safe }}
+
+
+ {% for row in slot_payload.rows %} +
+
{{ row.label | safe }}
+
{{ row.col_a | safe }}
+
{{ row.col_b | safe }}
+
+ {% endfor %} +
+
+