fix(b09): 중기 호표 잡품을 주연료비 × 율 가산 행으로·성분 소계 절사 — 골든셋 중기 143 호표 전수
- 잡품을 연료 수량에 접지 않고 주연료비의 % 가산 행으로(STmate 잡품 단가 칸 = 연료 금액) - 시간당 중기사용료(X) 호표 — 줄 금액 0.1원 미만 · 성분 소계 원 미만 절사(명세 7장, 봉화 0.7㎥ 96,843) - 조종원 시간당 노임 자르는 자리를 프로젝트 설정 operator_wage_digits 로(기본 0, 화면은 뒤) - 골든셋 ④ 벌 — 실무 6건 중기사용료 시트 143 호표를 우리 단가표 층으로 되풀어 전수 일치 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -94,6 +94,8 @@ def _slots(value: Decimal) -> list[Decimal | None]:
|
||||
@dataclass
|
||||
class UnitPriceBuild:
|
||||
book: PriceBook = field(default_factory=PriceBook)
|
||||
#: 조종원 시간당 노임을 자른 자리(프로젝트 설정 `operator_wage_digits`, 기본 0 = 원 미만).
|
||||
operator_wage_digits: int = 0
|
||||
#: 세우지 못한 공종 — 값이 안 서는 것을 빈 줄로 두지 않는다.
|
||||
skipped: list[str] = field(default_factory=list)
|
||||
#: 기계 성분이 비어 있는 채로 쓰인 기종 (연료·조종원 미확보).
|
||||
@@ -244,8 +246,26 @@ def _apply_combined_misc_rate(book: PriceBook, work_item_titles: list[str]) -> i
|
||||
return swapped
|
||||
|
||||
|
||||
#: 중기 호표 잡품 줄 이름표 — 화면이 「공구손료·잡재료(1-2-6)」와 가르는 데 씀.
|
||||
MISC_NOTE_PREFIX = "잡품"
|
||||
|
||||
|
||||
def _misc_row(hourly_code: str, percent: Decimal) -> PriceDetail:
|
||||
"""중기 호표 **잡품** — 주연료비의 % 가 재료비로 붙는 가산 행(자기 호표를 가리키는 비율 줄)."""
|
||||
return PriceDetail(
|
||||
hourly_code,
|
||||
hourly_code,
|
||||
Decimal(0),
|
||||
note=f"{MISC_NOTE_PREFIX} — 주연료비 × {percent}%",
|
||||
percent_of_material=percent,
|
||||
)
|
||||
|
||||
|
||||
def _add_machine_layers(
|
||||
book: PriceBook, machine_codes: set[str], fuel_region: str | None = None
|
||||
book: PriceBook,
|
||||
machine_codes: set[str],
|
||||
fuel_region: str | None = None,
|
||||
operator_wage_digits: int = 0,
|
||||
) -> list[str]:
|
||||
"""`S`(취득가) · `L`(운전사) · `M`(연료) 을 세우고 그 위에 `X` 를 올린다.
|
||||
|
||||
@@ -353,10 +373,11 @@ def _add_machine_layers(
|
||||
|
||||
liters = record.fuel_liters_per_hour
|
||||
if liters is not None:
|
||||
book.add_detail(PriceDetail(hourly_code, fuel_code, liters, note="주연료"))
|
||||
if record.misc_material_percent is not None:
|
||||
# 잡재료는 **주연료의 %** — 유가와 같이 움직인다.
|
||||
liters = liters * (Decimal(1) + record.misc_material_percent / Decimal(100))
|
||||
book.add_detail(PriceDetail(hourly_code, fuel_code, liters, note="주연료 + 잡재료"))
|
||||
# 잡품은 **주연료비 × 율의 가산 행**(명세 7장 · STmate 17번 §3 「잡품 단가 칸 = 연료
|
||||
# 금액」) — 연료 수량에 접지 않음. 유가와 같이 움직이는 것은 그대로.
|
||||
book.add_detail(_misc_row(hourly_code, record.misc_material_percent))
|
||||
|
||||
# 조합 사용(리퍼·브레이커·집게)일 때 쓸 **잡재료 16%** 짜리 층을 함께 세운다.
|
||||
# 같은 기계라도 조합이면 본체 잡재료가 줄어든다(품셈 제8장 [주]⑤).
|
||||
@@ -378,11 +399,11 @@ def _add_machine_layers(
|
||||
PriceDetail(
|
||||
combined_code,
|
||||
fuel_code,
|
||||
record.fuel_liters_per_hour
|
||||
* (Decimal(1) + COMBINED_MISC_PERCENT / Decimal(100)),
|
||||
note=f"주연료 + 잡재료 {COMBINED_MISC_PERCENT}% (조합 사용)",
|
||||
record.fuel_liters_per_hour,
|
||||
note="주연료 (조합 사용)",
|
||||
)
|
||||
)
|
||||
book.add_detail(_misc_row(combined_code, Decimal(COMBINED_MISC_PERCENT)))
|
||||
else:
|
||||
incomplete.append(f"{code} (연료소모량 없음)")
|
||||
|
||||
@@ -402,12 +423,18 @@ def _add_machine_layers(
|
||||
kind=PriceKind.LABOR,
|
||||
name="조종원(시간당)",
|
||||
unit="hr",
|
||||
slots=_slots(hourly_operator_wage(wages[wage_code])),
|
||||
slots=_slots(
|
||||
hourly_operator_wage(wages[wage_code], digits=operator_wage_digits)
|
||||
),
|
||||
# ⚠ 조종원도 노임이다 — 같은 플래그가 붙어야 한다.
|
||||
reliability=load_labor_reliability().get(wage_code, ""),
|
||||
)
|
||||
)
|
||||
wage_code = hourly_wage_code
|
||||
cut = (
|
||||
"원 미만" if operator_wage_digits == 0 else f"소수 {operator_wage_digits}자리 아래"
|
||||
)
|
||||
operator_note = f"조종원 (일당 × {OPERATOR_WAGE_FORMULA} 좌→우 · {cut} 절사)"
|
||||
# 조합 층에도 조종원을 같이 단다 — 본체를 모는 사람은 하나뿐이다.
|
||||
combined_code = f"{hourly_code}#조합"
|
||||
if combined_code in book.titles:
|
||||
@@ -416,7 +443,7 @@ def _add_machine_layers(
|
||||
combined_code,
|
||||
wage_code,
|
||||
per_hour_person,
|
||||
note=f"조종원 (일당 × {OPERATOR_WAGE_FORMULA} 좌→우 · 원 미만 절사)",
|
||||
note=operator_note,
|
||||
)
|
||||
)
|
||||
book.add_detail(
|
||||
@@ -424,7 +451,7 @@ def _add_machine_layers(
|
||||
hourly_code,
|
||||
wage_code,
|
||||
per_hour_person,
|
||||
note=f"조종원 (일당 × {OPERATOR_WAGE_FORMULA} 좌→우 · 원 미만 절사)",
|
||||
note=operator_note,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -580,6 +607,7 @@ def build_unit_prices(
|
||||
transport_distance_km: Decimal | None = None,
|
||||
transport_road: str | None = None,
|
||||
labor_surcharge_choices: dict[str, str] | None = None,
|
||||
operator_wage_digits: int = 0,
|
||||
) -> UnitPriceBuild:
|
||||
"""자원 축을 일위대가(`B`)로 조립한다.
|
||||
|
||||
@@ -599,6 +627,9 @@ def build_unit_prices(
|
||||
|
||||
⚠ `labor_surcharge_choices` — 품의 할인·할증 26계열(산림품셈 1-4). **안 고르면 안 붙는다.**
|
||||
어느 공종에 붙일지는 원문 [주]가 작업을 지목하므로 **켜는 것은 사용자 몫**이다.
|
||||
|
||||
⚠ `operator_wage_digits` — 조종원 시간당 노임을 자르는 자리(0 = 원 미만 절사). 실무마다 다름
|
||||
(다섯 건 0 · 2025 울진 소광 1) — 규정이 없는 자리라 프로젝트 설정 칸(브레인 판정 2026-09-13).
|
||||
"""
|
||||
from B09_Estimation.B09_Estimation_FactorChoices import (
|
||||
chosen_values,
|
||||
@@ -731,7 +762,10 @@ def build_unit_prices(
|
||||
from B09_Estimation.B09_Estimation_Transport import TRANSPORT_VARIANTS
|
||||
|
||||
machine_codes |= {variant["machine_code"] for variant in TRANSPORT_VARIANTS}
|
||||
build.incomplete_machines = _add_machine_layers(build.book, machine_codes, fuel_region)
|
||||
build.operator_wage_digits = operator_wage_digits
|
||||
build.incomplete_machines = _add_machine_layers(
|
||||
build.book, machine_codes, fuel_region, operator_wage_digits
|
||||
)
|
||||
|
||||
# 규격 갈래(`variant`)가 있으면 **갈래마다 따로 세운다** — 무근·철근·소형구조물은
|
||||
# 품이 달라 한 일위대가로 뭉치면 어느 것도 안 맞는다.
|
||||
@@ -1116,6 +1150,7 @@ def cached_build(
|
||||
transport_distance_km: str = "",
|
||||
transport_road: str = "",
|
||||
labor_surcharge: tuple[tuple[str, str], ...] = (),
|
||||
operator_wage_digits: str = "",
|
||||
) -> UnitPriceBuild:
|
||||
"""조립 결과를 한 번만 만든다 — 품셈 3 MB 를 요청마다 다시 읽지 않는다.
|
||||
|
||||
@@ -1142,9 +1177,16 @@ def cached_build(
|
||||
transport_distance_km=parse_distance_km(transport_distance_km),
|
||||
transport_road=transport_road or None,
|
||||
labor_surcharge_choices=dict(labor_surcharge),
|
||||
operator_wage_digits=parse_operator_wage_digits(operator_wage_digits),
|
||||
)
|
||||
|
||||
|
||||
def parse_operator_wage_digits(value: Any) -> int:
|
||||
"""조종원 시간당 노임 자르는 자리 설정 — 0·1·2 만 받고 그 밖(빈 값 포함)은 기본 0."""
|
||||
text = str(value if value is not None else "").strip()
|
||||
return int(text) if text in ("0", "1", "2") else 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class DirectCostBreakdown:
|
||||
"""⑤ 공사원가계산서가 받는 **직접비 3분할**.
|
||||
|
||||
Reference in New Issue
Block a user