feat(catalog): activate bim_dx_comparison_table (IMP-04 Track A 4/16)
Reason : V4 UAI=1 (01-2 "용어간 상호관계") — UAI tier strongest after F12/F11.
Track A frame 4 per Codex round 41 V4-priority acceptance.
3-layer architecture (matrix §0) :
- V4 = matching authority — V4 ranked this frame use_as_is for 01-2.
- figma_to_html (1171281195) = source/evidence — analysis/texts/index.html/
flat/assets all present.
- Phase Z = runtime orchestration — adds catalog + new builder + new parser +
new partial + smoke fixture.
NEW builder + NEW parser (Codex round 41 mandatory review path) :
1. src/phase_z2_mapper.py — NEW `compare_row_2col_item` parser in ITEM_PARSERS
- input : (top_line, nested_lines)
- output : {label, col_a, col_b}
- label = bold from top_line
- col_a / col_b = first 2 nested bullets, optional prefix stripping ("BIM:"/
"DX:" or similar ≤8-char tag with colon)
- inline emphasis preserved as <strong>
2. src/phase_z2_mapper.py — NEW `compare_table_2col` PAYLOAD_BUILDERS entry
- payload : title + col_a_label + col_b_label + rows[]
- builder_options : item_parser, col_a/b_label_default, max_rows (default 999)
- max_rows truncation tracked via _truncated_count
3. templates/phase_z2/families/bim_dx_comparison_table.html — NEW partial
- 3-column grid (category / col_a / col_b) with header row + N data rows
- PROMOTED CSS : title gradient (#000 → #883700, zone-title family), header
brown bg (rgba(50,31,9,0.85-0.95)), zebra striping, brown family bullet
accent, subtle border (#A5BBB4 F11 family).
- NOT PROMOTED (P1 case-by-case + preservation guardrail) : Figma column
header raster icons, color emphasis variants, hanja deco. figma_to_html
source evidence remains preserved.
- ADAPTED : Figma absolute positioning + zoom → Phase Z flex/grid 3-col
table, typography → token-fixed, row heights auto content-fit.
4. templates/phase_z2/catalog/frame_contracts.yaml — F18 contract appended
- frame_id=1171281195, family=table, source_shape=top_bullets, strict 2
(2 columns), role_order=[col_a, col_b].
- visual_hints.min_height_px = 350 (title 30 + header 30 + 6 rows×35 +
padding 30 = 300 + 50 buffer; F14-class).
- accepted_content_types = [text_block].
- sub_zones : col_a_header / col_b_header (strict 1 each) + rows (min 1,
max 12 category rows).
5. scripts/smoke_frame_render.py — bundled fixture for F18 self-check (6
category rows : 범위 / S/W / 프로세스 / 성과물 / 활용 / 수행개념).
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 7/7 (F18 added
at 4211 chars CSS-only)
- python scripts/smoke_frame_render.py bim_dx_comparison_table --render-to
data/runs/imp04_f18_visual : PASS, R3 artifact, 0 raster refs (CSS-only)
- python run_mdx03_pipeline.py --phase-z2 --run-id imp04_f18_regression :
PASS (MDX 03 V4 rank-1 still F13/F29; F18 only routes 01-2 per V4)
scope-lock honored (3-layer + 4-class) :
- V4 logic / V4 evidence yaml : unchanged
- Existing PAYLOAD_BUILDERS (4 builders) : unchanged. compare_table_2col added
as NEW entry.
- Existing ITEM_PARSERS (2 parsers) : unchanged. compare_row_2col_item added
as NEW entry.
- Existing 6 partials : unchanged.
- Composition planner / production render / Phase R' / AI/Kei : unchanged.
4-class status :
- class 1 readiness : ✅ contract + new builder + new parser + partial +
smoke fixture + R3 artifact aligned.
- class 2 content-fit : watch — cell content single-line; long Korean
sentences may wrap. Row height auto handles wrap; max_rows=12 limit
protects vertical overflow.
- class 3/4 : N/A.
Codex review mandatory per scope-lock §5 (new builder pattern first
introduction : compare_table_2col).
Refs Gitea #4 (IMP-04 Track A frame 4 — V4 UAI tier, NEW builder)
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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"<strong>\1</strong>", 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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 보호
|
||||
|
||||
146
templates/phase_z2/families/bim_dx_comparison_table.html
Normal file
146
templates/phase_z2/families/bim_dx_comparison_table.html
Normal file
@@ -0,0 +1,146 @@
|
||||
<!-- Phase Z-2 MVP-1.5b frame-derived adapted block.
|
||||
§17 룰 — Figma 시각 언어 promote, geometry 만 zone-compatible adapt. -->
|
||||
{#
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
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).
|
||||
#}
|
||||
|
||||
<style>
|
||||
.f18b {
|
||||
width: 100%; height: 100%;
|
||||
display: flex; flex-direction: column;
|
||||
gap: 6px;
|
||||
font-family: 'Noto Sans KR', 'Pretendard', sans-serif;
|
||||
word-break: keep-all;
|
||||
}
|
||||
.f18b__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));
|
||||
}
|
||||
|
||||
/* compare table — 3-column (category / col_a / col_b) */
|
||||
.f18b__table {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 2fr;
|
||||
gap: 0;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
border: 1px solid #A5BBB4;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* header row */
|
||||
.f18b__header {
|
||||
display: contents;
|
||||
}
|
||||
.f18b__header-cell {
|
||||
background: linear-gradient(180deg, rgba(50,31,9,0.85) 0%, rgba(50,31,9,0.95) 100%);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: var(--font-caption);
|
||||
line-height: 1.2;
|
||||
padding: 6px 8px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #A5BBB4;
|
||||
}
|
||||
.f18b__header-cell--category {
|
||||
background: linear-gradient(180deg, rgba(120,90,40,0.85) 0%, rgba(120,90,40,0.95) 100%);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* data rows */
|
||||
.f18b__rows {
|
||||
display: contents;
|
||||
}
|
||||
.f18b__cell {
|
||||
padding: 5px 8px;
|
||||
font-size: var(--font-body);
|
||||
line-height: var(--lh-body);
|
||||
border-bottom: 1px solid rgba(165,187,180,0.4);
|
||||
color: #1a1a1a;
|
||||
overflow: hidden;
|
||||
}
|
||||
.f18b__cell--category {
|
||||
background: rgba(255,250,240,0.7);
|
||||
font-weight: 700;
|
||||
color: #5a4b2e; /* PROMOTED — brown family */
|
||||
text-align: center;
|
||||
}
|
||||
/* zebra striping (visual readability) — odd rows alternate */
|
||||
.f18b__row:nth-child(odd) .f18b__cell:not(.f18b__cell--category) {
|
||||
background: rgba(248,250,252,0.6);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="f18b" data-frame-id="1171281195" data-template-id="bim_dx_comparison_table">
|
||||
<div class="f18b__title">{{ slot_payload.title }}</div>
|
||||
<div class="f18b__table">
|
||||
<div class="f18b__header">
|
||||
<div class="f18b__header-cell f18b__header-cell--category">구분</div>
|
||||
<div class="f18b__header-cell">{{ slot_payload.col_a_label | safe }}</div>
|
||||
<div class="f18b__header-cell">{{ slot_payload.col_b_label | safe }}</div>
|
||||
</div>
|
||||
<div class="f18b__rows">
|
||||
{% for row in slot_payload.rows %}
|
||||
<div class="f18b__row" style="display:contents;">
|
||||
<div class="f18b__cell f18b__cell--category">{{ row.label | safe }}</div>
|
||||
<div class="f18b__cell f18b__cell--col-a">{{ row.col_a | safe }}</div>
|
||||
<div class="f18b__cell f18b__cell--col-b">{{ row.col_b | safe }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user