refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)
B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음). 화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음. B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry 로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠. B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져 부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음. B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함. B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
"""B09 제비율 요율표 화면 자료 — STmate 「원가계산기준」(wM_KAN_RATE) 첫 탭 차례 (PLAN 12장).
|
||||
|
||||
⚠ 묶음 제목·산식 표기는 DFM `TWM_KAN_RATE` 첫 탭(☞ 공사원가계산 제잡비율) 차례,
|
||||
값·구간은 우리 데이터.
|
||||
⚠ 요율 칸마다 **주소**(`RateOverride.address_of`)와 **기본값(마스터)·지금 값(프로젝트)** 을
|
||||
함께 실음 — 화면이 「기본값 ↔ 고친 값」을 갈라 보이고 [수 정]에서 그 칸만 고쳐 사유와 함께 돌려줌.
|
||||
"""
|
||||
|
||||
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_RateOverride import address_of
|
||||
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)
|
||||
|
||||
|
||||
class _Cells:
|
||||
"""요율 칸 만들기 — 지금 판(덮어쓴 복사본)과 마스터 판을 같은 주소로 맞대 봄."""
|
||||
|
||||
def __init__(self, current: RateDataset, master: RateDataset) -> None:
|
||||
self.current, self.master = current.variables, master.variables
|
||||
|
||||
def cell(self, variable: str, table: str | None, row: dict[str, Any] | None) -> dict[str, Any]:
|
||||
address = address_of(variable, table, row)
|
||||
value = self._rate(self.current, address)
|
||||
default = self._rate(self.master, address)
|
||||
return {
|
||||
"value": value,
|
||||
"default": default,
|
||||
"overridden": value != default,
|
||||
"address": address,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _rate(variables: dict[str, Any], address: dict[str, Any]) -> Any:
|
||||
variable = variables[address["variable"]]
|
||||
if not address["table"]:
|
||||
return variable.get("rate_percent")
|
||||
for row in variable[address["table"]]:
|
||||
if all(str(row.get(k)) == str(v) for k, v in address["match"].items()):
|
||||
return row.get("rate_percent")
|
||||
return None
|
||||
|
||||
|
||||
def _pivot(rows: list[dict[str, Any]], row_keys: tuple[str, ...], col_key: str):
|
||||
"""세로 줄을 (구간…) × 공종 표로 편다 — 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, master: RateDataset | None = None) -> list[dict[str, Any]]:
|
||||
"""묶음 목록 `{title, formula, columns, rows}` — DFM 첫 탭 위→아래 차례.
|
||||
|
||||
`rows` 의 요율 칸은 `{value, default, overridden, address}` · 이름 칸은 글자.
|
||||
"""
|
||||
v = dataset.variables
|
||||
c = _Cells(dataset, master or dataset)
|
||||
sections: list[dict[str, Any]] = []
|
||||
|
||||
profit = {row["estimated_price_bracket"]: row for row in v["rate_profit"]["brackets"]}
|
||||
sections.append(
|
||||
{
|
||||
"title": "일반관리비 · 이윤",
|
||||
"formula": "일반관리비 (재+노+경) × 율 · 이윤 (노+경+일) × 율",
|
||||
"columns": ["공사규모(추정가격)", "일반관리비(%)", "이윤(%)"],
|
||||
"rows": [
|
||||
[
|
||||
amount_label(row["estimated_price_bracket"]),
|
||||
c.cell("rate_overhead", "civil_landscape_industrial", row),
|
||||
c.cell("rate_profit", "brackets", profit[row["estimated_price_bracket"]])
|
||||
if row["estimated_price_bracket"] in profit
|
||||
else "",
|
||||
]
|
||||
for row in v["rate_overhead"]["civil_landscape_industrial"]
|
||||
],
|
||||
}
|
||||
)
|
||||
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(col) for col in columns)],
|
||||
"rows": [
|
||||
[
|
||||
amount_label(size),
|
||||
duration_label(days),
|
||||
*(
|
||||
c.cell(variable, "brackets", cells[col]) if col in cells else ""
|
||||
for col in columns
|
||||
),
|
||||
]
|
||||
for (size, days), cells in grid.items()
|
||||
],
|
||||
}
|
||||
)
|
||||
sections.append(
|
||||
{
|
||||
"title": "고용보험료",
|
||||
"formula": "(노무비) × 율",
|
||||
"columns": ["등급", "추정금액", "요율(%)"],
|
||||
"rows": [
|
||||
[
|
||||
str(row["grade"]),
|
||||
amount_label(row["estimated_amount_bracket"]),
|
||||
c.cell("rate_goyong", "brackets", row),
|
||||
]
|
||||
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(col, safety=True)}(%)" for col in columns),
|
||||
"기초액(원)",
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
amount_label(size),
|
||||
*(
|
||||
c.cell("rate_safety_pct", "brackets", cells[col]) if col in cells else ""
|
||||
for col in columns
|
||||
),
|
||||
str(
|
||||
next(
|
||||
(
|
||||
cells[col].get("base_amount_krw")
|
||||
for col in columns
|
||||
if cells.get(col, {}).get("base_amount_krw")
|
||||
),
|
||||
"",
|
||||
)
|
||||
),
|
||||
]
|
||||
for (size,), cells in grid.items()
|
||||
],
|
||||
}
|
||||
)
|
||||
sections.append(
|
||||
{
|
||||
"title": "환 경 보 전 비",
|
||||
"formula": "(재료비+직접노무비+산출경비) × 율",
|
||||
"columns": ["구 분", "요율(%)"],
|
||||
"rows": [
|
||||
[_type(row["work_type"]), c.cell("rate_environment", "all_work_types", row)]
|
||||
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"]),
|
||||
c.cell("rate_subcontract_payment_guarantee", "brackets", row),
|
||||
]
|
||||
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"]),
|
||||
c.cell("rate_equipment_payment_guarantee", "general_construction", row),
|
||||
]
|
||||
for row in equipment["general_construction"]
|
||||
),
|
||||
*(
|
||||
[
|
||||
"전문건설 — " + row["work_type"],
|
||||
c.cell("rate_equipment_payment_guarantee", "specialty_construction", row),
|
||||
]
|
||||
for row in equipment["specialty_construction"]
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
year = int(str(dataset.effective_date)[:4] or 0)
|
||||
pension_row = next((r for r in v["rate_pension"]["annual_rates"] if r["year"] == year), None)
|
||||
sections.append(
|
||||
{
|
||||
"title": "퇴직부금비 · 법 정 부 담 금 · 보험료",
|
||||
"formula": "",
|
||||
"columns": ["항목", "산식", "요율(%)"],
|
||||
"rows": [
|
||||
[
|
||||
"퇴직공제부금비",
|
||||
"(직접노무비) × 율",
|
||||
c.cell("rate_retirement_mutual_aid", None, None),
|
||||
],
|
||||
["석면분담금", "(노무비) × 율", c.cell("rate_asbestos_contribution", None, None)],
|
||||
[
|
||||
"임금채권부담금",
|
||||
"(노무비) × 율",
|
||||
c.cell("rate_wage_claim_contribution", None, None),
|
||||
],
|
||||
["산재보험료", "(노무비) × 율", c.cell("rate_sanjae", None, None)],
|
||||
["건강보험료", "(직접노무비) × 율", c.cell("rate_health", None, None)],
|
||||
["장기요양보험료", "(건강보험료) × 율", c.cell("rate_care", None, None)],
|
||||
[
|
||||
"연금보험료",
|
||||
"(직접노무비) × 율",
|
||||
c.cell("rate_pension", "annual_rates", pension_row) if pension_row else "",
|
||||
],
|
||||
["부가가치세", "(공급가액) × 율", c.cell("rate_vat", None, None)],
|
||||
],
|
||||
}
|
||||
)
|
||||
return sections
|
||||
Reference in New Issue
Block a user