From 2ffb913c36fd121e24f7acb1702a5d8d66b929e7 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 13 Sep 2026 18:02:50 +0900 Subject: [PATCH] =?UTF-8?q?fix(b08):=20=EA=B3=A0=EC=B9=9C=20=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20=EC=9E=A5=20=EC=9D=B4=EB=A6=84=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20=EC=96=91=EC=8B=9D=20+=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EB=AC=B6=EC=9D=8C=20=E2=80=94=20=EC=A0=9C?= =?UTF-8?q?=EC=9B=90=EC=9D=84=20=EA=B3=A0=EC=B3=90=EB=8F=84=20=EC=8B=9D?= =?UTF-8?q?=EC=9D=B4=20=EB=94=B0=EB=9D=BC=EA=B0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 저장 칸 structure_formula_overrides 의 키를 장 이름에서 양식 type_id 로 바꿈(브레인 판정, PLAN 10장) - 구조물도·build_table 모두 type_id 로 찾음 · 같은 양식의 장이 모두 같은 고친 식을 받음 - 화면 안내에 「같은 양식의 장 모두에 걸림」 · 시험: 뒷길이를 고쳐 장 이름이 바뀌어도 사용자 식 남음 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- .../B08_Quantity_Engine_StructureTemplate.py | 15 +++++++------ .../B08_Quantity_Engine_UnitQuantity.py | 2 +- .../B08_Quantity_Router_StructureSheet.py | 5 +++-- .../B08_Quantity_UI_StructureSheet.ts | 4 ++-- .../tester/test_b08_structure_sheet_router.py | 22 ++++++++++++++++++- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py b/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py index fa25553b..7bc01918 100644 --- a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py +++ b/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py @@ -5,8 +5,9 @@ ⚠ 규격(높이·뒷길이·돌종류)마다 항목을 늘리지 않음 — 제원은 `vars` 로 들어가고 같은 식이 수량을 다시 냄(명세 16장 「한 조합 + 규격별 수량표」). -⚠ 사용자가 고친 식(PLAN 3장 ⑤)은 **프로젝트 장 단위** — 산출 조건 `quantity` 구획의 - `structure_formula_overrides` = `{장 이름: {차례: {"formula": 식}}}`. 고친 줄은 출처 `user`. +⚠ 사용자가 고친 식(PLAN 3장 ⑤)은 **양식 + 프로젝트 단위** — 산출 조건 `quantity` 구획의 + `structure_formula_overrides` = `{양식 type_id: {차례: {"formula": 식}}}`. 고친 줄은 출처 `user`. + 장 이름(제원 조합)에 묶지 않음 — 높이를 고치면 식이 사라짐. 제원별로 달리 쓰려면 `when` 칸. """ from __future__ import annotations @@ -155,10 +156,10 @@ def replace_with_templates( ⚠ 전개가 성분을 하나도 못 낸 구조물(표에 없는 뒷길이 등)은 그대로 둠 — 전개 사유가 이미 드러냄. ⚠ Node 가 안 돌면 전개 값을 두고 **사유를 남김** — 조용히 섞이지 않게. ⚠ 오류 난 양식 줄은 성분에서 빼고 사유로 — 전개도 원문 「-」 줄을 안 세우고 사유로 둠. - ⚠ 사용자가 고친 식은 **그 구조물이 속한 장 이름**으로 찾음 — 구조물도와 같은 값이 되게. + ⚠ 사용자가 고친 식은 **양식(type_id)** 으로 찾음 — 구조물도와 같은 값이 되게. """ from B08_Quantity.B08_Quantity_Engine_Formula import evaluate_sheets - from B08_Quantity.B08_Quantity_Engine_StructureSheet import sheet_key, slope_of + from B08_Quantity.B08_Quantity_Engine_StructureSheet import slope_of from B08_Quantity.B08_Quantity_Engine_UnitQuantity import Component, _section_mode_at from common_util.common_util_structure_face_role import structure_face_role @@ -185,7 +186,7 @@ def replace_with_templates( "options": options, } values = template_vars(template, structure, slope_of(sheet)[0], settings) - overrides = (formula_overrides or {}).get(sheet_key(sheet)) + overrides = (formula_overrides or {}).get(quantity.type_id) targets.append((quantity, template_sheet(template, values, overrides))) if not targets: return @@ -261,7 +262,7 @@ def apply_templates(payload: dict[str, Any], settings: dict[str, Any] | None = N "options": sheet.get("options"), } values = template_vars(template, structure, slope_of(sheet)[0], settings) - body = template_sheet(template, values, all_overrides.get(sheet.get("key"))) + body = template_sheet(template, values, all_overrides.get(sheet.get("type_id"))) targets.append((sheet, body)) if not targets: @@ -290,7 +291,7 @@ def save_sheet_overrides( template: dict[str, Any], edits: list[dict[str, Any]], ) -> tuple[dict[str, Any], int]: - """장 하나의 고친 식을 갈아 끼운 **새 전체 값**과 바뀐 줄 수. + """양식 하나(`key` = type_id)의 고친 식을 갈아 끼운 **새 전체 값**과 바뀐 줄 수. ⚠ 빈 식·양식 식과 같은 식은 **지움**(「고친 적 없음」으로 되돌림) — 같은 값을 박아 두면 양식이 바뀌어도 그 장만 옛 식으로 남음. diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py index 49924320..12ddea71 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py @@ -1517,7 +1517,7 @@ def build_table( ⭐ 2026-09-13(PLAN 3장 ④-2) — **양식이 있는 종류는 양식 풀이 값으로 성분을 갈음** (`B08_Quantity_Engine_StructureTemplate.replace_with_templates`). 원단위·자재총괄·인계가 구조물도와 같은 값을 보게 함. `use_templates=False` 는 대조 시험이 **전개만** 볼 때 씀. - ⭐ `structure_formulas` — 사용자가 장마다 고친 식(산출 조건 `structure_formula_overrides`, + ⭐ `structure_formulas` — 사용자가 양식마다 고친 식(산출 조건 `structure_formula_overrides`, PLAN 3장 ⑤). 부르는 쪽이 산출 조건에서 넘김 — 안 넘기면 구조물도와 값이 갈림. """ observed = load_observed_table() diff --git a/B08_Quantity/B08_Quantity_Router_StructureSheet.py b/B08_Quantity/B08_Quantity_Router_StructureSheet.py index f6ac31c9..6591bf85 100644 --- a/B08_Quantity/B08_Quantity_Router_StructureSheet.py +++ b/B08_Quantity/B08_Quantity_Router_StructureSheet.py @@ -123,7 +123,7 @@ class FormulaEdit(BaseModel): class StructureSheetFormulaRequest(BaseModel): - """구조물도 장 하나에서 고친 식들(PLAN 3장 ⑤ · 프로젝트 장 단위).""" + """구조물도 장 하나에서 고친 식들(PLAN 3장 ⑤) — 저장은 그 장의 **양식 + 프로젝트** 단위.""" model_config = ConfigDict(extra="forbid") @@ -161,8 +161,9 @@ async def put_structure_sheet_formulas( ) try: current = quantity_settings(project_root).get(OVERRIDES_KEY) or {} + # 장 이름이 아니라 **양식**에 묶음 — 제원을 고쳐도 식이 따라가고 같은 양식의 장이 모두 받음. merged, changed = save_sheet_overrides( - current, payload.sheet_key, template, [row.model_dump() for row in payload.rows] + current, str(picked["type_id"]), template, [row.model_dump() for row in payload.rows] ) except ValueError as exc: return JSONResponse(status_code=400, content={"status": "error", "message": str(exc)}) diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet.ts b/B08_Quantity/B08_Quantity_UI_StructureSheet.ts index 46abb19d..ac9da798 100644 --- a/B08_Quantity/B08_Quantity_UI_StructureSheet.ts +++ b/B08_Quantity/B08_Quantity_UI_StructureSheet.ts @@ -8,7 +8,7 @@ * ⚠ 상단 그림·하단 일위대가는 뒤 일감 — 지금은 가운데 원단위 수량표만. * ⚠ 제원 저장은 칸 옆 [제원 저장] 한 번에 정본(`structures.json`)으로 감 — 옛 B07 폼 규약 그대로. * ⚠ 식 고치기(PLAN 3장 ⑤) — 칸에서 고치면 **이 화면이 같은 풀이기로 즉시** 다시 풂(왕복 없음). - * [식 저장] 때는 식만 보내고 **값은 서버가 Node 로 다시 냄**(판정 Ⓐ). 저장은 프로젝트 장 단위. + * [식 저장] 때는 식만 보내고 **값은 서버가 Node 로 다시 냄**(판정 Ⓐ). 저장은 양식 + 프로젝트 단위. * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; @@ -273,7 +273,7 @@ function formulaTable( saveButton.disabled = !dirty; discardButton.disabled = !dirty; status.textContent = dirty - ? "저장 안 한 식 있음 — 값은 이 화면 계산(저장하면 서버가 다시 셈)" + ? "저장 안 한 식 있음 — 값은 이 화면 계산(저장하면 서버가 다시 셈 · 같은 양식의 장 모두에 걸림)" : ""; onDirty(dirty); }; diff --git a/resources/tester/test_b08_structure_sheet_router.py b/resources/tester/test_b08_structure_sheet_router.py index b361217c..a82ab102 100644 --- a/resources/tester/test_b08_structure_sheet_router.py +++ b/resources/tester/test_b08_structure_sheet_router.py @@ -132,7 +132,8 @@ def test_식을_고쳐_저장하면_서버가_다시_풀고_되돌릴_수_있다 assert after["모르터"]["default_formula"] == 원래["formula"] assert after["돌쌓기"]["source"] == "library" # 안 고친 줄은 그대로 양식 stored = json.loads((project / "project_settings.json").read_text(encoding="utf-8")) - assert stored["quantity"]["structure_formula_overrides"][sheet["key"]] == { + # 장 이름이 아니라 양식(type_id)에 묶임 — 브레인 판정(PLAN 3장 ⑤ ⓒ). + assert stored["quantity"]["structure_formula_overrides"]["masonry_wet"] == { str(원래["no"]): {"formula": "A*0.018"} } @@ -166,6 +167,25 @@ def test_식을_고쳐_저장하면_서버가_다시_풀고_되돌릴_수_있다 assert stored["quantity"]["structure_formula_overrides"] == {} +def test_제원을_고쳐도_고친_식이_따라간다(client: TestClient) -> None: + """고친 식은 양식 + 프로젝트에 묶임 — 뒷길이를 고쳐 장 이름이 바뀌어도 사용자 식이 남음.""" + sheet = _sheet(client) + seq = next(row["no"] for row in sheet["rows"] if row["name"] == "모르터") + client.put( + f"{SHEETS}/formulas", + json={"sheet_key": sheet["key"], "rows": [{"seq": seq, "formula": "A*0.018"}]}, + ) + spec = client.put( + f"{SHEETS}/spec", + json={"sheet_key": sheet["key"], "base_revision": 1, "back_len_cm": "55"}, + ) + assert spec.status_code == 200, spec.text + after = _sheet(client) + assert after["key"] != sheet["key"] + mortar = next(row for row in after["rows"] if row["name"] == "모르터") + assert mortar["source"] == "user" and mortar["formula"] == "A*0.018" + + def test_틀린_식도_막지_않고_저장하되_오류를_돌려준다(client: TestClient) -> None: sheet = _sheet(client) seq = next(row["no"] for row in sheet["rows"] if row["name"] == "모르터")