From 607e7c0cd88599603e910d08622accb1a66bda16 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 15 Sep 2026 05:33:34 +0900 Subject: [PATCH] =?UTF-8?q?fix(b09):=20=EB=AC=B6=EC=9D=8C=20=EC=A4=84=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=9D=B4=20=EC=A4=84=20=EC=88=98=EB=9F=89?= =?UTF-8?q?=EB=A7=8C=ED=81=BC=20=EB=B6=80=ED=92=80=EB=8D=98=20=EA=B2=83=20?= =?UTF-8?q?=E2=80=94=20=EC=A1=B0=EA=B0=81=20=EC=88=98=EB=9F=89=EC=9D=80=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=20=ED=95=A9=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 까닭 — B08 인계의 조각 수량은 「합계」인데(그쪽 시험이 지킴: BOX 동바리 3.92×10 · 포장 97.66㎥) B09 가 Σ(조각 단가 × 조각 수량) 을 1단위 단가로 보고 줄 수량을 또 곱했음. 평떼 718.14㎡ 가 720배(42억)로 섰음(2026-09-15 랩탑 서브 실측). 고침 — 계약은 그대로 두고 읽는 쪽을 고침. 합계를 그대로 금액으로 쓰고 단가는 나눠서 보임. 재·노·경 성분 셋도 같이. 계약을 코드 가까이 못박음 — 두 자리(composite_parts 칸 · _composite_row). ⭐ 시험도 고침 — 종전이 줄 수량 1 로만 재서 못 잡았음. _line(parts, quantity) 로 열고 수량 2·10·718.14 로 잼 · 성분 셋도 따로. 빨강 4 확인한 뒤 고쳤음. ⚠ 지금 검증 프로젝트엔 값이 선 묶음 줄이 0 이라 금액 변화는 안 드러남 — BOX·포장·집수정·녹화가 서면 그때 제 값으로 섬. 전체 시험 2136 통과 · 28 건너뜀 · 1 xfail · 1 실패는 내 변경과 무관한 노임 신뢰도 시험(아래 보고). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA --- .../B09_Estimation_BillOfQuantities_Input.py | 5 +++ .../B09_Estimation_BillOfQuantities_Rows.py | 22 +++++++---- .../test_b09_composite_partial_parts.py | 39 +++++++++++++++++-- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py index 8731689e..1b2ae559 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Input.py @@ -55,6 +55,11 @@ class HandoffWorkItem: application_ratio_breakdown: dict | None = None quantity_breakdown: dict | None = None #: 묶음 줄(옹벽처럼 품셈에 그 공종이 없는 것) — 무엇으로 이루어지는지. + #: ⭐ **조각 수량은 「합계」다 — 1단위당이 아니다.** B08 과의 계약이고 그쪽 시험이 지킨다 + #: (BOX 동바리 3.92 × 연장 10m · 포장 97.66㎥). 그러므로 `Σ(조각 단가 × 조각 수량)` 이 + #: **그 줄 전체 금액**이며 **줄 수량을 다시 곱하면 안 된다**. + #: ⚠ 이 줄이 안 적혀 있어서 금액이 배로 부풀었다 — 평떼 718.14㎡ 가 720배(42억). + #: 2026-09-15 랩탑 서브 실측 · 고친 자리는 `_Rows._composite_row`. composite_parts: tuple = () #: 묶음인데 아직 못 채운 조각 — 「단가 없음」과 「물량 없음」을 갈라 적는다. composite_not_ready: tuple = () diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py index 318a731a..dee8ee3e 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py @@ -171,15 +171,23 @@ def _composite_row( ) return row + # ⭐ **인계 조각 수량은 「합계」다** — B08 과의 계약이고 그쪽 시험이 지킨다 + # (BOX 동바리 3.92 × 연장 10m · 포장 97.66㎥). 그래서 위에서 더한 `money` 는 이미 + # **그 줄 전체 금액**이지 1단위 단가가 아니다. 종전엔 이것을 1단위로 보고 아래에서 + # 줄 수량을 **한 번 더** 곱해 금액이 배로 부풀었다 — 평떼 718.14㎡ 가 720배(42억)로 섰다 + # (2026-09-15 랩탑 서브 실측 · B09 시험이 줄 수량 1 로만 재서 못 잡았음). + # 계약은 그대로 두고 **읽는 쪽**을 고친다 — 합계를 그대로 쓰고 단가는 나눠서 보인다. money = money.floored(Decimal(1)) - line = bill_line(money, settle_quantity(row)) + quantity = settle_quantity(row) _set_unit(row, money) - row.unit_price_krw = round_at(money.total, OutputPlace.UNIT_PRICE_ROW) - row.amount_krw = line.total - row.material_krw = line.material - row.labor_krw = line.labor - row.expense_krw = line.expense - row.add_note("quantity", f"묶음 {len(item.composite_parts)}조각 합계") + row.unit_price_krw = ( + round_at(money.total / quantity, OutputPlace.UNIT_PRICE_ROW) if quantity else None + ) + row.amount_krw = money.total + row.material_krw = money.material + row.labor_krw = money.labor + row.expense_krw = money.expense + row.add_note("quantity", f"묶음 {len(item.composite_parts)}조각 합계 — 조각 수량이 이미 합계") _mark_manual_materials(row, [code for code, _ in row.parts], unit_prices, result) return row diff --git a/resources/tester/test_b09_composite_partial_parts.py b/resources/tester/test_b09_composite_partial_parts.py index 559363cb..b2a8bf2e 100644 --- a/resources/tester/test_b09_composite_partial_parts.py +++ b/resources/tester/test_b09_composite_partial_parts.py @@ -7,11 +7,13 @@ from __future__ import annotations +import pytest + from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill from B09_Estimation.B09_Estimation_UnitPrice import cached_build -def _composite(parts: list[dict]) -> dict: +def _composite(parts: list[dict], quantity: str = "1") -> dict: return { "work_items": [ { @@ -19,7 +21,7 @@ def _composite(parts: list[dict]) -> dict: "name": "시험 묶음", "spec": "", "unit": "개소", - "quantity": "1", + "quantity": quantity, "in_bill": True, "composite_parts": parts, "composite_not_ready": [], @@ -29,8 +31,8 @@ def _composite(parts: list[dict]) -> dict: } -def _line(parts: list[dict]): - rows = build_bill(_composite(parts)).rows +def _line(parts: list[dict], quantity: str = "1"): + rows = build_bill(_composite(parts, quantity)).rows return next(r for r in rows if r.code is None and r.name == "시험 묶음") @@ -52,3 +54,32 @@ def test_밑수_없는_조각도_같이_막음() -> None: def test_온전한_조각만이면_종전대로_금액() -> None: line = _line([{"code": "FP-09-15-02", "quantity": 2}]) assert line.amount_krw and line.amount_krw > 0, line.note + + +# ── 줄 수량이 1 이 아닐 때 — 2026-09-15 랩탑 서브가 찾은 부풀음 ── +#: ⭐ **인계 조각 수량은 「합계」다** — B08 계약이고 그쪽 시험이 지킴 +#: (BOX 동바리 3.92×10 · 포장 97.66㎥). +#: 그래서 `Σ(조각 단가 × 조각 수량)` 이 이미 **그 줄 전체 금액**이다. +#: 줄 수량을 또 곱하면 배로 부푼다. 종전 시험이 **줄 수량 1 로만 재서** 못 잡았다 — +#: 평떼 718.14㎡ 가 720배(42억)로 섰다. +_PART = {"code": "FP-09-15-02", "quantity": 2} + + +@pytest.mark.parametrize("quantity", ["2", "10", "718.14"]) +def test_묶음_금액은_줄_수량을_다시_안_곱한다(quantity) -> None: + """조각이 합계이므로 **줄 수량이 몇이든 금액은 같다**. 단가는 그 금액 ÷ 수량으로 보인다.""" + one = _line([_PART]) + many = _line([_PART], quantity) + assert one.amount_krw and many.amount_krw, (one.amount_krw, many.amount_krw) + assert many.amount_krw == one.amount_krw, (quantity, many.amount_krw, one.amount_krw) + assert many.unit_price_krw < one.unit_price_krw, (quantity, many.unit_price_krw) + + +def test_묶음_성분도_줄_수량만큼_안_부푼다() -> None: + """재·노·경 셋도 같이 봐야 함 — 합계만 보면 성분이 부푼 것을 놓친다.""" + one, many = _line([_PART]), _line([_PART], "10") + assert (many.material_krw, many.labor_krw, many.expense_krw) == ( + one.material_krw, + one.labor_krw, + one.expense_krw, + )