fix(b09): 묶음 줄 금액이 줄 수량만큼 부풀던 것 — 조각 수량은 이미 합계
까닭 — 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA
This commit is contained in:
@@ -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 = ()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user