Files
Aislo/B09_Estimation/B09_Estimation_RateTable.py
T
eomsangdonandClaude Opus 5 cb33e81d98 feat(b09): 원가계산서(일반형식)·제비율 요율표 탭 파일 — STmate 배치 · 값은 우리 엔진
- 서버: /estimation/cost-sheet(GET·PUT) · /estimation/rate-table — 새 라우터 파일
- 직접비 = 설계내역서 합계 · 기준 입력 = estimation.cost_sheet · 폐기물처리비 = 임목폐기물 톤 × 수동 단가
- 서식 줄: 나.순공사원가 … 바.총공사비 (DFM TWM_KANJUB 차례·표기) · 경비 세목 번호 차례
- 기준 입력판: 형식(일반만) · 2.공사의종류 · 3.공사기간 · 6.관/사급 · 7.산업안전 · 8.환경보전 · 9.건설기계 — 선택지는 요율 데이터에서
- 요율표: 첫 탭 묶음 10개(보기 전용 · 수정은 잠김)
- 탭 계약 파일은 서브 314c0040 과 같은 내용(blob 동일) · 사전은 새 벌 b4
- 시험: 서식 차례·엔진 값 일치·폐기물 자리·저장값 거름·구간 표기

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
2026-09-14 01:32:30 +09:00

