feat(B09 원가계산서 · 708-2): 현행 비목 다섯 셈 — 퇴직공제 · 임금채권 · 석면 · 하도급 요율 셈 · 공사이행보증 금액 손입력

- 셈 짝(Calc_Total py ↔ ts) — STmate 일반형식 식 · 자리(지식DB 12 · 35 분석) · 경비 묶음 안 · 나.소계에 듦
  퇴직공제부금비 = 직접노무비 × 율 · 임금채권 · 석면 = (직노 + 간노) × 율 · 하도급 = 순공사비 × 율 ·
  공사이행보증 = 금액 직접 입력(performance_fee) · 자동 요율 없음 · 요율 빈칸 = 해당 없음(0)
- 요율 출처 「불러옴」 = rates_imported(707-2 불러오기가 실음 · 요율 고치면 사용자)
- 707-2 불러오기 — 「셈 없음」 알림 뺌 · 공사이행보증 줄 금액 = 손입력 칸
- 총괄 · 원가계산서 탭 · 인쇄 미리보기 = 같은 줄 짜기
- 낡은 시험 고침 — 현행 비목 기대값 빈칸 → 0 · 퇴직공제 요율 칸 고침 가능 · 거창 총공사비 = 엑셀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 21:27:40 +09:00
co-authored by Claude Opus 5.5
parent a5179e4dfe
commit 472bf98ba2
9 changed files with 161 additions and 79 deletions
+28 -13
View File
@@ -19,7 +19,14 @@
산업안전보건관리비 = A(관급 순자재대 ÷ 1.1 포함) · B(관급 빼고 × 1.2) 중 작은 쪽 ·
이윤 = ROUNDDOWN((다 − 재료비) × 율, 0) − 보정 · 부가세 = ROUNDDOWN(공급가액 × 율, 0) + 조정.
빈칸 규칙 = 요율(사용자 · 자동 둘 다 없음) · 비목 합이 빈칸이면 그 줄 빈칸(0 을 채우지 않음) · 그 줄을 쓰는 소계도 빈칸 ·
손입력(기초액 · 보정 · 조정 · 간접재료비 · 작업설) 빈칸 = 0 · 엑셀에 없는 현행 비목 = 줄만(늘 빈칸).
손입력(기초액 · 보정 · 조정 · 간접재료비 · 작업설) 빈칸 = 0.
현행 비목 다섯(PLAN 708-2 · 엑셀 총괄에 없음 · 경쟁사 둘 다 셈) = STmate 일반형식 식 · 자리
(지식DB `STmate 분석/20_분석/12_원가계산_4형식과_RATE표_복원.md` 일반 · 수공 경비 차례 ·
`35_형식과_단계의_공통과_차이.md` 밑수 표) — 경비 묶음 안 · 나.소계(순공사원가)에 듦 ·
퇴직공제부금비 = 직접노무비 × 율 · 임금채권 · 석면 = (직접노무비 + 간접노무비) × 율 ·
하도급대금지급보증수수료 = 순공사비 × 율 · 공사이행보증수수료 = 금액 손입력(`performance_fee` ·
STmate 식 = 공종 · 기간 표라 뒤로) · 자동 요율 없음 · 요율 빈칸 = 해당 없음(0 · 빈칸 아님).
요율 출처 = 사용자 값 중 `rates_imported` 키 = 「import」(707-2 불러옴).
"""
from __future__ import annotations
@@ -29,7 +36,8 @@ from typing import Any
from B09_Estimation.B09_Estimation_Calc_Base import num, rounddown, roundup, total
from common_util.common_util_time import kst_today
#: 제비율 줄 (키, 이름, M01 요율 후보 키, 산식 글) — 엑셀 총괄 49~58 차례 · 나.소계에 듦
#: 제비율 줄 (키, 이름, M01 요율 후보 키, 산식 글) — 엑셀 총괄 49~58 차례 +
#: 현행 비목(STmate 경비 차례) · 나.소계에 듦
RATE_LINES: tuple[tuple[str, str, str, str], ...] = (
("indirect_labor", "간접노무비", "RT000008", "직접노무비 × 율"),
("accident", "산재보험료", "RT000001", "(직접노무비 + 간접노무비) × 율"),
@@ -37,19 +45,18 @@ RATE_LINES: tuple[tuple[str, str, str, str], ...] = (
("health", "건강보험료", "RT000003", "직접노무비 × 율"),
("care", "노인장기요양보험료", "RT000005", "건강보험료 × 율"),
("pension", "연금보험료", "RT000004", "직접노무비 × 율"),
("retirement", "퇴직공제부금비", "RT000011", "직접노무비 × 율"),
("safety", "산업안전보건관리비", "RT000006", "A · B 중 작은 쪽 + 기초액"),
("other", "기타경비", "RT000009", "(직접노무비 + 간접노무비 + 재료비) × 율"),
("performance", "공사이행보증수수료", "RT000016", "금액 직접 입력"),
("subcontract", "하도급대금지급보증수수료", "RT000014", "순공사비 × 율"),
("environment", "환경보전비", "RT000010", "순공사비 × 율"),
("machine", "건설기계대여금지급보증", "RT000015", "순공사비 × 율"),
("asbestos", "석면피해구제분담금", "RT000013", "(직접노무비 + 간접노무비) × 율"),
("wage_claim", "임금채권보장분담금", "RT000012", "(직접노무비 + 간접노무비) × 율"),
)
#: 엑셀에 없는 현행 비목 — 줄만 두고 빈칸(밑수 · 고르는 값 미정 · 나.소계에 안 듦)
CURRENT_ONLY: tuple[tuple[str, str, str], ...] = (
("retirement", "퇴직공제부금비", "RT000011"),
("wage_claim", "임금채권보장분담금", "RT000012"),
("asbestos", "석면피해구제분담금", "RT000013"),
("subcontract", "하도급대금지급보증수수료", "RT000014"),
("performance", "공사이행보증수수료", "RT000016"),
)
#: 현행 비목 — 요율 빈칸 = 해당 없음(0) · 공사이행보증 = 금액 손입력(율 칸 없음)
CURRENT_ONLY = ("retirement", "subcontract", "asbestos", "wage_claim", "performance")
#: 소계 아래 줄 (키, 이름, 후보 키, 산식 글)
TAIL_LINES: tuple[tuple[str, str, str, str], ...] = (
("overhead", "일반관리비", "RT000017", "나.소계 × 율"),
@@ -135,6 +142,7 @@ def compute_total(
sums = sums if isinstance(sums, dict) else {}
est = estimate if isinstance(estimate, dict) else {}
rates_in = est.get("rates") if isinstance(est.get("rates"), dict) else {}
imported = est.get("rates_imported") if isinstance(est.get("rates_imported"), list) else []
def hand(key: str) -> float:
return num(est.get(key)) or 0.0
@@ -164,7 +172,7 @@ def compute_total(
mine, got = num(rates_in.get(key)), auto.get(key) or {}
rates[key] = {
"rate": got.get("rate") if mine is None else mine,
"from": "user"
"from": ("import" if key in imported else "user")
if mine is not None
else ("auto" if got.get("rate") is not None else None),
"auto": got.get("rate"),
@@ -193,6 +201,14 @@ def compute_total(
line["other"] = _pct(total([labor, line["indirect_labor"], material]), rate["other"])
line["environment"] = _pct(direct, rate["environment"])
line["machine"] = _pct(direct, rate["machine"])
line["retirement"] = _pct(labor, rate["retirement"])
line["subcontract"] = _pct(direct, rate["subcontract"])
line["asbestos"] = _pct(total_labor, rate["asbestos"])
line["wage_claim"] = _pct(total_labor, rate["wage_claim"])
for key in CURRENT_ONLY: # 요율 빈칸 = 해당 없음
if rate[key] is None:
line[key] = 0.0
line["performance"] = hand("performance_fee")
net_cost = total([direct, *(line[key] for key, *_ in RATE_LINES)]) # 나. 순공사원가
line["overhead"] = _pct(net_cost, rate["overhead"])
da = total([net_cost, line["overhead"]]) # 다
@@ -220,8 +236,7 @@ def compute_total(
"indirect_labor": line["indirect_labor"],
"labor": total_labor,
"expense": expense,
**{key: line[key] for key in ("accident", "employment", "health", "care", "pension")},
**{key: line[key] for key in ("safety", "other", "environment", "machine")},
**{key: line[key] for key, *_ in RATE_LINES[1:]},
"expense_sum": total(
[expense, *(line[key] for key, *_ in RATE_LINES if key != "indirect_labor")]
),