구조물 폼 select 는 「기본값 없는 필수」만 빈 보기를 두어, `empty_means` 가 적힌 칸도 첫 보기가 골라진 채 저장됐다. `fill_concrete_mpa` 가 180 으로 박혀 엔진 기준값 210(확정 ⑩)과 갈렸다 — 채움콘크리트 규격·단가가 조용히 달라지는 자리. 빈 보기 판정을 「필수 + empty_means」 한 줄(`keepEmpty`)로 합치고 초기값도 같은 판정을 씀. 기본값이 있는 칸은 그대로 첫 보기를 쓴다(2026-08-17 지시 유지). 클라이언트 옵션 타입에 empty_means 를 더함 — 서버는 이미 실어 보내고 있었다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fe1QWPTfw11PaKh2LjwXdR
105 lines
4.6 KiB
Python
105 lines
4.6 KiB
Python
# -*- 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")
|
|
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:
|
|
"""소스 검사 — 빈 보기와 초기값이 **같은 판정**(`keepEmpty`)을 쓴다."""
|
|
assert "option.empty_means" in PANEL
|
|
assert "const keepEmpty = mustPick || meansEmpty;" in PANEL
|
|
assert 'if (keepEmpty) choices.unshift(["", mustPick ? "— 선택 —" : "— 안 정함 —"]);' in PANEL
|
|
# 빈 보기만 넣고 초기값을 첫 보기로 두면 화면은 「안 정함」인데 값은 첫 보기가 된다.
|
|
assert 'String(preset || (keepEmpty ? "" : (option.choices[0] ?? "")))' in PANEL
|
|
# 클라이언트 옵션 타입에 칸이 있어야 서버 응답의 `empty_means` 가 살아 온다.
|
|
assert "empty_means?: string | null;" in API
|
|
# 빈 칸은 아예 안 실린다 — 이 규칙이 있어야 「안 정함」이 정본에 안 박힌다.
|
|
assert "if (input.isEmpty()) return;" in COMMIT
|
|
|
|
|
|
def test_기본값_있는_칸은_빈_보기를_안_둔다() -> None:
|
|
"""`empty_means` 가 적혀 있어도 **기본값이 있으면** 그 값이 뜬다(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 '(option.default ?? "") === ""' in PANEL # 기본값 있으면 빈 보기를 안 둠
|
|
|
|
|
|
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"]
|