Files
Aislo/B09_Estimation/B09_Estimation_Engine_Cost_Options.py
T
eomsangdonandClaude Opus 5 fb4eb59cdb feat(b09): 원가계산서 기준 입력 10·11·12 — 일반관리비 주/전문 · 절사 · 부가세 방식
- 10 일반관리비: (주)공사/전문공사 = 요율 구간표만 갈림 · 밑수는 형식 칸(일반 = 순공사원가 + 관리품목자재대)
- 11 절사: 총공사비/공급가액 N원 미만 — 고른 때만 이윤 자동보정(총공사비·공급가액 비례 부가세면 ÷1.1 반올림, 실무 봉화 373→339·영월 695→632, 산림조합은 차액 그대로) · 잔차 맞춤
- 12 부가세: 공급가액·재료비·없음·산림조합 형식·산림조합-면세품 (칸 이름은 기성 선택지를 받을 수 있게 문자열)
- 기본값은 종전 계산과 같음 · 자동보정 줄은 「… 절사 자동보정」으로 적음
- 원가계산서 탭: 10·11·12 고르개 · 관리품목 자재대 · 면세품 금액 칸
- 시험: 기본값 불변 · 밑수·표 갈림 · 실무 절사 식 · 끝자리 · 골든셋 초록

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

95 lines
4.8 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.
"""원가계산서 기준 입력 10·11·12 — 일반관리비 주/전문 · 절사 · 부가세 방식 (PLAN 6장 새 절).
`Engine_Cost` 가 700줄에 가까워 **고르는 규칙만** 여기 둔다. 계산 사슬은 엔진 그대로.
근거(2026-09-14 조사 — STmate `TWM_KANJUB` 칸 힌트 · 실무 엑셀 수식)
10 일반관리비 — 식은 그대로, **요율 구간표만** 주공사/전문공사로 갈림
(KJ 「전문공사: 1/10 규모」). 밑수는 **형식마다** 다름(35번 §1) — 일반 T 「순공사원가 +
관리품목자재대」.
11 절사 — 「절사된 금액은 이윤에서 자동보정」(KJ 힌트). 실무 봉화·영월:
차액 = 총공사비 − 천원 미만 버림 · 이윤 − ROUND(차액 ÷ 1.1) 잔차
(부가세가 공급가액 비례라 ÷1.1).
영덕(산림조합-면세품): 차액 그대로 뺌(부가세가 이윤과 무관).
12 부가세 — 「공급가액의 10% / 재료비의 10% / 없음 / 산림조합 형식 (재료비 + 산출경비 의 10%) /
산림조합-면세품 (재료비-면세품 + 산출경비의 10%)」(KJ 힌트). ⚠ 기성 단계는 선택지가 달라
(직접입력 · 공급가액 · 재료비 · 재료비+산출경비) 칸 이름을 문자열로 두어 늘릴 수 있게 함.
⚠ 기본값은 **전부 종전과 같게** — 공급가액 10% · 주공사 · 절사 없음(수동 이윤보정만).
자동 이윤보정은 설계자가 절사 기준을 **고른 때만** 돈다(2026-09-08 「조용히 깎지 않음」).
"""
from __future__ import annotations
from decimal import ROUND_FLOOR, ROUND_HALF_UP, Decimal
_ZERO = Decimal(0)
# ── 10 일반관리비 ─────────────────────────────────────────────────────────
#: 요율 데이터 `rate_overhead` 의 표 이름 — (주)공사 / 전문공사.
OVERHEAD_CLASSES = {
"civil_landscape_industrial": "(주)공사",
"specialty_electric_communication_fire_other": "전문공사",
}
DEFAULT_OVERHEAD_CLASS = "civil_landscape_industrial"
#: 형식별 일반관리비 밑수 — 지금은 일반(T)만. 나머지 셋은 형식이 설 때 한 줄씩.
OVERHEAD_BASE_LABELS = {"general": "순공사원가+관리품목자재대"}
def overhead_base(form: str, net_construction_cost: Decimal, managed_material: Decimal) -> Decimal:
"""형식별 일반관리비 밑수. 모르는 형식은 멈춤 — 다른 형식 밑수로 조용히 세지 않음."""
if form == "general":
return net_construction_cost + managed_material
raise ValueError(f"일반관리비 밑수가 아직 없는 형식입니다: {form}")
# ── 12 부가가치세 ─────────────────────────────────────────────────────────
VAT_MODES = {
"supply": "공급가액",
"material": "재료비",
"none": "없음",
"forest_coop": "재료비+산출경비",
"forest_coop_exempt": "재료비−면세품+산출경비",
}
DEFAULT_VAT_MODE = "supply"
def vat_base(
mode: str,
total_cost: Decimal,
material_cost: Decimal,
direct_expense: Decimal,
tax_exempt_material: Decimal,
) -> Decimal:
"""부가세 밑수. 「없음」은 0. 모르는 방식은 멈춤."""
if mode == "supply":
return total_cost
if mode == "material":
return material_cost
if mode == "none":
return _ZERO
if mode == "forest_coop":
return material_cost + direct_expense
if mode == "forest_coop_exempt":
return material_cost - tax_exempt_material + direct_expense
raise ValueError(f"모르는 부가세 방식입니다: {mode}")
# ── 11 절사 ───────────────────────────────────────────────────────────────
#: 절사 기준 — `grand_total` 총공사비에서 조정 · `supply` 공급가액 자체를 자름. 빈 값 = 안 자름.
CUT_BASES = {"grand_total": "총공사비", "supply": "공급가액"}
CUT_UNITS_KRW = (10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000)
def cut_gap(amount: Decimal, unit: int) -> Decimal:
"""`unit` 원 미만 버림으로 떨어지는 몫."""
unit_decimal = Decimal(unit)
kept = (amount / unit_decimal).quantize(Decimal(1), rounding=ROUND_FLOOR) * unit_decimal
return amount - kept
def profit_cut(gap: Decimal, basis: str, vat_mode: str) -> Decimal:
"""이윤에서 뺄 몫. 총공사비 절사 + 부가세가 공급가액 비례면 ÷1.1 반올림(실무 봉화 373→339)."""
if basis == "grand_total" and vat_mode == "supply":
return (gap / Decimal("1.1")).quantize(Decimal(1), rounding=ROUND_HALF_UP)
return gap