feat(B09): 공구손료·잡재료 칸 — 기본은 빔, 넣으면 주재료비의 %로 붙음
사용자 확정 5차 작은 것 1 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」. 근거는 산림품셈 1-2-6 — 주재료비(할증수량 제외)의 2~5%까지, 산정 근거 명시. - 기초자료 탭 「산출 조건」에 칸 하나 + [적용]. 비면 줄 자체가 안 섬(지금 상태 그대로). - 밑수는 **자재 줄만** — 노무·경비, 하위 일위대가 재료비는 안 듦(층마다 거듭 세지 않음). - 상한 5% 초과는 거절(400) — 조용히 깎아 넣지 않음. - ⚠ 지금은 일위대가에 주재료비가 선 공종이 0개라 붙을 밑수가 없음 — 그 사실을 칸 밑에 띄움. - 곁다리: 비율 줄(제잡비·공구손료)이 저장했다 읽으면 사라지던 것을 고침. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from dataclasses import dataclass, field
|
||||
from dataclasses import replace as dataclass_replace
|
||||
from decimal import Decimal
|
||||
from functools import lru_cache
|
||||
from typing import Any
|
||||
|
||||
from B09_Estimation.B09_Estimation_Guards import check_column_sums, check_surcharge_once
|
||||
from B09_Estimation.B09_Estimation_MachineCost import (
|
||||
@@ -157,6 +158,40 @@ _ATTACHMENT_WORDS = ("브레이커", "리퍼", "부착용집게", "집게")
|
||||
#: 조금씩 비싸진다(굴착기 0.7 기준 시간당 1,285원).
|
||||
COMBINED_MISC_PERCENT = Decimal(16)
|
||||
|
||||
#: ⭐ 사용자 확정 5차 작은 것 1 — **공구손료·잡재료 칸**.
|
||||
#: 「지금은 안 넣음. 다만 숫자를 넣으면 되게 열어 둘 것 — 칸을 만들되 기본은 빔, 비면 안 붙음」
|
||||
#: 근거는 산림품셈 1-2-6 — 「각 항목에 명시되어 있는 잡재료 및 소모재료에 대해서는 이를
|
||||
#: 계상하고, 명시되어 있지 않는 … 주재료비(재료비의 할증수량 제외)의 **2~5%까지** 별도
|
||||
#: 계상하되 **산정 근거를 명시**하여야 한다」.
|
||||
#: ⚠ **몇 %인지는 사용자 몫이다** — 기본값을 만들어 두지 않는다(범위값을 임의로 굳히면
|
||||
#: 금액이 조용히 그 값으로 선다). 비어 있으면 줄 자체가 안 서고 지금 상태 그대로다.
|
||||
MISC_MATERIAL_MAX_PERCENT = Decimal(5) # 「2~5%**까지**」 — 상한
|
||||
MISC_MATERIAL_MIN_PERCENT = Decimal(2) # 원문이 적은 아랫값(아래로 내려가면 사유로 알린다)
|
||||
MISC_MATERIAL_BASIS = "공구손료·잡재료 — 주재료비의 {percent}% (산림품셈 1-2-6, 산정 근거 명시)"
|
||||
|
||||
|
||||
def parse_misc_material_percent(raw: Any) -> Decimal | None:
|
||||
"""설정 칸의 값을 비율로 읽는다. **비면 `None`**(= 안 붙음).
|
||||
|
||||
⚠ 상한(5%)을 넘는 값은 **받지 않는다** — 품셈이 「2~5%까지」로 못 박은 자리다.
|
||||
"""
|
||||
text = str(raw or "").strip().rstrip("%").strip()
|
||||
if not text:
|
||||
return None
|
||||
try:
|
||||
percent = Decimal(text)
|
||||
except (ArithmeticError, ValueError):
|
||||
raise ValueError(f"공구손료·잡재료 비율을 숫자로 못 읽었습니다: {raw!r}") from None
|
||||
if percent <= 0:
|
||||
return None
|
||||
if percent > MISC_MATERIAL_MAX_PERCENT:
|
||||
raise ValueError(
|
||||
f"공구손료·잡재료는 주재료비의 {MISC_MATERIAL_MAX_PERCENT}% 까지입니다"
|
||||
f" (산림품셈 1-2-6) — 받은 값 {percent}%"
|
||||
)
|
||||
return percent
|
||||
|
||||
|
||||
#: 조합으로 쓰는 부착 장비 — 이 층이 붙은 공종의 본체는 위 비율을 쓴다.
|
||||
#: 카탈로그 분류번호로 잡는다 — 0103 유압식 리퍼 · 0230 대형 브레이커 ·
|
||||
#: 0240 유압식 진동콤팩터(굴착기 부착용) · 7206 부착용 집게.
|
||||
@@ -487,6 +522,7 @@ def build_unit_prices(
|
||||
axis: AxisResult | None = None,
|
||||
factor_choices: dict[tuple[str, str], Decimal] | None = None,
|
||||
machine_picks: dict[str, str] | None = None,
|
||||
misc_material_percent: Decimal | None = None,
|
||||
) -> UnitPriceBuild:
|
||||
"""자원 축을 일위대가(`B`)로 조립한다.
|
||||
|
||||
@@ -495,6 +531,9 @@ def build_unit_prices(
|
||||
|
||||
⚠ `factor_choices` — 품셈이 **범위로 준 계수**에 사용자가 고른 값(확정 ①). 안 주면
|
||||
**평균**이 기본이다(`B09_Estimation_FactorChoices`). 범위가 아닌 칸은 안 덮는다.
|
||||
|
||||
⚠ `misc_material_percent` — 공구손료·잡재료(산림품셈 1-2-6). **안 주면 줄이 안 선다** —
|
||||
사용자 확정 5차 작은 것 1 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」 그대로다.
|
||||
"""
|
||||
from B09_Estimation.B09_Estimation_FactorChoices import (
|
||||
chosen_values,
|
||||
@@ -698,6 +737,20 @@ def build_unit_prices(
|
||||
)
|
||||
)
|
||||
|
||||
# 공구손료·잡재료 — **주재료비의 %가 재료비로** 붙는다(산림품셈 1-2-6).
|
||||
# ⚠ **비어 있으면 이 줄이 아예 안 선다** = 지금까지와 같은 금액이다(확정 5차 작은 것 1).
|
||||
# ⚠ 갈래 제목(`__` 로 시작하는 내부 갈래)에는 안 붙인다 — 제잡비와 같은 자리를 쓴다.
|
||||
if misc_material_percent is not None and not variant_key.startswith("__"):
|
||||
build.book.add_detail(
|
||||
PriceDetail(
|
||||
title_code,
|
||||
title_code,
|
||||
_ZERO,
|
||||
note=MISC_MATERIAL_BASIS.format(percent=misc_material_percent),
|
||||
percent_of_material=misc_material_percent,
|
||||
)
|
||||
)
|
||||
|
||||
# 장비 몫은 자원 수량이 아니라 **시공능력 공식**으로 온다 (품셈 8-1-4).
|
||||
# ⚠ 불도저와 굴착기는 **식이 다르다**(8-2-1 vs 8-1-4). 둘 다 붙이면 장비를 두 번
|
||||
# 세므로, 불도저가 붙은 자리는 굴착기 쪽을 아예 안 본다.
|
||||
@@ -892,6 +945,7 @@ DRILLABLE_KINDS = frozenset({PriceKind.MACHINE_HOURLY, PriceKind.UNIT_PRICE, Pri
|
||||
def cached_build(
|
||||
range_choices: tuple[tuple[str, str], ...] = (),
|
||||
machine_picks: tuple[tuple[str, str], ...] = (),
|
||||
misc_material_percent: str = "",
|
||||
) -> UnitPriceBuild:
|
||||
"""조립 결과를 한 번만 만든다 — 품셈 3 MB 를 요청마다 다시 읽지 않는다.
|
||||
|
||||
@@ -913,6 +967,7 @@ def cached_build(
|
||||
return build_unit_prices(
|
||||
factor_choices=chosen_values(scan_range_factors(master), settings),
|
||||
machine_picks=machine_choices(settings),
|
||||
misc_material_percent=parse_misc_material_percent(misc_material_percent),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user