From 73a98b8ad19aa897422bb2aea38833b327db2493 Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Wed, 13 May 2026 15:13:46 +0900 Subject: [PATCH] =?UTF-8?q?IMP-04=20F17=20schema=20correction=20=E2=80=94?= =?UTF-8?q?=20paired=5Frows=5F4x2=20+=20pill=20alternation=20+=20source-fa?= =?UTF-8?q?ithful=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit source = 8 atomic issues (4 paired rows × 2 cells per texts.md), 이전 strict-4 가 source 의 절반 누락. round 55~73 review-loop 의 calibration frame. - contract : source_shape=top_bullets / layout_variant=paired_rows_4x2_alternating_pills / strict 8 (no pad/truncate) / role_order row_{1..4}_{left,right} / visual_hints pill_positions + row_gap_after / builder paired_rows_4x2_slots - builder : new _build_paired_rows_4x2_slots — 2-axis (row × side) deterministic index mapping, strict 8 raises before render, quadrant_item parser 재사용 - partial : 4-row × 2-cell flex, pill alternation (row 1/3 top, row 2/4 bottom via column-reverse), row 2-3 visual gap, source-faithful color (rgb(204,82,0) →rgb(136,55,0) title + #60A451 row border + rgba(250,237,203,0.15) bg + #0c271e body + 2px dashed #60A451 cell 분할선), pill = CSS approximation (asset crop variant single-pass 비용 高 → fallback per Codex round 62/68 scope cap, pill shape + alternation + green/cream/brown theme 보존), no row headers (source 부재, inference 금지) - fixture : flat 8 top-bullet (texts.md 8 issues 그대로) - smoke + R3 : PASS (11/11 self-check, 5535 chars partial, 8 units rendered, pill alternation 정합, row 2-3 gap, no invented row headers) Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke_frame_render.py | 30 ++- src/phase_z2_mapper.py | 61 ++++++ .../phase_z2/catalog/frame_contracts.yaml | 93 +++++---- .../families/bim_current_problems_paired.html | 184 ++++++++++-------- 4 files changed, 246 insertions(+), 122 deletions(-) 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 의 `
` (행 2-3 사이 시각 단락) per Codex round 58 §3. visual_hints: - min_height_px: 350 + min_height_px: 380 + pill_positions: [top, bottom, top, bottom] + row_gap_after: [2] accepted_content_types: - text_block sub_zones: - - id: issue_1 + - id: row_1_left role: main_text accepts: [text_block] cardinality: { strict: 1 } - partial_target_path: ".f17b__grid > .f17b__cell:nth-child(1)" - - id: issue_2 + partial_target_path: ".f17b__row:nth-child(1) > .f17b__cell--left" + - id: row_1_right role: main_text accepts: [text_block] cardinality: { strict: 1 } - partial_target_path: ".f17b__grid > .f17b__cell:nth-child(2)" - - id: issue_3 + partial_target_path: ".f17b__row:nth-child(1) > .f17b__cell--right" + - id: row_2_left role: main_text accepts: [text_block] cardinality: { strict: 1 } - partial_target_path: ".f17b__grid > .f17b__cell:nth-child(3)" - - id: issue_4 + partial_target_path: ".f17b__row:nth-child(2) > .f17b__cell--left" + - id: row_2_right role: main_text accepts: [text_block] cardinality: { strict: 1 } - partial_target_path: ".f17b__grid > .f17b__cell:nth-child(4)" + partial_target_path: ".f17b__row:nth-child(2) > .f17b__cell--right" + - id: row_3_left + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f17b__row:nth-child(3) > .f17b__cell--left" + - id: row_3_right + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f17b__row:nth-child(3) > .f17b__cell--right" + - id: row_4_left + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f17b__row:nth-child(4) > .f17b__cell--left" + - id: row_4_right + role: main_text + accepts: [text_block] + cardinality: { strict: 1 } + partial_target_path: ".f17b__row:nth-child(4) > .f17b__cell--right" payload: title: source: section.title - # Builder 재사용 = `quadrant_flat_slots` (F16 pattern, pad_to=4). - # F16 = TL/TR/BL/BR quadrant. F17 = paired-rows-2x2 (top-left/top-right/bottom-left/bottom-right). - # 차이 = key prefix only ("quadrant_N" vs "issue_N"). partial layout 동일 2x2 grid. - builder: quadrant_flat_slots + # 신규 builder — quadrant_flat_slots 와 차이 : 1-axis (TL/TR/BL/BR) → 2-axis (row × side). + # strict 8 + no pad/truncate (silent loss 차단, Codex round 60 §3). + # parser = quadrant_item 재사용 (label + body heading-less, Codex round 60). + builder: paired_rows_4x2_slots builder_options: - item_parser: quadrant_item # 재사용 - pad_to: 4 - truncate_at: 4 - label_key_pattern: "issue_{n}_label" - body_key_pattern: "issue_{n}_body" - empty_label: "" - empty_body: [] + item_parser: quadrant_item + label_key_pattern: "row_{r}_{side}_label" + body_key_pattern: "row_{r}_{side}_body" + rows: 4 + sides: [left, right] diff --git a/templates/phase_z2/families/bim_current_problems_paired.html b/templates/phase_z2/families/bim_current_problems_paired.html index 023b278..056792f 100644 --- a/templates/phase_z2/families/bim_current_problems_paired.html +++ b/templates/phase_z2/families/bim_current_problems_paired.html @@ -1,80 +1,124 @@ - + {# ───────────────────────────────────────────────────────────────────────────── -Frame 17 = "현황 및 문제점" (cards/paired-rows-2x2). 4 problem cards in 2x2 grid. -Track A frame 8 (V4 RS=1 — restructure tier, V4 catalog-completeness 정합). -Builder 재사용 = `quadrant_flat_slots` (F16 pattern, pad_to=4) + `issue_{n}_*` keys. +Frame 17 = "현황 및 문제점" (cards/paired-rows-4x2-alternating-pills). +**source = 8 atomic issues** (4 paired rows × 2 issue cells per row). +이전 strict-4 → source 의 절반 누락. round 55~73 review-loop 의 schema-correction. -PROMOTED CSS : per-issue warning theme (4 distinct color tints), title gradient. -NOT PROMOTED : Figma source decoration / banner / texture / numbered badges — figma_to_html 보존. -ADAPTED : Figma absolute → 2x2 CSS grid + token-fixed typography. +Visual provenance — figma_to_html_agent/blocks/1171281194/{index.html, texts.md, assets}. +- title gradient : rgb(204,82,0) → rgb(136,55,0) (source line 50) +- row border : 3px solid #60A451 (source line 69) +- row bg : rgba(250,237,203,0.15) (source line 68) +- body text : #0c271e (source line 130) +- 분할선 (cell 사이) : 2px dashed #60A451 (source line 135) +- pill alternation : row 1/3 = pill 상단, row 2/4 = pill 하단 (source line 161~225) +- row 2-3 visual gap : source `
` (height 65px) line 199-200 -slots : title, issue_1/2/3/4_label, issue_1/2/3/4_body. +PROMOTED CSS : source-faithful unified green/cream/brown theme + pill shape + +top/bottom/top/bottom pill alternation + row 2-3 visual gap. + +NOT PROMOTED (scope cap per Codex round 64/72 — CSS fallback 정당) : +- pill asset crop variant (`.crop-left` left -45.3% width 145.3% / `.crop-right` width 151.25%) — + source 의 PNG asset 정확 crop reproduction 은 single-pass scope 외 (Codex round 62 §6 + + round 68 scope cap). CSS approximation = pill shape + alternation + theme 모두 보존. +- bottom pill flip image transform (source line 117) — 단순 column-reverse 로 처리. + +ADAPTED : Figma absolute → 4-row flex layout + token-fixed typography. + +slots (17) : title + + row_{1..4}_{left,right}_{label,body} (8 × 2 = 16). + +no row headers (source 부재, infer 금지 per Codex round 58 §1 + round 70 §3.6). #}
{{ slot_payload.title }}
-
-
-
{{ slot_payload.issue_1_label | safe }}
-
- {% if slot_payload.issue_1_body %} - {% for line in slot_payload.issue_1_body %}
{{ line.text | safe }}
{% endfor %} - {% endif %} -
-
-
-
{{ slot_payload.issue_2_label | safe }}
-
- {% if slot_payload.issue_2_body %} - {% for line in slot_payload.issue_2_body %}
{{ line.text | safe }}
{% endfor %} - {% endif %} -
-
-
-
{{ slot_payload.issue_3_label | safe }}
-
- {% if slot_payload.issue_3_body %} - {% for line in slot_payload.issue_3_body %}
{{ line.text | safe }}
{% endfor %} - {% endif %} -
-
-
-
{{ slot_payload.issue_4_label | safe }}
-
- {% if slot_payload.issue_4_body %} - {% for line in slot_payload.issue_4_body %}
{{ line.text | safe }}
{% endfor %} - {% endif %} +
+ {% for r in [1, 2, 3, 4] %} +
+
+
{{ slot_payload['row_' ~ r ~ '_left_label'] | safe }}
+
+ {% set body_left = slot_payload['row_' ~ r ~ '_left_body'] %} + {% if body_left %} + {% for line in body_left %}
{{ line.text | safe }}
{% endfor %} + {% endif %} +
+
+
+
{{ slot_payload['row_' ~ r ~ '_right_label'] | safe }}
+
+ {% set body_right = slot_payload['row_' ~ r ~ '_right_body'] %} + {% if body_right %} + {% for line in body_right %}
{{ line.text | safe }}
{% endfor %} + {% endif %} +
+ {% endfor %}