diff --git a/scripts/smoke_frame_render.py b/scripts/smoke_frame_render.py index 45b03fc..18dabb1 100644 --- a/scripts/smoke_frame_render.py +++ b/scripts/smoke_frame_render.py @@ -172,11 +172,23 @@ _MOCK_THREE_PERSONA_BENEFITS = { ], } +# Track A frame 2 — construction_goals_three_circle_intersection (frame 12). +# Builder = cycle_intersect_3 + quadrant_item parser (label only). +# slot_payload : title, circle_1_label, circle_2_label, circle_3_label, intersection. +_MOCK_CONSTRUCTION_GOALS = { + "title": "건설산업의 목표 (BIM 의 목적)", + "circle_1_label": "안전과 품질", + "circle_2_label": "생산성 향상", + "circle_3_label": "소통과 신뢰", + "intersection": "3요소가 조화를 이룰 때 BIM 의 궁극적 목표 달성", +} + SELF_CHECK_FIXTURES: dict[str, dict] = { "three_parallel_requirements": _MOCK_THREE_PARALLEL, "process_product_two_way": _MOCK_PROCESS_PRODUCT, "bim_issues_quadrant_four": _MOCK_QUADRANT, "three_persona_benefits": _MOCK_THREE_PERSONA_BENEFITS, + "construction_goals_three_circle_intersection": _MOCK_CONSTRUCTION_GOALS, } diff --git a/src/phase_z2_mapper.py b/src/phase_z2_mapper.py index d3ceb9c..c57625c 100644 --- a/src/phase_z2_mapper.py +++ b/src/phase_z2_mapper.py @@ -431,10 +431,65 @@ def _build_quadrant_flat_slots(section, units, contract) -> dict: return payload +def _build_cycle_intersect_3(section, units, contract) -> dict: + """F12-style — cycle-3way-intersection. top_bullets 3 items → flat keyed + circle_1_label / circle_2_label / circle_3_label. *body 무시* (label only — + 이 frame 의 3 메인 원 visual 은 label 만 사용). intersection 텍스트는 별 + optional (default 빈 문자). + + F16 quadrant_flat_slots 와 비교 : + - F16 : N=4 + body 사용 (quadrant_N_label + quadrant_N_body) + - F12 : N=3 + body 미사용 (circle_N_label 만) + intersection text 별 + + builder_options : + item_parser : ITEM_PARSERS key (label 만 사용, body 무시) + pad_to : N (default=3) — units < N 이면 empty label 로 채움 + truncate_at : M (default=3) — units > M 이면 무시 + _truncated_count + label_key_pattern : "circle_{n}_label" (n = 1-based) + empty_label : pad slot 의 label 값 (default = "") + intersection_default : intersection 텍스트 (slot optional — default 빈 문자) + """ + 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." + ) + + pad_to = options.get("pad_to", 3) + truncate_at = options.get("truncate_at", pad_to) + label_key = options.get("label_key_pattern", "circle_{n}_label") + empty_label = options.get("empty_label", "") + intersection = options.get("intersection_default", "") + + payload: dict = {} + payload.update(_resolve_title(section, contract["payload"], contract)) + + visible_units = list(units[:truncate_at]) + parsed = [parser(u) for u in visible_units] + + for i in range(pad_to): + n = i + 1 + if i < len(parsed): + payload[label_key.format(n=n)] = parsed[i]["label"] + else: + payload[label_key.format(n=n)] = empty_label + + payload["intersection"] = intersection + + if len(units) > truncate_at: + payload["_truncated_count"] = len(units) - truncate_at + + 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, } diff --git a/templates/phase_z2/catalog/frame_contracts.yaml b/templates/phase_z2/catalog/frame_contracts.yaml index 3cab5e8..9335c00 100644 --- a/templates/phase_z2/catalog/frame_contracts.yaml +++ b/templates/phase_z2/catalog/frame_contracts.yaml @@ -248,3 +248,73 @@ three_persona_benefits: item_parser: quadrant_item # ITEM_PARSERS entry (F16 재사용) array_root: personas # payload.personas = list of items role_field: color_class # role_order[i] → item.color_class + + +construction_goals_three_circle_intersection: + # Reason : V4 use_as_is=1 (02-1) + light_edit=1 (01-1) + restructure=1 — UAI tier strongest. + # Pattern : diagram / cycle-3way-intersection (3 메인 원 + 중앙 intersection). + # Track A frame 2 / IMP-04 Track A V4 priority strict (Codex round 30/33/35 합의). + template_id: construction_goals_three_circle_intersection + frame_id: 1171281189 + family: diagram + + source_shape: top_bullets + cardinality: + strict: 3 # 3 메인 원 — strict. + overflow_policy: abort_or_review + + # 순서 기반 visual role (3 메인 원 각자 다른 gradient — index.html L141-189). + # circle_1 = 안전(orange/red), circle_2 = 생산성(brown/grey), circle_3 = 소통(green/dark). + role_order: + - safety + - productivity + - trust + + # min_height_px derivation : + # Figma frame total height = 1195 px @ scale 0.58312 → 697 px adapted (full frame). + # Phase Z slide-body ≤ 585 px → compact adapted layout 필요. + # Content density (3 메인 원 visual + title) — F14 (350, raster-heavy) 보다 가벼움. + # Derived = 320 (3 원 60-80 px × 3 + title 30 + intersection 30 + padding 30). + # Confirm via smoke `--render-to` artifact (R3 acceptance gate). + visual_hints: + min_height_px: 320 + + accepted_content_types: + - text_block + + # Frame Slot 선언 (SPEC v1 §3 layer B). 3 메인 원 + intersection center. + sub_zones: + - id: circle_1 + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f12b__circles > .f12b__circle:nth-child(1)" + - id: circle_2 + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f12b__circles > .f12b__circle:nth-child(2)" + - id: circle_3 + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f12b__circles > .f12b__circle:nth-child(3)" + - id: intersection + role: emphasis + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f12b__intersection" + + payload: + title: + source: section.title + # New builder `cycle_intersect_3` (phase_z2_mapper.py) — 3 circle labels + intersection. + # parser 재사용 = `quadrant_item` (label 만 사용, body 무시). + builder: cycle_intersect_3 + builder_options: + item_parser: quadrant_item # F16 의 parser 재사용 (label only) + pad_to: 3 + truncate_at: 3 + label_key_pattern: "circle_{n}_label" + empty_label: "" + intersection_default: "" # MDX 안 intersection 명시 안 되면 빈 문자 diff --git a/templates/phase_z2/families/construction_goals_three_circle_intersection.html b/templates/phase_z2/families/construction_goals_three_circle_intersection.html new file mode 100644 index 0000000..153734c --- /dev/null +++ b/templates/phase_z2/families/construction_goals_three_circle_intersection.html @@ -0,0 +1,173 @@ + +{# +───────────────────────────────────────────────────────────────────────────── +Visual Provenance — figma_to_html_agent/blocks/1171281189/ (frame 12) +───────────────────────────────────────────────────────────────────────────── +Frame 12 = "건설산업의 목표 (BIM 의 목적)" (cycle-3way-intersection, 2195×1195 +px, scale 0.58312). Figma 원본 = 3 메인 원 (안전·품질 / 생산성·향상 / 소통· +신뢰) + 6 액센트 한자 원 (安/質/速/利/通/信) + 6 사이드 라벨 (안전성 제고 등) ++ 3 장식 rect + 3 arc + bg texture. + +본 partial = Track A frame 2 (Codex round 30/33/35 합의, V4 strongest UAI tier). +`index.html` (510 lines) base + Phase Z 규약 adapt + **compact MDX-mapped focus** +(MDX 콘텐츠와 직접 매핑되는 5 slots 만 유지. Figma 의 frame-side decoration +는 NOT PROMOTED — zone 사이즈에서 cluttered 회피). + +PROMOTED — CSS (Figma 색/디자인 의도 → CSS 으로 충분) : + - circle 1 (safety) gradient : index.html L142-145 (outer) + L149-154 (inner) + outer = linear-gradient(145.28deg, #FDC69E, #E0782C) multiply blend + inner = linear-gradient(145.28deg, #BC652B, #A24200) + white border + shadow + - circle 2 (productivity) : index.html L158-172 + outer = linear-gradient(218.84deg, #D5AA89, #737373) multiply + inner = linear-gradient(153.95deg, #897445, #3E3523) + white border + - circle 3 (trust) : index.html L175-189 + outer = linear-gradient(145.90deg, #FFFFFF, #253E1F) multiply + inner = linear-gradient(153.95deg, #296B55, #123328) + white border + - title gradient : #000 → #883700 (index.html L42-46) — F13/F14 zone-title 와 동일 family + - main label typography : 700 weight, white, text-shadow (index.html L192-204) + +NOT PROMOTED — Phase Z compact zone fit (자체 결정, P1 case-by-case) : + - 6 액센트 한자 원 (安/質/速/利/通/信) — Figma 외 deco, MDX 무관, zone 사이즈에서 cluttered + - 6 사이드 라벨 (안전성 제고 / 품질 향상 / 신속·정확성 증진 / 비용저감 / 소통·이해 / 신뢰·투명성) + — Figma source 의 정해진 문구. MDX content 와 무관 deco text + - 3 장식 rect (gradient 곡선 wrap) + 3 arc 이미지 — Figma 의 시각 풍부함이지만 zone-size 에서 visible 효과 약함 + - bg-texture multiply image (index.html L375) — zone 사이즈에서 effect 미미 + +ADAPTED : + - Figma 70/50/40px → token-fixed (zone-title 13 / sub-title 12 / caption / body 11) + - 3 메인 원 absolute positioning + zoom 0.58312 → Phase Z flex/grid (3-circle row) + - 350×350 원 사이즈 → compact 70-90 px circle (zone fit) + - Figma 의 3 원 *겹침 layout* (안전-품질 위 / 생산성 좌 / 소통 우) → flex row (분리, *cycle 의 의미*는 intersection text 가 표현) +───────────────────────────────────────────────────────────────────────────── +min_height_px derivation (Codex round 13 §2.2 — derive + confirm) : + Figma frame 1195 px @ scale 0.58312 → 697 px adapted (full frame, 6 deco 포함). + Phase Z compact (3 메인 원 + title + intersection) : + title 30 + 3 circle row 80 + label area 60 + intersection 30 + padding 40 = 240 px + + safety buffer (가벼운 텍스트 overflow 보호) 80 = **320** (F14 350 보다 가벼움). + Confirm via smoke harness `--render-to` artifact. +───────────────────────────────────────────────────────────────────────────── +slots : + - title : section.title (Jinja 변수) + - circle_1_label / 2_label / 3_label : 3 메인 원 label (cycle_intersect_3 builder 산출) + - intersection : 중앙 교차 텍스트 (optional) +───────────────────────────────────────────────────────────────────────────── +4-class failure taxonomy (matrix §4.1 Fix 7) : + - class 1 adapter readiness 보호 : new builder cycle_intersect_3 가 PAYLOAD_BUILDERS + 에 등록되어야 contract 가 valid. 안 등록 시 → builder None → 본 frame runtime fail + - class 2 content-fit : MDX 의 3 bullets 가 너무 길면 (label > ~20 chars) overflow. + label 짧게 (compact) 권장 +───────────────────────────────────────────────────────────────────────────── +#} + + + +
+
{{ slot_payload.title }}
+
+
+
{{ slot_payload.circle_1_label | safe }}
+
+
+
{{ slot_payload.circle_2_label | safe }}
+
+
+
{{ slot_payload.circle_3_label | safe }}
+
+
+
{{ slot_payload.intersection | safe }}
+