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:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user