④ 1.2배의 대상을 「호별 산정이 끝난 금액」으로 바꿈 — evaluate 가
버림(밑수 × 율 + 기초액) 을 먼저 맺고 배수를 곱함(종전엔 곱한 뒤 한 번만 버림).
근거 둘이 같은 쪽 — 고시 제4조① 단서 문언 · 골든셋 실증(영월 2024 B 20,330,639 → 20,330,638).
A(배수 1)는 어느 차례로 해도 같은 값.
⚠ 두 실증본이 갈림 — 거창 2025 원본(STmate 출력 아님)은 옛 규칙이라 B 가 1원 위.
목표가 STmate 골든셋 재현이라 STmate 쪽으로 맞춤. 고정값 벌에 갈림을 적어 둠.
⑤ SA(실적수공) 안전관리비 「확인 대기」 → 별도내역. TA 와 같은 자리가 됨.
PENDING_SAFETY 상수를 걷고 파일 머리 문구도 고침. 금액엔 안 닿음.
⑥ 고시 대조를 상시 벌로 세움 — test_b09_safety_notice.py 여덟 자리.
별표1 표를 원문 md 에서 읽어 요율 데이터 16칸 전수 대조(값을 시험에 안 박음) ·
제4조①1·2호 구간 경계 넷 · 단서 A·B·1.2배 · 제2조2호 대상액 구성 · 1.2배 차례 회귀.
잴 시험 먼저 빨강 확인한 뒤 고침. 전체 시험 1911 통과 · 28 건너뜀 · 1 xfail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Prk9BCHG1EMAywk9k8wegA
166 lines
7.7 KiB
Python
166 lines
7.7 KiB
Python
"""원가계산서 4형식 중 나머지 셋 — **서식 차례만** (수공 S · 실적일반 TA · 실적수공 SA).
|
||
|
||
근거: STmate 분석 `12_원가계산_4형식과_RATE표_복원.md` §4~6 (DFM 복원 서식) ·
|
||
`35_형식과_단계의_공통과_차이.md` §1·§2 (공통 사슬 · 형식별 차이 · 12번 정정 반영).
|
||
|
||
⚠ **금액을 안 낸다** — 요율 데이터가 없다(2026-09-14 확인: 수공 세부 경비 율표 `RateB` ·
|
||
표준시장단가 제비율 `RATE_RA`·`RATE_RA1`·실적 안전표 `An_A`). 줄마다 무엇이 막혔는지만 적는다.
|
||
**0 원으로 채우지 않는다** — 0 원은 「없다」가 아니라 「공짜」로 읽혀 총액이 조용히 작아진다.
|
||
⚠ 35번 §5 미확인은 「확인 대기」로 세운다 — 지금 남은 것은 **실적형 이윤 밑수** 하나다.
|
||
실적수공 안전관리비는 2026-09-14 코덱스가 STmate 원문에서 **있음**을 확인해 별도내역으로 내렸다.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
#: 줄 상태 — 화면이 그대로 보인다.
|
||
COMMON = "공통 요율 — 일반형식과 같은 표(형식 전체가 서면 금액이 섬)"
|
||
SEPARATE = "별도내역 — 요율이 아니라 따로 산출한 금액"
|
||
RATE_B = "요율표 미확보 — 수공 세부 경비 율표(STmate RateB)"
|
||
RATE_RA = "요율표 미확보 — 표준시장단가(실적공사비) 제비율(STmate RATE_RA·RATE_RA1)"
|
||
RATE_AN_A = "요율표 미확보 — 실적 산업안전보건관리비 표(STmate An_A·An_A1)"
|
||
TOTAL = "합계 — 위 줄이 다 서야 섬"
|
||
PENDING_PROFIT = "확인 대기 — 실적형 이윤 밑수가 복원식과 화면 문구가 충돌(35번 §5-1)"
|
||
DIRECT = "설계내역서 합계에서 옴"
|
||
|
||
_SUGONG_EXPENSES = (
|
||
"수도광열비",
|
||
"복리후생비",
|
||
"소모품·사무용품비",
|
||
"여비·교통비·통신비",
|
||
"세금·공과금",
|
||
"폐기물처리비",
|
||
"도서인쇄비",
|
||
"지급수수료",
|
||
)
|
||
|
||
FORM_LABELS = {
|
||
"general": "일반형식",
|
||
"sugong": "수공형식",
|
||
"actual_general": "실적일반",
|
||
"actual_sugong": "실적수공",
|
||
}
|
||
|
||
|
||
def _r(mark: str, label: str, formula: str, status: str, level: int) -> dict[str, Any]:
|
||
return {
|
||
"key": "",
|
||
"mark": mark,
|
||
"label": label,
|
||
"level": level,
|
||
"formula": formula,
|
||
"rate_percent": None,
|
||
"amount_krw": None,
|
||
"note": status,
|
||
"total": level == 0,
|
||
"blocked": status not in (COMMON, DIRECT),
|
||
}
|
||
|
||
|
||
def _sugong() -> list[dict[str, Any]]:
|
||
"""수공 S — 12번 §4.1."""
|
||
rows = [
|
||
_r("1.", "순공사원가(가+나+다)", "", TOTAL, 0),
|
||
_r("가.", "순공사비", "", DIRECT, 1),
|
||
_r("나.", "간접노무비", "직접노무비 × 율", COMMON, 1),
|
||
_r("다.", "경 비", "", TOTAL, 1),
|
||
_r("1)", "산재보험료", "(직접노무비+간접노무비) × 율", COMMON, 2),
|
||
_r("2)", "고용보험료", "(직접노무비+간접노무비) × 율", COMMON, 2),
|
||
_r("3)", "건강보험료", "직접노무비 × 율", COMMON, 2),
|
||
_r("4)", "장기요양보험료", "건강보험료 × 율", COMMON, 2),
|
||
_r("5)", "연금보험료", "직접노무비 × 율", COMMON, 2),
|
||
_r("6)", "퇴직공제부금비", "직접노무비 × 율", COMMON, 2),
|
||
_r("7)", "산업안전보건관리비", "(재료비+직접노무비+도급자관급자재) × 율", COMMON, 2),
|
||
_r("8)", "안전관리비", "", SEPARATE, 2),
|
||
_r("9)", "품질관리비", "", SEPARATE, 2),
|
||
]
|
||
rows += [
|
||
_r(f"{10 + i})", name, "(직노·노무·재료 중 고른 밑수) × 율", RATE_B, 2)
|
||
for i, name in enumerate(_SUGONG_EXPENSES)
|
||
]
|
||
rows += [
|
||
_r("18)", "공사이행 수수료", "[기초액 + (직공비−기준액) × 율] × 공기", COMMON, 2),
|
||
_r("19)", "하도대금 수수료", "(재료비+직노+산출경비) × 율", COMMON, 2),
|
||
_r("20)", "환 경 보 전 비", "(재료비+직노+산출경비) × 율", COMMON, 2),
|
||
_r("21)", "건설기계 대여금", "(재료비+직노+산출경비) × 율", COMMON, 2),
|
||
_r("22)", "석면분담금", "(직노+간노) × 율", COMMON, 2),
|
||
_r("23)", "임금채권부담금", "(직노+간노) × 율", COMMON, 2),
|
||
_r(
|
||
"2.",
|
||
"일반관리비",
|
||
"(순공사비+간접노무비+경비) × 율 — 밑수에 수공 경비가 들어 미산출",
|
||
RATE_B,
|
||
1,
|
||
),
|
||
_r("3.", "이 윤", "(순공사원가+일반관리비−재료비) × 율 — 밑수 미산출", RATE_B, 1),
|
||
_r("4.", "공급가액", "", TOTAL, 0),
|
||
_r("5.", "도급공사비", "", TOTAL, 0),
|
||
_r("6.", "총공사비", "", TOTAL, 0),
|
||
]
|
||
return rows
|
||
|
||
|
||
def _actual(sugong: bool) -> list[dict[str, Any]]:
|
||
"""실적일반 TA(§5 · 35번 정정) / 실적수공 SA(§6)."""
|
||
labor_base = "(직접노무비+간접노무비)" if sugong else "노무비"
|
||
rows = [
|
||
_r("1.", "직접공사비", "", DIRECT, 0),
|
||
_r("2.", "간접공사비", "", TOTAL, 0),
|
||
_r("1)", "간접노무비", "직접노무비 × 율", RATE_RA, 1),
|
||
_r("2)", "산재보험료", f"{labor_base} × 율", COMMON, 1),
|
||
_r("3)", "고용보험료", f"{labor_base} × 율", COMMON, 1),
|
||
_r("4)", "건강보험료", "직접노무비 × 율", COMMON, 1),
|
||
_r("5)", "장기요양보험료", "건강보험료 × 율", COMMON, 1),
|
||
_r("6)", "연금보험료", "직접노무비 × 율", COMMON, 1),
|
||
_r("7)", "퇴직공제부금비", "직접노무비 × 율", COMMON, 1),
|
||
_r("8)", "산업안전보건관리비", "(직접공사비+도급자관급자재) × 율", RATE_AN_A, 1),
|
||
# TA·SA 둘 다 별도내역 — SA 도 있음이 확인됨(2026-09-14 코덱스, 35번 §5-2 풀림).
|
||
_r("9)", "안전관리비", "", SEPARATE, 1),
|
||
_r("10)", "품질관리비", "", SEPARATE, 1),
|
||
]
|
||
if sugong:
|
||
rows += [
|
||
_r(f"{11 + i})", name, "(직노·노무·재료 중 고른 밑수) × 율", RATE_B, 1)
|
||
for i, name in enumerate(_SUGONG_EXPENSES)
|
||
]
|
||
guarantee_base = "(재료비+직노+산출경비)"
|
||
tail = 19
|
||
else:
|
||
rows.append(_r("11)", "기타경비", "직접공사비 × 율", RATE_RA, 1))
|
||
guarantee_base = "직접공사비"
|
||
tail = 12
|
||
rows += [
|
||
_r(f"{tail})", "공사이행 수수료", "[기초액 + (직공비−기준액) × 율] × 공기", COMMON, 1),
|
||
_r(f"{tail + 1})", "하도대금 수수료", f"{guarantee_base} × 율", COMMON, 1),
|
||
_r(f"{tail + 2})", "환 경 보 전 비", "(재료비+직노+산출경비) × 율", COMMON, 1),
|
||
_r(f"{tail + 3})", "건설기계 대여금", f"{guarantee_base} × 율", COMMON, 1),
|
||
_r(f"{tail + 4})", "석면분담금", f"{labor_base} × 율", COMMON, 1),
|
||
_r(f"{tail + 5})", "임금채권부담금", f"{labor_base} × 율", COMMON, 1),
|
||
_r(
|
||
"3.",
|
||
"일반관리비",
|
||
"(직접공사비+간접노무비) × 율"
|
||
if sugong
|
||
else "(직접공사비+간접공사비+관리품목자재) × 율",
|
||
RATE_RA,
|
||
0,
|
||
),
|
||
_r("4.", "이 윤", "", PENDING_PROFIT, 0),
|
||
_r("5.", "공급가액", "", TOTAL, 0),
|
||
_r("6.", "도급공사비", "", TOTAL, 0),
|
||
_r("7.", "총공사비", "", TOTAL, 0),
|
||
]
|
||
return rows
|
||
|
||
|
||
def form_rows(form: str) -> list[dict[str, Any]]:
|
||
"""형식별 서식 줄(금액 없음). 일반형식은 엔진이 셈하므로 여기 없음."""
|
||
if form == "sugong":
|
||
return _sugong()
|
||
if form == "actual_general":
|
||
return _actual(sugong=False)
|
||
if form == "actual_sugong":
|
||
return _actual(sugong=True)
|
||
raise ValueError(f"서식이 없는 형식입니다: {form}")
|