IMP-04 F17 schema correction — paired_rows_4x2 + pill alternation + source-faithful theme

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 15:13:46 +09:00
parent 5c27c492ba
commit 73a98b8ad1
4 changed files with 246 additions and 122 deletions

View File

@@ -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,
}