236 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""B09 제비율 요율표 화면 자료 — STmate 「원가계산기준」(wM_KAN_RATE) 첫 탭 차례 (PLAN 12장).
⚠ 요율 데이터(`rates_<연도>.json`)를 **읽어 보이기만** 한다 — 묶음 제목·산식 표기는 DFM
`TWM_KAN_RATE` 첫 탭(☞ 공사원가계산 제잡비율) 차례를 따르고, 값·구간은 우리 데이터 그대로.
⚠ 수정은 아직 없음 — 엔진이 프로젝트 덮어쓰기 요율을 받는 칸이 서야 열 수 있음.
"""
from __future__ import annotations
from decimal import Decimal
from typing import Any
from B09_Estimation.B09_Estimation_CostSheet import SAFETY_LABELS, WORK_TYPE_LABELS
from B09_Estimation.B09_Estimation_Rates import RateDataset, _bracket_bounds, _duration_bounds
_EOK = Decimal(100_000_000)
def _won(amount: Decimal) -> str:
"""원 → 「50억」·「5천만」 표기."""
if amount >= _EOK:
return f"{amount / _EOK:g}억"
return f"{amount / 10_000_000:g}천만"
def amount_label(label: str) -> str:
"""구간 키 → 사람 표기. 모르는 키는 그대로(지어내지 않음)."""
bounds = _bracket_bounds(label)
if bounds is None:
return label
low, high = bounds
if low == 0:
return f"{_won(high)} 미만"
if high == Decimal("Infinity"):
return f"{_won(low)} 이상" + (" " + label.split("_", 3)[-1] if label.count("_") > 2 else "")
return f"{_won(low)}~{_won(high)} 미만"
def duration_label(label: str) -> str:
bounds = _duration_bounds(label)
if bounds is None:
return label
low, high = bounds
if low == 0:
return f"{high}일 이하"
if high >= 10**9:
return f"{low}일 이상"
return f"{low}~{high}일"
def _type(value: str, safety: bool = False) -> str:
return {**WORK_TYPE_LABELS, **(SAFETY_LABELS if safety else {})}.get(value, value)
def _pivot(rows: list[dict[str, Any]], row_keys: tuple[str, ...], col_key: str, safety=False):
"""세로 줄을 (구간…) × 공종 표로 편다 — DFM 표 모양(행 구간 · 열 공종)."""
columns = list(dict.fromkeys(str(row[col_key]) for row in rows))
grid: dict[tuple[str, ...], dict[str, Any]] = {}
for row in rows:
grid.setdefault(tuple(str(row[k]) for k in row_keys), {})[str(row[col_key])] = row
return columns, grid
def rate_sections(dataset: RateDataset) -> list[dict[str, Any]]:
"""묶음 목록 `{title, formula, columns, rows}` — DFM 첫 탭 위→아래 차례."""
v = dataset.variables
sections: list[dict[str, Any]] = []
overhead = v["rate_overhead"]["civil_landscape_industrial"]
profit = {
row["estimated_price_bracket"]: row["rate_percent"] for row in v["rate_profit"]["brackets"]
}
sections.append(
{
"title": "일반관리비 · 이윤",
"formula": "일반관리비 (재+노+경) × 율 · 이윤 (노+경+일) × 율",
"columns": ["공사규모(추정가격)", "일반관리비(%)", "이윤(%)"],
"rows": [
[
amount_label(row["estimated_price_bracket"]),
row["rate_percent"],
profit.get(row["estimated_price_bracket"], ""),
]
for row in overhead
],
}
)
for variable, title, formula in (
("rate_indirect_labor", "간접노무비", "(직접노무비) × 율(%)"),
("rate_other_expense", "기타경비", "(재료비+노무비) × 율(%)"),
):
columns, grid = _pivot(
v[variable]["brackets"], ("direct_cost_bracket", "duration_bracket"), "work_type"
)
sections.append(
{
"title": title,
"formula": formula,
"columns": ["공사규모(직접공사비)", "공사기간", *(_type(c) for c in columns)],
"rows": [
[
amount_label(size),
duration_label(days),
*(cells[c]["rate_percent"] if c in cells else "" for c in columns),
]
for (size, days), cells in grid.items()
],
}
)
sections.append(
{
"title": "고용보험료",
"formula": "(노무비) × 율",
"columns": ["등급", "추정금액", "요율(%)"],
"rows": [
[row["grade"], amount_label(row["estimated_amount_bracket"]), row["rate_percent"]]
for row in v["rate_goyong"]["brackets"]
],
}
)
columns, grid = _pivot(
v["rate_safety_pct"]["brackets"], ("target_amount_bracket",), "work_type"
)
sections.append(
{
"title": "산업안전보건관리비",
"formula": "(재료비+직접노무비+관급자재) × 율 + 기초액",
"columns": ["대상액", *(f"{_type(c, safety=True)}(%)" for c in columns), "기초액(원)"],
"rows": [
[
amount_label(size),
*(cells[c]["rate_percent"] if c in cells else "" for c in columns),
next(
(
cells[c].get("base_amount_krw")
for c in columns
if cells.get(c, {}).get("base_amount_krw")
),
"",
),
]
for (size,), cells in grid.items()
],
}
)
sections.append(
{
"title": "환 경 보 전 비",
"formula": "(재료비+직접노무비+산출경비) × 율",
"columns": ["구 분", "요율(%)"],
"rows": [
[_type(row["work_type"]), row["rate_percent"]]
for row in v["rate_environment"]["all_work_types"]
],
}
)
sections.append(
{
"title": "공사이행보증수수료",
"formula": "[추가금액 + (직접공사비-기준금액) × 율] × 공기(년)",
"columns": ["공사규모(직접공사비)", "수수료 산식"],
"rows": [
[
amount_label(row["direct_cost_bracket"]),
str(row.get("formula", ""))
.replace("direct_cost", "직공비")
.replace("duration_years", "공기(년)"),
]
for row in v["rate_performance_guarantee_fee"]["brackets"]
],
}
)
sections.append(
{
"title": "건설하도급대금지급보증서발급수수료",
"formula": "(직접공사비) × 율",
"columns": ["공사규모", "요율(%)"],
"rows": [
[amount_label(row["estimated_price_bracket"]), row["rate_percent"]]
for row in v["rate_subcontract_payment_guarantee"]["brackets"]
],
}
)
equipment = v["rate_equipment_payment_guarantee"]
sections.append(
{
"title": "건설기계대여대금 지급보증서 발급금액",
"formula": "(직접공사비) × 율",
"columns": ["구 분", "요율(%)"],
"rows": [
*(
[_type(row["work_type"]), row["rate_percent"]]
for row in equipment["general_construction"]
),
*(
["전문건설 — " + row["work_type"], row["rate_percent"]]
for row in equipment["specialty_construction"]
),
],
}
)
pension = next(
(
row["rate_percent"]
for row in v["rate_pension"]["annual_rates"]
if row["year"] == int(str(dataset.version_stamp.get("effective_date", "0"))[:4] or 0)
),
"",
)
sections.append(
{
"title": "퇴직부금비 · 법 정 부 담 금 · 보험료",
"formula": "",
"columns": ["항목", "산식", "요율(%)"],
"rows": [
[
"퇴직공제부금비",
"(직접노무비) × 율",
v["rate_retirement_mutual_aid"]["rate_percent"],
],
["석면분담금", "(노무비) × 율", v["rate_asbestos_contribution"]["rate_percent"]],
[
"임금채권부담금",
"(노무비) × 율",
v["rate_wage_claim_contribution"]["rate_percent"],
],
["산재보험료", "(노무비) × 율", v["rate_sanjae"]["rate_percent"]],
["건강보험료", "(직접노무비) × 율", v["rate_health"]["rate_percent"]],
["장기요양보험료", "(건강보험료) × 율", v["rate_care"]["rate_percent"]],
["연금보험료", "(직접노무비) × 율", pension],
["부가가치세", "(공급가액) × 율", v["rate_vat"]["rate_percent"]],
],
}
)
return sections