From 3934bd8d07d135d9f72c74f78e23aecd8be05d0f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 9 Sep 2026 23:50:23 +0900 Subject: [PATCH] =?UTF-8?q?fix(B09):=20=EC=A4=91=EA=B8=B0=EA=B2=BD?= =?UTF-8?q?=EB=B9=84=EA=B3=84=EC=82=B0=EC=84=9C=EC=97=90=20=EA=B0=88?= =?UTF-8?q?=EB=9E=98=20=ED=91=9C=EC=8B=9C=20=E2=80=94=20=EA=B0=99=EC=9D=80?= =?UTF-8?q?=20=EA=B8=B0=EC=A2=85=EC=9D=B4=20=EB=91=90=20=EC=9E=A5=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=B4=EC=9D=B4=EB=8D=98=20=EC=9E=90=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 조합 사용이면 본체 잡재료가 16% 로 줄어 재료비가 달라져 층이 따로 섬(굴착기 0.7 재료비 26,130 → 24,845). 갈래를 안 적어 똑같은 장이 두 번 나온 것처럼 읽혔음. Co-Authored-By: Claude Opus 5 (1M context) --- .../B09_Estimation_MachineExpenseSheet.py | 13 +++++++++++-- B09_Estimation/B09_Estimation_UI_BaseData.ts | 13 ++++++++++++- resources/tester/test_b09_machine_expense.py | 13 +++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/B09_Estimation/B09_Estimation_MachineExpenseSheet.py b/B09_Estimation/B09_Estimation_MachineExpenseSheet.py index 75a66fa7..4d5d8692 100644 --- a/B09_Estimation/B09_Estimation_MachineExpenseSheet.py +++ b/B09_Estimation/B09_Estimation_MachineExpenseSheet.py @@ -31,6 +31,9 @@ from B09_Estimation.B09_Estimation_MachineCost import ( ) from B09_Estimation.B09_Estimation_PriceBook import PriceKind +#: 조합 사용(굴착기+부착장비)일 때의 본체 잡재료비율 — 품셈 제8장 [주]⑤. +COMBINED_MISC_PERCENT = 16 + _CATALOG_SUBPATH = ("resources", "data_cost_input_value") _THOUSAND = Decimal(1000) _ZERO = Decimal(0) @@ -93,7 +96,7 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]: for code, title in sorted(build.book.titles.items()): if title.kind is not PriceKind.MACHINE_HOURLY: continue - machine_code = code[2:].split("#")[0] + machine_code, _, variant = code[2:].partition("#") machine = catalog.machines.get(machine_code) if machine is None: continue @@ -121,6 +124,10 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]: "machine_code": machine_code, "name": machine.name, "spec": machine.specification, + # ⚠ 같은 기종이 **두 장**일 수 있다 — 조합 사용이면 잡재료가 16% 로 줄어 + # 재료비가 달라지므로 층이 따로 선다(품셈 제8장 [주]⑤). 갈래를 안 적으면 + # 똑같은 장이 두 번 나온 것처럼 읽힌다. + "variant": variant, # ① 손료 "price_thousand_krw": _money(machine.price_thousand_krw), "economic_life_hours": raw.get("economic_life_hours"), @@ -138,7 +145,9 @@ def machine_expense_sheets(build: Any) -> list[dict[str, Any]]: "fuel_liters_per_hour": _money(fuel_liters), "fuel_price_per_liter": _money(fuel_price), "fuel_scope": fuel_meta.get("region_name") or "전국 공시가", - "misc_material_percent": _money(misc_percent), + "misc_material_percent": ( + str(COMBINED_MISC_PERCENT) if variant else _money(misc_percent) + ), "operator_code": occupation, "operator_daily_wage": _money(wage), "operator_krw_per_hour": _money( diff --git a/B09_Estimation/B09_Estimation_UI_BaseData.ts b/B09_Estimation/B09_Estimation_UI_BaseData.ts index 72cafae8..47dcad4d 100644 --- a/B09_Estimation/B09_Estimation_UI_BaseData.ts +++ b/B09_Estimation/B09_Estimation_UI_BaseData.ts @@ -272,6 +272,7 @@ export interface MachineExpenseDto { labor_krw: string | null; expense_krw: string | null; total_krw: string | null; + variant: string; attachment: boolean; attachment_note: string; gaps: string[]; @@ -293,7 +294,9 @@ function machineExpenseSheet(sheet: MachineExpenseDto["sheets"][number]): HTMLEl box.className = "b09-panel__group"; const title = document.createElement("p"); title.className = "b09-panel__legend"; - title.textContent = `${sheet.machine_code} ${sheet.name} ${sheet.spec}`.trim(); + title.textContent = + `${sheet.machine_code} ${sheet.name} ${sheet.spec}`.trim() + + (sheet.variant ? ` — ${sheet.variant}` : ""); box.append(title); const coefficient = (value: number | null) => (value === null ? "—" : String(value)); @@ -334,6 +337,14 @@ function machineExpenseSheet(sheet: MachineExpenseDto["sheets"][number]): HTMLEl [0, 1], ), ); + if (sheet.variant) { + box.append( + note( + "같은 기종이라도 조합 사용이면 잡재료가 16% 로 줄어 재료비가 달라집니다 —" + + " 그래서 층이 따로 섭니다(건설품셈 제8장 [주]⑤).", + ), + ); + } if (sheet.attachment_note) box.append(note(sheet.attachment_note)); for (const gap of sheet.gaps) box.append(note(`⚠ ${gap}`)); return box; diff --git a/resources/tester/test_b09_machine_expense.py b/resources/tester/test_b09_machine_expense.py index 106c69f0..5602b427 100644 --- a/resources/tester/test_b09_machine_expense.py +++ b/resources/tester/test_b09_machine_expense.py @@ -75,3 +75,16 @@ def test_수송비는_이_장에_안_붙는다() -> None: assert any("수송비" in line and "회당" in line for line in report["notes"]) for sheet in report["sheets"]: assert "transport" not in sheet + + +def test_같은_기종의_두_장은_갈래로_갈린다() -> None: + """⚠ 조합 사용이면 잡재료가 16% 로 줄어 재료비가 달라진다 — 갈래를 안 적으면 + 똑같은 장이 두 번 나온 것처럼 읽힌다(건설품셈 제8장 [주]⑤).""" + sheets = {sheet["code"]: sheet for sheet in _report()["sheets"]} + plain = sheets.get("X-0201-0070") + combined = sheets.get("X-0201-0070#조합") + assert plain is not None and combined is not None + assert plain["variant"] == "" and combined["variant"] == "조합" + assert combined["misc_material_percent"] == "16" + # 조합 쪽이 잡재료가 줄어 재료비가 더 싸야 한다. + assert Decimal(combined["material_krw"]) < Decimal(plain["material_krw"])