From 892e156efec4bdeb2f64032f8b8ccd64542d576f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 1 Oct 2026 13:28:25 +0900 Subject: [PATCH] =?UTF-8?q?fix(m01-sl):=20=ED=91=9C=20=EB=B8=94=EB=A1=9D?= =?UTF-8?q?=20=EC=B9=B8=20=EC=B0=A8=EB=A1=80=EB=A5=BC=20=EC=A0=95=ED=95=A8?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=E2=80=94=20=EB=B2=94=EC=9C=84=EA=B0=80=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EC=B9=B8=EC=9D=80=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=C2=B7=20=EA=B0=92=20=EB=AA=A8=EB=91=90=20=EB=91=90=20=EC=B9=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 브레인 정함: 서버가 칸 차례 정본 · 조건 칸 → 값 칸 · 범위 조건 · 범위 값(「20~45」)은 「이름·아래」 「이름·위」 - 그 칸에 수 하나면 아래 · 위에 같은 수 · 빈 칸은 빈 칸 · 블록 왼위 = 「표」 바깥 알약 칸 - f8d777628(값 칸 한 칸 · 「80,100」 글)을 되돌림 · 시험 기대값 맞춤 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL --- M01_MasterData/SheetLogic/M01_SL_Router.py | 25 ++++++++++------------ resources/tester/test_m01_sl_router.py | 25 ++++++++++------------ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/M01_MasterData/SheetLogic/M01_SL_Router.py b/M01_MasterData/SheetLogic/M01_SL_Router.py index f9578f586..a47d9f48e 100644 --- a/M01_MasterData/SheetLogic/M01_SL_Router.py +++ b/M01_MasterData/SheetLogic/M01_SL_Router.py @@ -161,9 +161,10 @@ def _logic(key: str, inputs: dict, field: str | None) -> dict: def _table(key: str) -> dict: - """품셈 표 통째 → 머리 줄 + 줄 — `M01_SL_Table_Build.layoutOf · blockCells` 와 같은 열 차례. + """품셈 표 통째 → 머리 줄 + 줄 — 표 블록 칸 차례의 정본(브레인 정함 · 화면도 이것을 따름). - 조건 칸(범위 조건은 「이름·아래」 「이름·위」 두 칸) → 값 칸(늘 한 칸 · 범위 값은 「아래,위」). + 조건 칸 → 값 칸 · 범위가 있는 칸(범위 조건 · 「20~45」 같은 범위 값)은 「이름·아래」 「이름·위」 + 두 칸 · 그 칸에 수 하나면 아래 · 위에 같은 수 · 빈 칸은 빈 칸. """ files = store.loaded()[0] table = next( @@ -178,25 +179,21 @@ def _table(key: str) -> dict: ) if table is None: return {"ok": False, "reason": f"없는 표 「{key}」"} - conds = table.get("조건") or {} + conds, lines = table.get("조건") or {}, table.get("줄") or [] names = [*conds, *(table.get("값칸") or {})] - split = {n for n in conds if conds[n] == "범위"} + split = { + n for n in names if conds.get(n) == "범위" or any(isinstance(r.get(n), list) for r in lines) + } head = [h for n in names for h in ((n + RANGE_LOW, n + RANGE_HIGH) if n in split else (n,))] - - def plain(v: Any) -> Any: # JS String(배열) 과 같게 — 「80,100」 · 빈 끝은 빈 글 - if isinstance(v, list): - return ",".join("" if x is None else str(x) for x in v) - return None if v == "" else v - rows = [] - for r in table.get("줄") or []: + for r in lines: line = [] for n in names: v = r.get(n) - if n in split: # 범위 칸 — 목록이 아니면 둘 다 빈 칸 - line += list(v) if isinstance(v, list) else [None, None] + if n in split: # 범위 칸 — 수 하나면 아래 · 위 같은 수 + line += list(v) if isinstance(v, list) else [v, v] else: - line.append(plain(v)) + line.append(None if v == "" else v if not isinstance(v, dict) else str(v)) rows.append(line) return {"ok": True, "rows": [head, *rows], "name": table.get("이름"), "unit": table.get("기준")} diff --git a/resources/tester/test_m01_sl_router.py b/resources/tester/test_m01_sl_router.py index e524e9f11..9a5e8c8cf 100644 --- a/resources/tester/test_m01_sl_router.py +++ b/resources/tester/test_m01_sl_router.py @@ -158,10 +158,12 @@ def test_저장_열기_목록_지우기_서버가_다시_셈(client: TestClient, assert Decimal(book["계산값"]["s1"]["B1"]["수"]) == 2 * price # 서버 다시 셈(999 는 버림) assert "C1" not in cells # 출처 없는 바깥 알약은 안 넣음 table = book["시트"][1]["칸"] - head = [table[c]["값"] for c in ("A1", "B1", "C1", "D1", "E1")] - assert head == ["돌", "높이·아래", "높이·위", "석공", "증가율"] # 머리 줄 · A1 부터 - assert "B2" not in table and table["C2"]["값"] == 3 and table["E2"]["값"] == "80,100" - assert "B3" not in table and "C3" not in table and table["D3"]["값"] == 0.7 # 범위 아님 → 빈 칸 + head = [table[c]["값"] for c in ("A1", "B1", "C1", "D1", "E1", "F1")] + assert head == ["돌", "높이·아래", "높이·위", "석공", "증가율·아래", "증가율·위"] # A1 부터 + assert ( + "B2" not in table and table["C2"]["값"] == 3 and table["F2"]["값"] == 100 + ) # 빈 끝은 빈 칸 + assert table["B3"]["값"] == table["C3"]["값"] == 4 and "E3" not in table # 수 하나 → 같은 수 assert saved["logic"]["edges"] == _logic(labor)["edges"] answers = {v["id"]: v for v in saved["values"]} assert set(answers) == {"n1!A1", "n2!A1", "n1:노무비"} and all( @@ -174,7 +176,7 @@ def test_저장_열기_목록_지우기_서버가_다시_셈(client: TestClient, second = _post(client, "/api/m01/sl/save", {"logic": at_e1}) assert second["key"] == "SL-0002" moved = second["logic"]["workbook"]["시트"][1]["칸"] - assert moved["E1"]["값"] == "돌" and moved["I2"]["값"] == "80,100" and "A1" not in moved + assert moved["E1"]["값"] == "돌" and moved["J2"]["값"] == 100 and "A1" not in moved renamed = {**saved["logic"], "name": "고친 이름"} assert _post(client, "/api/m01/sl/save", {"logic": renamed})["key"] == "SL-0001" @@ -221,16 +223,11 @@ def test_바깥_값_표_노임_로직_틀린_종류(client: TestClient) -> None: {"id": "z", "kind": "모름"}, ] got = {v["id"]: v for v in _post(client, "/api/m01/sl/outside", {"items": items})["values"]} - assert got["t"]["rows"][0] == [ - "돌", - "높이·아래", - "높이·위", - "석공", - "증가율", - ] # sub3 layoutOf 차례 + # 정함: 조건 → 값 · 범위가 있는 칸(조건이든 값이든)은 두 칸 · 수 하나면 아래 · 위 같은 수 + assert got["t"]["rows"][0] == ["돌", "높이·아래", "높이·위", "석공", "증가율·아래", "증가율·위"] assert got["t"]["rows"][1:] == [ - ["깬돌", None, 3, 0.5, "80,100"], - ["견치돌", None, None, 0.7, None], + ["깬돌", None, 3, 0.5, 80, 100], + ["견치돌", 4, 4, 0.7, None, None], ] assert Decimal(str(got["l"]["value"])) == price and got["l"]["unit"] assert Decimal(str(got["g"]["value"])) == Decimal("0.5") * price