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
+52 -3
View File
@@ -17,7 +17,7 @@ ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation.B09_Estimation_Calc_Total import compute_total # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Total import CURRENT_ONLY, compute_total # noqa: E402
from B09_Estimation.B09_Estimation_Rates import rate_candidates # noqa: E402
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
@@ -60,6 +60,7 @@ EXCEL_LINES = { # 총괄 D49~D58 · D60 · D62 · D64 · D66
"profit": 108109955,
"vat": 102911727,
"supply": 69850000,
**{key: 0 for key in CURRENT_ONLY}, # 708-2 현행 비목 — 엑셀(기번3)에 없음 = 요율 빈칸 = 0
}
EXCEL_SUBTOTALS = { # 총괄 D59 · D61 · D63 · D65 · D67
"net_cost": 868874829,
@@ -103,7 +104,8 @@ def test_간접재료비_작업설은_직접재료비만_바꿈():
def test_빈칸_규칙():
blank = compute_total(SUMS, {})
assert all(v is None for k, v in blank["lines"].items() if k != "supply")
assert all(v is None for k, v in blank["lines"].items() if k not in ("supply", *CURRENT_ONLY))
assert all(blank["lines"][k] == 0 for k in CURRENT_ONLY) # 708-2 요율 빈칸 = 해당 없음
assert blank["subtotals"]["net_cost"] is None and blank["subtotals"]["grand"] is None
assert blank["lines"]["supply"] is None # 조달수수료율 빈칸
# 요율 하나만 빈칸 — 그 줄 · 소계 빈칸 · 다른 줄은 셈
@@ -157,6 +159,8 @@ def test_거울_TS_가_같은_값():
[{**SUMS, "supply_net": 2_000_000_000}, ESTIMATE],
[{**SUMS, "labor": None}, ESTIMATE],
[SUMS, {**ESTIMATE, "rates": {**RATES, "care": None, "profit": 12.5}}],
[SUMS, CURRENT],
[{**SUMS, "labor": None}, CURRENT],
]
assert _run_ts(cases) == [compute_total(s, e) for s, e in cases]
@@ -208,8 +212,53 @@ console.log(JSON.stringify(out));
) # fmt: skip
assert result.returncode == 0, result.stderr
out = json.loads(result.stdout)
assert out["editable"] == [True, False, True, False, False]
assert out["editable"] == [True, False, True, True, False] # 708-2 퇴직공제 요율 칸
assert out["care"] == 13.14 and out["careGone"] is False
assert out["grandBlank"] is None # 요율 지우면 그 줄 · 소계 빈칸
assert out["base"] == 1000 and out["scrap"] == 300
assert out["link"] == {"tab": "total", "key": "safety"}
CURRENT = { # 708-2 현행 비목 — 율 넷 · 공사이행보증 금액 · 불러온 율 표시
**ESTIMATE,
"rates": {
**RATES,
"retirement": 2.3,
"wage_claim": 0.09,
"asbestos": 0.006,
"subcontract": 0.1,
},
"rates_imported": ["retirement", "safety"],
"performance_fee": 1234567,
}
def test_708_2_현행_비목_다섯():
out = compute_total(SUMS, CURRENT)
ln, total_labor, direct = out["lines"], 346800554 + 50286080, 710359136
assert ln["retirement"] == int(346800554 * 2.3 / 100) # 직접노무비 × 율
assert ln["wage_claim"] == int(total_labor * 0.09 / 100) # (직노 + 간노) × 율
assert ln["asbestos"] == int(total_labor * 0.006 / 100)
assert ln["subcontract"] == int(direct * 0.1 / 100) # (재료 + 직노 + 산출경비) × 율
assert ln["performance"] == 1234567 # 금액 손입력
added = sum(ln[k] for k in CURRENT_ONLY)
assert out["subtotals"]["net_cost"] == EXCEL_SUBTOTALS["net_cost"] + added # 나.소계에 듦
assert out["cost"]["expense_sum"] == 277553778 + added # 경비 묶음 안
assert out["cost"]["retirement"] == ln["retirement"]
assert (
out["rates"]["retirement"]["from"] == "import"
and out["rates"]["safety"]["from"] == "import"
)
assert out["rates"]["wage_claim"]["from"] == "user"
# 요율 빈칸 = 해당 없음(0 · 소계 빈칸 아님) — 옛 공사 금액 그대로
old = compute_total(SUMS, ESTIMATE)
assert all(old["lines"][k] == 0 for k in CURRENT_ONLY)
assert old["subtotals"] == EXCEL_SUBTOTALS
def test_708_2_저장_칸():
from B09_Estimation import B09_Estimation_Store as store
raw = {"estimate": {"performance_fee": 1234567, "rates_imported": ["retirement", 3]}}
got = store.clean_user(raw)["estimate"]
assert got["performance_fee"] == 1234567 and got["rates_imported"] == ["retirement", "3"]
+2 -2
View File
@@ -15,7 +15,7 @@ ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation.B09_Estimation_Calc_Total import compute_total # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Total import CURRENT_ONLY, compute_total # noqa: E402
from B09_Estimation.B09_Estimation_Rates import rate_table # noqa: E402
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
@@ -142,7 +142,7 @@ def test_엑셀_검산_손값이면_그대로():
out = total(SUMS, EXCEL, 2026)
assert out["subtotals"]["grand"] == 1201879000
assert out["lines"]["safety"] == 16586996 and out["lines"]["profit"] == 108109955
assert all(r["from"] == "user" for r in out["rates"].values())
assert all(r["from"] == "user" for k, r in out["rates"].items() if k not in CURRENT_ONLY)
def _node(script: str) -> str:
@@ -3,7 +3,7 @@
총액 대조 = 엑셀 비목 합 + 실은 요율 · 손입력으로 우리 식(`compute_total`) → 엑셀 총공사비:
대흥 = 같음 · 영월 = 「국가지점번호판검증수수료」 762,000 원만 다름(우리 표에 줄 없음) ·
거창 = 퇴직공제부금비(셈 없는 현행 비목)만큼 · 소광 = 산출근거 빈칸 율 넷 · 폐기물처리 줄.
거창 = 같음(708-2 퇴직공제부금비 셈 뒤) · 소광 = 산출근거 빈칸 율 넷 · 폐기물처리 줄.
"""
import sys
@@ -61,7 +61,7 @@ def test_707_2_요율_짝(rates):
2.3,
)
assert gc["estimate"]["safety_base"] == 0 # 글에 + 기초액 없음
assert any("퇴직공제부금비" in n and "셈 없음" in n for n in gc["notes"])
assert gc["notes"] == [] # 708-2 퇴직공제부금비 = 셈(「셈 없음」 없음)
sk = rates["소광"]
assert sum("율 없음" in n for n in sk["notes"]) == 4 # 산출근거 빈칸 — 지금 율 그대로
assert any("페기물처리" in n for n in sk["notes"])
@@ -73,11 +73,11 @@ def test_707_2_총액_대조(rates):
assert got["대흥"]["differ"] == []
assert got["영월"]["excel_grand"] - got["영월"]["grand"] == 762000 # 국가지점번호판 줄 몫
assert set(got["영월"]["differ"]) == {"도급공사비", "총공사비"}
# 708-2 — 퇴직공제부금비 5,603,907(직노 × 2.3 %)가 순공사원가에 듦 → 총공사비 = 엑셀
assert got["거창"]["grand"] == got["거창"]["excel_grand"] == 744653000
assert got["거창"]["differ"] == []
lines = {ln["key"]: ln for ln in got["거창"]["lines"]}
rate_keys = ("indirect_labor", "accident", "employment", "health", "care", "pension")
assert all(lines[k]["same"] for k in (*rate_keys, "safety", "other", "environment"))
assert not lines["overhead"]["same"] # 퇴직공제부금비 5,603,907 이 순공사원가에 없음
assert lines["overhead"]["excel"] - lines["overhead"]["ours"] == 448312 # = 5,603,907 × 8 %
assert lines["retirement"]["ours"] == lines["retirement"]["excel"] == 5603907
def test_707_3_단가표_읽기():