From 290eccbb9213f09bc37f1525eda8e494d17b14dc Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 11:44:01 +0900 Subject: [PATCH] =?UTF-8?q?fix(b05,b08):=20C=EA=B5=B0=20=EB=B2=BD=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=20=EC=97=86=EC=96=B4=EB=8F=84=20=EB=86=93?= =?UTF-8?q?=EC=9E=84=20=E2=80=94=20=EB=86=92=EC=9D=B4=EA=B0=80=20=EB=B0=91?= =?UTF-8?q?=EC=88=98=EC=9D=B8=20=EB=B2=BD=EC=9D=80=20=EC=A4=84=EB=A7=8C=20?= =?UTF-8?q?=EC=84=9C=EA=B3=A0=20=EB=AF=B8=ED=99=95=EC=A0=95(=ED=8C=90?= =?UTF-8?q?=EC=A0=95=20=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_Structure_Types.json | 12 ++-- .../B05_Profile_UI_Structures_Fields.ts | 4 ++ .../B05_Profile_UI_Structures_Panel_Commit.ts | 9 +-- .../B08_Quantity_Engine_UnitQuantity.py | 3 +- .../B08_Quantity_Engine_UnitQuantity_Base.py | 17 +++++ .../test_b08_wall_height_unconfirmed.py | 64 +++++++++++++++++++ 6 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 resources/tester/test_b08_wall_height_unconfirmed.py diff --git a/B05_Profile/B05_Profile_Structure_Types.json b/B05_Profile/B05_Profile_Structure_Types.json index 9d7772ef..38146213 100644 --- a/B05_Profile/B05_Profile_Structure_Types.json +++ b/B05_Profile/B05_Profile_Structure_Types.json @@ -728,7 +728,8 @@ "unit": "m", "default": 2.5, "required": false, - "phase": "b05" + "phase": "b05", + "empty_means": "비워도 놓임 — 줄만 서고 미확정이라 금액에 안 들어감(높이를 적으면 섬 · 2026-09-14 브레인 판정 ①)" }, { "key": "length_m", @@ -814,7 +815,8 @@ "unit": "m", "default": 2.5, "required": false, - "phase": "b05" + "phase": "b05", + "empty_means": "비워도 놓임 — 줄만 서고 미확정이라 금액에 안 들어감(높이를 적으면 섬 · 2026-09-14 브레인 판정 ①)" }, { "key": "length_m", @@ -987,7 +989,8 @@ "unit": "m", "default": 2.5, "required": false, - "phase": "b05" + "phase": "b05", + "empty_means": "비워도 놓임 — 줄만 서고 미확정이라 금액에 안 들어감(높이를 적으면 섬 · 2026-09-14 브레인 판정 ①)" }, { "key": "length_m", @@ -1373,7 +1376,8 @@ "unit": "m", "default": 2.5, "required": false, - "phase": "b05" + "phase": "b05", + "empty_means": "비워도 놓임 — 줄만 서고 미확정이라 금액에 안 들어감(높이를 적으면 섬 · 2026-09-14 브레인 판정 ①)" }, { "key": "length_m", diff --git a/B05_Profile/B05_Profile_UI_Structures_Fields.ts b/B05_Profile/B05_Profile_UI_Structures_Fields.ts index 5e695ecf..cdfcd657 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Fields.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Fields.ts @@ -38,6 +38,8 @@ interface OptionShape { choices: string[]; default: string | number | null; required?: boolean; + /** 비워 두면 어떻게 되나 — 칸에 마우스를 올리면 보임. */ + empty_means?: string | null; } /** @@ -60,6 +62,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; return element; } const element = @@ -67,6 +70,7 @@ 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; return element; } diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel_Commit.ts b/B05_Profile/B05_Profile_UI_Structures_Panel_Commit.ts index 7ef4bc87..516ac307 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel_Commit.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel_Commit.ts @@ -231,13 +231,8 @@ export async function commit(ctx: StructuresCommitContext, live = false): Promis ctx.optionInputs.find((entry) => entry.key === "length_m")?.input.focus(); return; } - // 높이도 비면 안 놓음 — 칸이 비면 횡단도 벽이 조용히 안 서고 수량도 안 섬. 적거나 - // [제안값 넣기]를 누를 것(2026-09-14 브레인 판정 「기본값을 몰래 확정으로 안 바꿈」). - const height = ctx.optionInputs.find((entry) => entry.key === "height_m"); - if (height?.isEmpty()) { - height.input.focus(); - return; - } + // 높이는 비워도 놓음 — 자리부터 잡고 치수를 뒤에 넣는 길을 막지 않음. 높이가 물량 밑수인 + // 벽은 B08 에서 줄만 서고 미확정(금액 밖)으로 뜸(2026-09-14 브레인 판정 ①, `missing_height_reason`). const before = ctx.readBeforeM(); if (anchor - before < -0.005) { const beforeInput = ctx.optionInputs.find((entry) => entry.key === "before_m")?.input; diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py index a7feeb03..69c2fc15 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py @@ -52,6 +52,7 @@ from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Base import ( # noqa: F401 StructureQuantity, _num, is_unconfirmed, + missing_height_reason, ) from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Masonry import ( # noqa: F401 — 다시 내보냄 boulder_masonry, @@ -529,7 +530,7 @@ def build_table( if rubble is not None: quantity.components.append(rubble) _append_section_trench(quantity, item, observed, rubble_base_thickness_m) - quantity.unconfirmed = str(item.get("unconfirmed") or "") + quantity.unconfirmed = str(item.get("unconfirmed") or "") or missing_height_reason(item) quantities.append(quantity) if use_templates: # 늦게 부름 — 양식 모듈이 이 모듈을 부르므로 맨 위에서 부르면 맞물림. diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py index ae11a11b..8b8aabc5 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity_Base.py @@ -145,5 +145,22 @@ def is_unconfirmed(structure: dict[str, Any]) -> bool: return bool(structure.get("unconfirmed")) +#: 높이가 물량 밑수인 벽 — 높이를 안 적고 놓아도 줄은 서되 금액 밖(2026-09-14 브레인 판정 ① +#: 「설계자가 자리부터 잡고 치수를 뒤에 넣는 길을 막지 말 것」). 흙막이(떼 개소당)는 높이를 안 씀. +HEIGHT_DRIVEN_TYPES = frozenset({"retaining_wall", "masonry_wet", "masonry_dry", "boulder_masonry"}) +UNCONFIRMED_NO_HEIGHT = "높이를 안 적고 놓았음 — 높이를 적으면 금액이 섬" + + +def missing_height_reason(structure: dict[str, Any]) -> str: + """높이 없이 놓인 벽이면 미확정 까닭 한 줄, 아니면 빈 글.""" + if structure.get("type_id") not in HEIGHT_DRIVEN_TYPES: + return "" + try: + height = float((structure.get("options") or {}).get("height_m") or 0.0) + except (TypeError, ValueError): + height = 0.0 + return "" if height > 0 else UNCONFIRMED_NO_HEIGHT + + def _num(value: Any, fallback: float = 0.0) -> float: return float(value) if isinstance(value, (int, float)) else fallback diff --git a/resources/tester/test_b08_wall_height_unconfirmed.py b/resources/tester/test_b08_wall_height_unconfirmed.py new file mode 100644 index 00000000..0b375b30 --- /dev/null +++ b/resources/tester/test_b08_wall_height_unconfirmed.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +"""높이 없이 놓인 벽 — **놓기는 되고 줄은 서되 미확정(금액 밖)** (2026-09-14 브레인 판정 ①). + +앞서 B05 폼이 C군 높이가 비면 놓기를 막았음. 우리 규칙 「줄은 서고 금액은 안 섬」과 어긋나 풀고, +높이가 물량 밑수인 벽(옹벽·돌쌓기 찰/메·큰돌쌓기)은 B08 표에서 미확정으로 뜨게 함. +흙막이(떼 개소당)는 높이를 안 써 그대로. +""" + +from __future__ import annotations + +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 +from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Base import ( # noqa: E402 + UNCONFIRMED_NO_HEIGHT, + missing_height_reason, +) + +COMMIT = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel_Commit.ts").read_text( + encoding="utf-8" +) + + +def _wall(type_id: str = "masonry_wet", **options) -> dict: + return { + "structure_id": "w", + "type_id": type_id, + "start_m": 0.0, + "end_m": 10.0, + "options": { + "length_m": 10.0, + "back_len_cm": 45, + "stone_kind": "깬돌", + "foundation": "기초유", + **options, + }, + } + + +def test_높이_없는_돌쌓기는_미확정으로_선다() -> None: + table = build_table([_wall()], {"masonry_wet": "돌쌓기(찰)"}, {}) + row = table["structures"][0] + assert row["unconfirmed"] == UNCONFIRMED_NO_HEIGHT + + +def test_높이를_적으면_미확정이_아니다() -> None: + table = build_table([_wall(height_m=2.0)], {"masonry_wet": "돌쌓기(찰)"}, {}) + assert table["structures"][0]["unconfirmed"] == "" + + +def test_높이를_안_쓰는_종류는_그대로() -> None: + assert missing_height_reason(_wall("soil_guard")) == "" + for type_id in ("retaining_wall", "masonry_dry", "boulder_masonry"): + assert missing_height_reason(_wall(type_id)) == UNCONFIRMED_NO_HEIGHT, type_id + assert missing_height_reason(_wall(type_id, height_m="1.5")) == "", type_id + + +def test_폼이_높이_빈칸으로_놓기를_안_막는다() -> None: + assert "height?.isEmpty()" not in COMMIT