# -*- coding: utf-8 -*- """구조물 폼의 **`empty_means` select 는 첫 보기를 박지 않는다** (2026-09-13). 2026-08-17 지시로 select 에는 빈 보기를 두지 않았다 — 「첫 보기가 곧 기본값」이기 때문이다. 그런데 2026-09-09 에 셋째 갈래가 생겼다: **비워 두는 것이 뜻인 칸**(`empty_means`). 이 칸까지 첫 보기를 박으니 **엔진 기준값과 다른 값이 조용히 저장**됐다. 실측 사고 — `fill_concrete_mpa` 는 등록부 기본이 없고 「비우면 계산 쪽 기준 강도」인데 폼이 「180」을 박아 저장했다. 엔진 기준은 확정 ⑩ 의 **210** 이라 채움콘크리트 규격·단가가 갈렸다. ⚠ 기본값이 **있는** 칸은 그대로 첫 보기를 쓴다 — 2026-08-17 지시가 그 자리다. """ from __future__ import annotations import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402 PANEL = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel.ts").read_text(encoding="utf-8") #: 2026-09-14 칸 만들기가 `_Structures_Fields.optionControl` 로 옮겨감(브레인 판정 ①② — 고르기 칸은 #: 늘 빈 보기 · 기본값은 제안으로만). FIELDS = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Fields.ts").read_text(encoding="utf-8") API = (ROOT / "B05_Profile" / "B05_Profile_Api_Structures.ts").read_text(encoding="utf-8") COMMIT = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel_Commit.ts").read_text( encoding="utf-8" ) def _registry_options(type_id: str) -> dict[str, dict]: payload = json.loads( (ROOT / "B05_Profile" / "B05_Profile_Structure_Types.json").read_text(encoding="utf-8") ) item = next(entry for entry in payload["types"] if entry["type_id"] == type_id) return {option["key"]: option for option in item["options"]} def test_폼이_empty_means_를_보고_빈_보기를_둔다() -> None: """소스 검사 — 고르기 칸은 **늘** 빈 보기를 두고, 초기값은 저장된 값뿐(첫 보기를 안 박음).""" assert "optionControl(option, values[option.key])" in PANEL assert 'select([["", blank], ...option.choices' in FIELDS # 초기값은 저장된 값만 — 첫 보기·기본값으로 채우지 않음. assert "element.value = value;" in FIELDS assert "option.choices[0]" not in PANEL # 클라이언트 옵션 타입에 칸이 있어야 서버 응답의 `empty_means` 가 살아 온다. assert "empty_means?: string | null;" in API # 빈 칸은 아예 안 실린다 — 이 규칙이 있어야 「안 정함」이 정본에 안 박힌다. assert "if (input.isEmpty()) return;" in COMMIT def test_기본값_있는_칸도_빈_보기에_제안으로만_보인다() -> None: """⭐ 2026-09-14 브레인 판정으로 뒤집힘 — 기본값이 **있어도** 칸에 채우지 않고 빈 보기 이름에 「제안 …」으로만 보임(옛 2026-08-17 「첫 보기가 곧 기본값」 걷음). 저장 한 번에 기본값이 확정으로 굳던 뿌리였음.""" options = _registry_options("masonry_wet") # 버림 콘크리트 — empty_means 가 있고 기본이 「넣음」. assert options["blinding_concrete"]["empty_means"] assert options["blinding_concrete"]["default"] == "넣음" # 채움 강도·야면석 계수 — 기본이 없어 빈 보기가 서야 하는 칸. for key in ("fill_concrete_mpa", "stone_coeff_basis"): assert options[key]["empty_means"], key assert options[key]["default"] is None, key assert options[key]["required"] is False, key assert "`— 안 정함 (제안 ${suggested}) —`" in FIELDS def _wall(**options) -> dict: return { "structure_id": "w", "type_id": "masonry_wet", "name": "돌쌓기(찰)", "start_m": 0.0, "end_m": 10.0, "length_m": 10.0, "height_m": 2.5, "options": { "height_m": 2.5, "length_m": 10.0, "back_len_cm": 45, "stone_kind": "깬돌", "foundation": "기초유", **options, }, } def _fill_row(**options) -> dict: table = build_table([_wall(**options)], {"masonry_wet": "돌쌓기(찰)"}, {}) rows = table["structures"][0]["components"] return next(row for row in rows if row["name"] == "채움콘크리트") def test_칸을_비우면_강도가_확정_210_으로_선다() -> None: """⚠ 폼이 180 을 박던 그 자리 — 비면 **엔진 기준값**이 서고 그 사실이 근거에 남는다.""" row = _fill_row() assert row["spec"] == "210" assert "안 정해 기본값" in row["basis"] def test_고른_값은_그대로_이긴다() -> None: row = _fill_row(fill_concrete_mpa="180") assert row["spec"] == "180" assert "저장 제원에서 고른 값" in row["basis"]