From f7a9240fe571d517a43d0f709c8fe293fd069406 Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Wed, 13 May 2026 12:21:02 +0900 Subject: [PATCH] =?UTF-8?q?fix(IMP-04):=20F18=20F1=20follow-ups=20?= =?UTF-8?q?=E2=80=94=20defaults=20+=20narrow=20alias=20+=20cardinality=20c?= =?UTF-8?q?larify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same-frame F1 follow-up per Codex round 43 (#15527). matrix §4.1 Fix 7 4-class F1 path (no Track A pause, small fixes + Codex re-review). Three fixes : 1. F1-a — explicit col_a/col_b label defaults - Previous (c7b0f5b) used empty defaults `col_a_label_default: ""` / `col_b_label_default: ""`, so an upstream MDX path without explicit column headers would render blank header cells. - Fix : set `col_a_label_default: "BIM"` and `col_b_label_default: "DX"` in F18 contract. Frame intent is the BIM-vs-DX comparison, so the headers are semantic and must not silently become blank. 2. F1-b — narrow prefix-stripping aliases (parser → builder option) - Previous parser used a broad regex `^[A-Za-z가-힣]{1,8}\s*:\s*(.+)$` to strip any short prefix before `:`. That could accidentally remove meaningful Korean/English prefixes from real cell content. - Fix : remove auto-stripping from `parse_compare_row_2col_item`. Stripping is now configurable via builder option `strip_col_prefix_aliases: []` and applied by `_build_compare_table_2col`. F18 contract uses `["BIM", "DX"]`, so only `BIM:` / `DX:` (with optional fullwidth `:`) prefixes are stripped; other Korean/English colons in real content stay intact. - Parser signature unchanged. Builder is the single place that owns the stripping policy. 3. F1-c — cardinality semantic clarification - Previous top-level `cardinality.strict: 2` was ambiguous: it could be read as `row strict: 2`. Rows are actually `1..12` via `sub_zones.rows.cardinality`. - Fix : add YAML comment that the top-level strict 2 = column count (col_a / col_b), not row count. Per-sub_zone cardinality remains authoritative for rows. Verification : - python -m py_compile src/phase_z2_mapper.py : PASS - python scripts/smoke_frame_render.py --self-check : PASS 7/7 (F18 fixture rendered unchanged at 4211 chars; smoke harness only loaded the partial, builder/parser logic not directly exercised in smoke) - Manual builder/parser invocation test with synthetic units : - col_a_label / col_b_label resolve to "BIM" / "DX" defaults. - `BIM: Only 3D` → `Only 3D` (alias-stripped). - `DX: BIM << DX (ENG. 포함)` → `BIM << DX (ENG. 포함)` (alias-stripped). - `분야별 단절` → `분야별 단절` (no BIM/DX prefix, untouched). - This matches the F1-b narrow-alias intent. - python scripts/smoke_frame_render.py bim_dx_comparison_table --render-to data/runs/imp04_f18_visual_r2 : PASS, R3 artifact written with same character count, generic viewer title. scope-lock honored : no V4 logic, no new builder/parser added (only behavior refinement of existing F18 builder/parser/contract), no other partial, no Phase Z production render, no Phase R'/AI/Kei changes. 4-class status (F18 post-F1) : - class 1 readiness : adapter cleanup complete — defaults explicit, aliases narrow, cardinality semantically clear. - class 2 content-fit : still a watch item (long Korean wrapping, 6+ rows). max_rows=12 protection unchanged. - class 3 / 4 : N/A. Refs Gitea #4 (IMP-04 Track A frame 4 — F1 follow-up per Codex round 43) --- src/phase_z2_mapper.py | 45 +++++++++++++------ .../phase_z2/catalog/frame_contracts.yaml | 11 +++-- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/phase_z2_mapper.py b/src/phase_z2_mapper.py index 17c0e57..69cffe0 100644 --- a/src/phase_z2_mapper.py +++ b/src/phase_z2_mapper.py @@ -237,25 +237,21 @@ def parse_quadrant_item(unit: tuple[str, list[str]]) -> dict: 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. + Pattern : top bullet = **카테고리**, nested = first 2 bullets. + *Parser 는 prefix stripping 안 함* (Codex round 43 §F1-b — narrow alias 정정). + Prefix stripping 은 *builder 의 strip_col_prefix_aliases option* 으로 위임. 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 bullets — strip bullet marker, take first 2 (no prefix stripping) 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"\1", txt) nested.append(txt) col_a = nested[0] if len(nested) > 0 else "" @@ -525,10 +521,13 @@ def _build_compare_table_2col(section, units, contract) -> dict: 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 + item_parser : ITEM_PARSERS key (예: `compare_row_2col_item`) + col_a_label_default : col_a header (MDX 미명시 시 fallback. F1-a fix) + col_b_label_default : col_b header (MDX 미명시 시 fallback) + strip_col_prefix_aliases : list[str] — col_a/col_b 값의 prefix `:` + 를 strip (Codex round 43 §F1-b — narrow alias). + 예 : ["BIM", "DX"]. default [] (no stripping). + max_rows : N (default 999 — practical 한계). """ options = contract["payload"]["builder_options"] parser_name = options["item_parser"] @@ -541,6 +540,7 @@ def _build_compare_table_2col(section, units, contract) -> dict: col_a_label = options.get("col_a_label_default", "") col_b_label = options.get("col_b_label_default", "") + strip_aliases = options.get("strip_col_prefix_aliases", []) or [] max_rows = options.get("max_rows", 999) payload: dict = {} @@ -548,8 +548,27 @@ def _build_compare_table_2col(section, units, contract) -> dict: payload["col_a_label"] = col_a_label payload["col_b_label"] = col_b_label + # Compile precise prefix patterns per alias (Codex round 43 §F1-b narrow). + strip_patterns = [ + re.compile(rf"^{re.escape(a)}\s*[::]\s*(.+)$") + for a in strip_aliases + ] + + def _strip_alias(value: str) -> str: + for pat in strip_patterns: + m = pat.match(value) + if m: + return m.group(1).strip() + return value + visible = list(units[:max_rows]) - rows = [parser(u) for u in visible] + rows = [] + for u in visible: + row = parser(u) + if strip_patterns: + row["col_a"] = _strip_alias(row.get("col_a", "")) + row["col_b"] = _strip_alias(row.get("col_b", "")) + rows.append(row) payload["rows"] = rows if len(units) > max_rows: diff --git a/templates/phase_z2/catalog/frame_contracts.yaml b/templates/phase_z2/catalog/frame_contracts.yaml index 87e82c6..cdd7711 100644 --- a/templates/phase_z2/catalog/frame_contracts.yaml +++ b/templates/phase_z2/catalog/frame_contracts.yaml @@ -398,8 +398,10 @@ bim_dx_comparison_table: family: table source_shape: top_bullets + # NOTE (Codex round 43 §F1-c) : top-level `cardinality.strict: 2` = *column 수* + # (col_a / col_b). data row 수 는 별 — `sub_zones.rows.cardinality` 의 `{min:1, max:12}`. cardinality: - strict: 2 # 2 columns (BIM vs DX 등) + strict: 2 # 2 columns (col_a / col_b) — NOT row count overflow_policy: abort_or_review role_order: @@ -443,6 +445,9 @@ bim_dx_comparison_table: 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: "" + col_a_label_default: "BIM" # F1-a (Codex round 43) — explicit default + col_b_label_default: "DX" # F1-a — explicit default + strip_col_prefix_aliases: # F1-b (Codex round 43) — narrow alias 만 strip + - "BIM" + - "DX" max_rows: 12 # typical 4-8, overflow 보호