feat(catalog): activate construction_goals_three_circle_intersection (IMP-04 Track A 2/16)
Reason : V4 strongest UAI tier candidate (use_as_is=1 for 02-1, light_edit=1
for 01-1, restructure=1). Track A frame 2 per Codex rounds 30/33/35 V4-
priority rule. F14 clean pass completed at 834ed39; this is the next
Track A activation.
3-layer architecture context (matrix §0) :
- V4 = matching authority — V4 ranked this frame as use_as_is for the
"DX의 궁극적 목표" section (02-1) and light_edit for "용어 정의" (01-1).
- figma_to_html (1171281189) = rich source/evidence — 510-line index.html
base, full analysis/flat/texts/assets present (A+T+I+F+S).
- Phase Z = runtime orchestration — this commit adds the runtime contract,
builder, partial, and fixture so the V4 candidate can be assembled.
New runtime additions :
1. src/phase_z2_mapper.py — new `cycle_intersect_3` PAYLOAD_BUILDERS entry
- Reuses existing `quadrant_item` ITEM_PARSERS (label only, body
ignored) — F16 parser reused, no new parser.
- Produces flat keys : circle_1_label / circle_2_label / circle_3_label
+ intersection text (optional) — distinct from F16's quadrant_N_body
structure since this frame's 3 main circles use labels only.
- pad_to=3, truncate_at=3, configurable via builder_options.
2. templates/phase_z2/families/construction_goals_three_circle_intersection.html
- Adapted from figma_to_html_agent/blocks/1171281189/index.html.
- Slot mapping : title + 3 circle labels + optional intersection text.
- PROMOTED CSS : 3 circle gradients (safety #BC652B/#A24200, productivity
#897445/#3E3523, trust #296B55/#123328) + outer multiply blend +
title gradient (#000 → #883700, F13/F14 zone-title family) + main
label typography (white text + shadow).
- NOT PROMOTED (P1 case-by-case, compact zone fit) : 6 accent hanja
circles (安/質/速/利/通/信), 6 side labels (안전성 제고 etc.), 3
decoration rects, 3 arc images, bg-texture multiply image. These
are Figma-side decorative content not in MDX and would clutter a
Phase Z zone of ~320 px.
- ADAPTED : Figma 70/50/40 px → token-fixed font sizes, 350×350
absolute-positioned overlapping circles → 110×110 flex row (cycle
intent expressed via intersection text instead of geometric overlap).
3. templates/phase_z2/catalog/frame_contracts.yaml — append F12 contract
- template_id, frame_id 1171281189, family=diagram, source_shape=
top_bullets, strict cardinality 3, role_order [safety, productivity,
trust].
- visual_hints.min_height_px = 320, derived from 3 circle row 80 +
title 30 + label area 60 + intersection 30 + padding 40 = 240
+ 80 safety buffer (lighter than F14's 350 since CSS-only).
- accepted_content_types = [text_block] only.
- 4 sub_zones declared (circle_1/2/3 main_text + intersection emphasis).
4. scripts/smoke_frame_render.py — add bundled fixture for F12 self-check.
Verification :
- python -m py_compile src/phase_z2_mapper.py scripts/smoke_frame_render.py
: PASS
- python scripts/smoke_frame_render.py --self-check : PASS 5/5 (F12 added
at 3691 chars CSS-only)
- python scripts/smoke_frame_render.py construction_goals_three_circle_intersection
--render-to data/runs/imp04_f12_visual : PASS, R3 artifact written. 0
raster refs (CSS-only partial); copy_assets ran successfully and
produced data/runs/imp04_f12_visual/assets/construction_goals_three_circle_intersection/
with the frame's 4 PNG files (unused since partial is CSS-only — assets
remain available for future raster promotion if visual inspection
flags fidelity loss).
- python run_mdx03_pipeline.py --phase-z2 --run-id imp04_f12_regression
: PASS (MDX 03 V4 rank-1 still F13/F29, F12 not selected — F12 only
triggered by 02-1 / 01-1 sections per V4 evidence)
scope-lock honored : V4 logic / V4 evidence / mapper existing builders /
composition planner / Phase R' / pipeline production render path / AI/Kei
all unchanged. New builder added without modifying existing 3 (mixed
strategy per scope-lock §4).
Calibration status (matrix §4.1 Fix 7 4-class) :
- class 1 adapter readiness : new builder registered, partial loadable,
contract valid, smoke passing.
- class 2 content-fit : compact 110×110 circles + label, watch for label
overflow if MDX bullets exceed ~12 chars.
- class 3/4 mapping/routing : not applicable for this commit.
- Codex review mandatory per scope-lock §5 (new builder pattern
cycle_intersect_3 first introduction).
Refs Gitea #4 (IMP-04 Track A frame 2 — V4 strongest UAI tier)
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 명시 안 되면 빈 문자
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
|
||||
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
|
||||
{#
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
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) 권장
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
#}
|
||||
|
||||
<style>
|
||||
.f12b {
|
||||
width: 100%; height: 100%;
|
||||
display: flex; flex-direction: column;
|
||||
gap: 6px;
|
||||
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
|
||||
word-break: keep-all;
|
||||
}
|
||||
.f12b__title {
|
||||
font-size: var(--font-zone-title);
|
||||
font-weight: 700;
|
||||
line-height: var(--lh-zone-title);
|
||||
background-image: linear-gradient(180deg, #000 0%, #883700 100%);
|
||||
-webkit-background-clip: text; background-clip: text;
|
||||
color: transparent;
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 0 3px rgba(50,44,30,0.4));
|
||||
}
|
||||
|
||||
/* 3 circle row — compact flex layout (Figma 의 겹침 cycle layout 단순화) */
|
||||
.f12b__circles {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
gap: 16px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.f12b__circle {
|
||||
flex: 0 0 auto;
|
||||
width: 110px; height: 110px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
}
|
||||
/* outer ring (Figma multiply blend 의도) — CSS gradient approximation */
|
||||
.f12b__circle::before {
|
||||
content: "";
|
||||
position: absolute; inset: -8px;
|
||||
border-radius: 50%;
|
||||
z-index: -1;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
/* per-circle gradient (PROMOTED from index.html L141-189) */
|
||||
.f12b__circle--safety {
|
||||
background: linear-gradient(145.28deg, #BC652B 16.04%, #A24200 55.20%);
|
||||
border: 3px solid #fff;
|
||||
}
|
||||
.f12b__circle--safety::before {
|
||||
background: linear-gradient(145.28deg, #FDC69E 16.04%, #E0782C 55.20%);
|
||||
}
|
||||
.f12b__circle--productivity {
|
||||
background: linear-gradient(153.95deg, #897445 15.27%, #3E3523 61.74%);
|
||||
border: 3px solid #fff;
|
||||
}
|
||||
.f12b__circle--productivity::before {
|
||||
background: linear-gradient(218.84deg, #D5AA89 14.08%, #737373 92.67%);
|
||||
}
|
||||
.f12b__circle--trust {
|
||||
background: linear-gradient(153.95deg, #296B55 15.27%, #123328 61.74%);
|
||||
border: 3px solid #fff;
|
||||
}
|
||||
.f12b__circle--trust::before {
|
||||
background: linear-gradient(145.90deg, #FFFFFF 8.47%, #253E1F 87.56%);
|
||||
}
|
||||
|
||||
/* circle 안 label (white text + shadow, index.html L192-204) */
|
||||
.f12b__circle-label {
|
||||
color: #fff;
|
||||
font-size: var(--font-caption);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
text-shadow: 0 0 4px rgba(0,0,0,0.6);
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
/* intersection — 중앙 교차 텍스트 (optional, MDX 의 emphasis content) */
|
||||
.f12b__intersection {
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
padding: 6px 12px;
|
||||
font-size: var(--font-sub-title);
|
||||
font-weight: 700;
|
||||
color: #883700; /* PROMOTED — title gradient 의 end color */
|
||||
background: linear-gradient(180deg, rgba(255,250,240,0.6) 0%, rgba(255,250,240,0) 100%);
|
||||
border-top: 1px dashed #883700;
|
||||
line-height: 1.3;
|
||||
min-height: 0;
|
||||
}
|
||||
.f12b__intersection:empty {
|
||||
display: none; /* slot 빈 경우 자동 hide */
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="f12b" data-frame-id="1171281189" data-template-id="construction_goals_three_circle_intersection">
|
||||
<div class="f12b__title">{{ slot_payload.title }}</div>
|
||||
<div class="f12b__circles">
|
||||
<div class="f12b__circle f12b__circle--safety">
|
||||
<div class="f12b__circle-label">{{ slot_payload.circle_1_label | safe }}</div>
|
||||
</div>
|
||||
<div class="f12b__circle f12b__circle--productivity">
|
||||
<div class="f12b__circle-label">{{ slot_payload.circle_2_label | safe }}</div>
|
||||
</div>
|
||||
<div class="f12b__circle f12b__circle--trust">
|
||||
<div class="f12b__circle-label">{{ slot_payload.circle_3_label | safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f12b__intersection">{{ slot_payload.intersection | safe }}</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user