From d352774ce24d76e13cc6fb79f1481abc8f25b037 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 14:01:20 +0900 Subject: [PATCH] =?UTF-8?q?feat(b05):=20=EC=9B=90=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=91=9C=EA=B0=80=20=EC=95=84=EC=A7=81=20=EC=95=88=20=EC=9D=BD?= =?UTF-8?q?=EB=8A=94=20=EC=B9=B8=EC=97=90=20=E3=80=8C=ED=91=9C=EC=97=90=20?= =?UTF-8?q?=EC=95=88=20=EC=93=B0=EC=9E=84=E3=80=8D=20=E2=80=94=20=EA=B3=A8?= =?UTF-8?q?=EB=A7=89=EC=9D=B4=C2=B7=EB=B0=94=EB=8B=A5=EB=A7=89=EC=9D=B4?= =?UTF-8?q?=C2=B7=ED=81=B0=EB=8F=8C=EC=8C=93=EA=B8=B0=C2=B7=ED=9D=99?= =?UTF-8?q?=EB=A7=89=EC=9D=B4=2023=EC=B9=B8=20=C2=B7=20=EA=B0=92=EC=9D=84?= =?UTF-8?q?=20=EB=B0=94=EA=BF=94=20=EB=8F=8C=EB=A0=A4=20=EB=B3=B4=EB=8A=94?= =?UTF-8?q?=20=EC=8B=9C=ED=97=98=EC=9D=B4=20=ED=91=9C=EC=8B=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=A7=80=ED=82=B4(=E2=91=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- B05_Profile/B05_Profile_Api_Structures.ts | 2 + B05_Profile/B05_Profile_Structure_Types.json | 69 +++++++---- B05_Profile/B05_Profile_Structures_Schema.py | 3 + .../B05_Profile_UI_Structures_Fields.ts | 10 +- .../B05_Profile_UI_Structures_Panel.ts | 2 +- .../tester/test_b05_not_in_table_marks.py | 110 ++++++++++++++++++ 6 files changed, 169 insertions(+), 27 deletions(-) create mode 100644 resources/tester/test_b05_not_in_table_marks.py diff --git a/B05_Profile/B05_Profile_Api_Structures.ts b/B05_Profile/B05_Profile_Api_Structures.ts index a98038b2..28951438 100644 --- a/B05_Profile/B05_Profile_Api_Structures.ts +++ b/B05_Profile/B05_Profile_Api_Structures.ts @@ -38,6 +38,8 @@ export interface StructureOptionField { /** 이 값을 넘으면 칸이 경고색 + 툴팁(막지 않음) — 기준값과 까닭 한 줄. */ warn_above?: number | null; warn_message?: string | null; + /** 원단위 표가 아직 안 읽는 칸 — 칸 이름 옆 「표에 안 쓰임」 + 툴팁 사유. */ + not_in_table?: string | null; } /** 구조물 배치 폼을 어느 화면이 쓰는가 — B05 는 유무·종류·위치만, **B06/B07 은 상세 diff --git a/B05_Profile/B05_Profile_Structure_Types.json b/B05_Profile/B05_Profile_Structure_Types.json index ee6b2240..a8a2a37b 100644 --- a/B05_Profile/B05_Profile_Structure_Types.json +++ b/B05_Profile/B05_Profile_Structure_Types.json @@ -1279,7 +1279,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 실무 구조물도 식으로 돎(뒷길이 + 0.30) — 값을 넣으면 그 값이 이김" + "empty_means": "비우면 실무 구조물도 식으로 돎(뒷길이 + 0.30) — 값을 넣으면 그 값이 이김", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "thickness_bottom_m", @@ -1289,7 +1290,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 상부 + 0.30×(H−1) 로 돎 — 값을 넣으면 그 값이 이김" + "empty_means": "비우면 상부 + 0.30×(H−1) 로 돎 — 값을 넣으면 그 값이 이김", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "back_len_cm", @@ -1299,7 +1301,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 돌쌓기 기본값으로 서고 「기본값으로 섰음」이 줄로 뜸 — 돌이 아닌 형태도 있어 필수로 못 검" + "empty_means": "비우면 돌쌓기 기본값으로 서고 「기본값으로 섰음」이 줄로 뜸 — 돌이 아닌 형태도 있어 필수로 못 검", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "stone_kind", @@ -1309,7 +1312,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 야면석 계수 열로 서고 그 사실이 줄로 뜸 — 돌이 아닌 형태도 있어 필수로 못 검" + "empty_means": "비우면 야면석 계수 열로 서고 그 사실이 줄로 뜸 — 돌이 아닌 형태도 있어 필수로 못 검", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "stone_supply", @@ -1319,7 +1323,8 @@ "default": "채집", "required": false, "phase": "detail", - "default_basis": "「채집」 — 별표2 「야면석 등은 가급적 현장에서 채취·사용」 권고. 구조물마다 바꿀 수 있음" + "default_basis": "「채집」 — 별표2 「야면석 등은 가급적 현장에서 채취·사용」 권고. 구조물마다 바꿀 수 있음", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "stone_coeff_basis", @@ -1329,7 +1334,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀" + "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "fill_concrete_mpa", @@ -1339,7 +1345,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 계산 쪽 기준 강도로 돎" + "empty_means": "비우면 계산 쪽 기준 강도로 돎", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "face_slope_ratio", @@ -1348,7 +1355,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 품셈 표준경사로 자동 판정 — 값을 넣으면 그 값이 이김" + "empty_means": "비우면 품셈 표준경사로 자동 판정 — 값을 넣으면 그 값이 이김", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "foundation", @@ -1367,7 +1375,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 국가기준 Ø50 으로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」" + "empty_means": "비우면 국가기준 Ø50 으로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "weep_hole_area_m2", @@ -1377,7 +1386,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 국가기준 2㎡당 1개로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」" + "empty_means": "비우면 국가기준 2㎡당 1개로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」", + "not_in_table": "아직 표에 안 쓰임 — 흙막이는 「떼」만 서고 치수가 정본 평균 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" } ] }, @@ -1454,7 +1464,8 @@ "choices": ["채집", "구입"], "default": "채집", "required": false, - "phase": "detail" + "phase": "detail", + "not_in_table": "아직 표에 안 쓰임 — 큰돌쌓기는 채집석 공제를 아직 안 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "stone_coeff_basis", @@ -1464,7 +1475,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀" + "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀", + "not_in_table": "아직 표에 안 쓰임 — 큰돌쌓기(13-6)는 계수 열을 아직 안 가름 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "fill_concrete_mpa", @@ -1474,7 +1486,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 계산 쪽 기준 강도로 돎" + "empty_means": "비우면 계산 쪽 기준 강도로 돎", + "not_in_table": "아직 표에 안 쓰임 — 큰돌쌓기(13-6)는 채움 콘크리트를 품에 포함 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "face_slope_ratio", @@ -1510,7 +1523,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 국가기준 Ø50 으로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」" + "empty_means": "비우면 국가기준 Ø50 으로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」", + "not_in_table": "아직 표에 안 쓰임 — 큰돌쌓기는 물빼기 관을 아직 안 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "weep_hole_area_m2", @@ -1520,7 +1534,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 국가기준 2㎡당 1개로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」" + "empty_means": "비우면 국가기준 2㎡당 1개로 돎 — 사용자 확정 「국가기준 + 숫자 변경 가능하게」", + "not_in_table": "아직 표에 안 쓰임 — 큰돌쌓기는 물빼기 관을 아직 안 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "blinding_concrete", @@ -1648,7 +1663,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 실무 구조물도 식으로 돎(뒷길이 + 0.30) — 값을 넣으면 그 값이 이김" + "empty_means": "비우면 실무 구조물도 식으로 돎(뒷길이 + 0.30) — 값을 넣으면 그 값이 이김", + "not_in_table": "아직 표에 안 쓰임 — 골막이 두께는 정본 식(ℓ3+0.1H · ℓ3+0.4H)으로만 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "thickness_bottom_m", @@ -1658,7 +1674,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 상부 + 0.30×(H−1) 로 돎 — 값을 넣으면 그 값이 이김" + "empty_means": "비우면 상부 + 0.30×(H−1) 로 돎 — 값을 넣으면 그 값이 이김", + "not_in_table": "아직 표에 안 쓰임 — 골막이 두께는 정본 식(ℓ3+0.1H · ℓ3+0.4H)으로만 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "back_len_cm", @@ -1676,7 +1693,8 @@ "choices": ["채집", "구입"], "default": "채집", "required": false, - "phase": "detail" + "phase": "detail", + "not_in_table": "아직 표에 안 쓰임 — 골막이는 채집석 공제를 아직 안 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "stone_coeff_basis", @@ -1686,7 +1704,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀" + "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀", + "not_in_table": "아직 표에 안 쓰임 — 골막이는 계수 열을 아직 안 가름 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "fill_concrete_mpa", @@ -1797,7 +1816,8 @@ "choices": ["채집", "구입"], "default": "채집", "required": false, - "phase": "detail" + "phase": "detail", + "not_in_table": "아직 표에 안 쓰임 — 바닥막이는 채집석 공제를 아직 안 셈 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "stone_coeff_basis", @@ -1807,7 +1827,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀" + "empty_means": "비우면 품셈 열로 돎 — 실무 관행 열은 골라야 씀", + "not_in_table": "아직 표에 안 쓰임 — 바닥막이 원단위는 돌붙임 정본 표 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "fill_concrete_mpa", @@ -1817,7 +1838,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 계산 쪽 기준 강도로 돎" + "empty_means": "비우면 계산 쪽 기준 강도로 돎", + "not_in_table": "아직 표에 안 쓰임 — 바닥막이 원단위는 돌붙임 정본 표 붙박이 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "face_slope_ratio", @@ -1826,7 +1848,8 @@ "default": null, "required": false, "phase": "detail", - "empty_means": "비우면 품셈 표준경사로 자동 판정 — 값을 넣으면 그 값이 이김" + "empty_means": "비우면 품셈 표준경사로 자동 판정 — 값을 넣으면 그 값이 이김", + "not_in_table": "아직 표에 안 쓰임 — 바닥막이는 평면적(돌붙임 ㎡)이라 기울기 몫이 없음 · 넣어도 수량·금액이 안 바뀜(2026-09-14)" }, { "key": "foundation", diff --git a/B05_Profile/B05_Profile_Structures_Schema.py b/B05_Profile/B05_Profile_Structures_Schema.py index 2cc9e302..7e851bba 100644 --- a/B05_Profile/B05_Profile_Structures_Schema.py +++ b/B05_Profile/B05_Profile_Structures_Schema.py @@ -64,6 +64,9 @@ class StructureOptionField(BaseModel): # 넘는지 보는 기준값과 그 까닭 한 줄(원문 쪽·줄 번호). warn_above: float | None = None warn_message: str | None = None + # 칸은 있는데 **원단위 표가 아직 안 읽는** 칸 — 넣어도 수량·금액이 안 바뀜을 칸 옆에 보임(2026-09-14 ①). + # 참말인지는 `test_b05_not_in_table_marks` 가 값을 바꿔 돌려 보고 지킴. + not_in_table: str | None = None class StructureType(BaseModel): diff --git a/B05_Profile/B05_Profile_UI_Structures_Fields.ts b/B05_Profile/B05_Profile_UI_Structures_Fields.ts index 2dc90b6d..00a0104b 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Fields.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Fields.ts @@ -43,6 +43,8 @@ interface OptionShape { /** 이 값을 넘으면 칸이 경고색 + 툴팁(막지 않음). */ warn_above?: number | null; warn_message?: string | null; + /** 원단위 표가 아직 안 읽는 칸 — 툴팁이 이 사유를 먼저 보임. */ + not_in_table?: string | null; } /** @@ -57,6 +59,8 @@ export function optionControl( ): HTMLInputElement | HTMLSelectElement { const value = stored === undefined || stored === null ? "" : String(stored); const suggested = option.default === null || option.default === "" ? "" : String(option.default); + // 툴팁 — 표가 아직 안 읽는 칸이면 그 사유가 먼저(넣어도 안 바뀜을 알아야 함), 아니면 비울 때의 뜻. + const tip = option.not_in_table ?? option.empty_means ?? ""; if (option.input === "select") { const blank = suggested ? `— 안 정함 (제안 ${suggested}) —` @@ -65,7 +69,7 @@ export function optionControl( : "— 안 정함 —"; const element = select([["", blank], ...option.choices.map((c) => [c, c] as [string, string])]); element.value = value; - if (option.empty_means) element.title = option.empty_means; + if (tip) element.title = tip; return element; } const element = @@ -73,14 +77,14 @@ export function optionControl( if (option.input !== "number") element.type = "text"; element.value = value; element.placeholder = suggested ? `제안 ${suggested}` : option.required ? "필수 입력" : ""; - if (option.empty_means) element.title = option.empty_means; + if (tip) element.title = tip; const limit = option.warn_above; if (typeof limit === "number") { // 기준을 넘으면 알리기만 — 막지 않음(2026-09-14 브레인 판정 ①·㉰). const warn = (): void => { const over = element.value.trim() !== "" && Number(element.value) > limit; element.classList.toggle("is-outside-standard", over); - element.title = over ? (option.warn_message ?? "") : (option.empty_means ?? ""); + element.title = over ? (option.warn_message ?? "") : tip; }; element.addEventListener("input", warn); warn(); diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel.ts b/B05_Profile/B05_Profile_UI_Structures_Panel.ts index 7d10dd28..92f25339 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel.ts @@ -322,7 +322,7 @@ export function createStructuresSection( // 첫 보기를 미리 안 고름(2026-09-14 브레인 판정 ①②, `optionControl` 주석). const input = optionControl(option, values[option.key]); suggestions.push({ input, suggested: String(option.default ?? "") }); - const label = option.unit ? `${option.label} (${option.unit})` : option.label; + const label = `${option.unit ? `${option.label} (${option.unit})` : option.label}${option.not_in_table ? " · 표에 안 쓰임" : ""}`; // `enabled:false` 는 칸을 남기고 잠그기만 한다 — 값은 기본값이 그대로 저장된다 // (고를 수 없는 칸이라 제안이 아니라 프로그램 값). const locked = option.enabled === false; diff --git a/resources/tester/test_b05_not_in_table_marks.py b/resources/tester/test_b05_not_in_table_marks.py new file mode 100644 index 00000000..358c0dd9 --- /dev/null +++ b/resources/tester/test_b05_not_in_table_marks.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +"""「칸은 있는데 표가 안 읽음」 — 등록부 칸 옆에 「아직 표에 안 쓰임」 (2026-09-14 브레인 차례 ①). + +사용자가 값을 넣어도 수량·금액이 아무것도 안 바뀌는 칸. 사유를 표에만 두지 말고 칸 옆에도 보임 +(브레인 판정 ③ 「B」). 표시가 참말인지 **값을 바꿔 돌려 보고** 지킴 — 표가 그 칸을 읽기 시작하면 +여기서 깨지고, 그때 표시를 걷음(표시가 거짓말로 남지 않게). +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402 +from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402 + +#: 종류 → (표가 서는 기준 제원, 안 읽는 칸). 2026-09-14 값을 바꿔 전수 대조한 결과. +#: ⚠ 기초(foundation)·높이는 인계 구조물터파기 심도 구간이 읽어 뺌 · 단 수·올림·이동은 횡단도 칸이라 뺌. +MARKED: dict[str, tuple[dict, list[str]]] = { + "erosion_check": ( + {"height_m": 2.0, "top_length_m": 6.0, "bottom_length_m": 4.0, "form": "돌"}, + ["thickness_top_m", "thickness_bottom_m", "stone_supply", "stone_coeff_basis"], + ), + "bed_sill": ( + {"area_m2": 10.0, "form": "돌붙임(찰)"}, + ["stone_supply", "stone_coeff_basis", "fill_concrete_mpa", "face_slope_ratio"], + ), + "boulder_masonry": ( + {"height_m": 2.5, "length_m": 10.0, "stone_cm": "60~80", "bond": "찰쌓기"}, + [ + "stone_supply", + "stone_coeff_basis", + "fill_concrete_mpa", + "weep_hole_diameter_mm", + "weep_hole_area_m2", + ], + ), + "soil_guard": ( + {"height_m": 1.0, "length_m": 10.0, "form": "떼"}, + [ + "thickness_top_m", + "thickness_bottom_m", + "back_len_cm", + "stone_kind", + "stone_supply", + "stone_coeff_basis", + "fill_concrete_mpa", + "face_slope_ratio", + "weep_hole_diameter_mm", + "weep_hole_area_m2", + ], + ), +} + +FIELDS = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Fields.ts").read_text(encoding="utf-8") +PANEL = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel.ts").read_text(encoding="utf-8") + + +def _snapshot(type_id: str, options: dict) -> str: + structure = { + "structure_id": "p", + "type_id": type_id, + "start_m": 0.0, + "end_m": 10.0, + "chainage_m": 5.0, + "options": options, + } + table = build_table([structure], {}, {5.0: "both_fill"}, None, 0.2) + # 저장 제원을 그대로 되돌려 싣는 칸(`options`)은 뺌 — 값이 **표에 닿았는지**만 봄. + for row in table["structures"]: + row.pop("options", None) + return json.dumps(table, ensure_ascii=False, sort_keys=True, default=str) + + +def _probe_values(option) -> list: + if option.choices: + return list(option.choices) + return [0.73, 1.37] if option.input == "number" else ["x"] + + +@pytest.mark.parametrize("type_id", sorted(MARKED)) +def test_등록부에_표시가_있다(type_id: str) -> None: + options = {option.key: option for option in structure_type_map()[type_id].options} + for key in MARKED[type_id][1]: + note = options[key].not_in_table + assert note and "아직 표에 안 쓰임" in note, f"{type_id}.{key}" + + +@pytest.mark.parametrize("type_id", sorted(MARKED)) +def test_표시한_칸은_정말_안_읽힌다(type_id: str) -> None: + base, keys = MARKED[type_id] + options = {option.key: option for option in structure_type_map()[type_id].options} + reference = _snapshot(type_id, dict(base)) + assert '"components": []' not in reference, "기준 제원으로 표가 안 섬 — 시험이 무의미" + for key in keys: + for value in _probe_values(options[key]): + assert _snapshot(type_id, {**base, key: value}) == reference, ( + f"{type_id}.{key}={value!r} 로 표가 바뀜 — 이제 읽힘, 표시를 걷을 것" + ) + + +def test_폼_칸_이름에_붙는다() -> None: + assert "not_in_table" in FIELDS and "not_in_table" in PANEL