Files
Aislo/resources/tester/test_707_b09_import_rates_prices.py
T
eomsangdonandClaude Opus 5.5 472bf98ba2 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>
2026-10-31 21:27:40 +09:00

175 lines
7.9 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.
"""707-2 원가계산서 요율 · 707-3 기초 단가표 불러오기 — 실무 xlsx 넷(EST 거창 · STmate 영월 · 울진 대흥 ·
울진 소광) · 이음 규칙 · 덮개 지킴(새 판 [업데이트] 뒤).
총액 대조 = 엑셀 비목 합 + 실은 요율 · 손입력으로 우리 식(`compute_total`) → 엑셀 총공사비:
대흥 = 같음 · 영월 = 「국가지점번호판검증수수료」 762,000 원만 다름(우리 표에 줄 없음) ·
거창 = 같음(708-2 퇴직공제부금비 셈 뒤) · 소광 = 산출근거 빈칸 율 넷 · 폐기물처리 줄.
"""
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation import B09_Estimation_Import_Prices as ip # noqa: E402
from B09_Estimation import B09_Estimation_Import_Rates as ir # noqa: E402
from B09_Estimation import B09_Estimation_Store as store # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Base import compute_base # noqa: E402
from B09_Estimation.B09_Estimation_Rates import rate_table # noqa: E402
K = ROOT / "resources" / "knowledge" / "original" / "실무문서"
FILES = {
"거창": "01.산불거창(거창북상병곡)/01.내역서/00.25년_산불_거창(거창,북상,병곡,산8외)내역+수량.xlsx",
"영월": "05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.xlsx",
"대흥": "2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).xlsx",
"소광": "25년 산불진화임도(기번8)(울진소광)/"
"2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).xlsx",
}
def _file(tag):
path = K / FILES[tag]
if not path.is_file():
pytest.skip(f"실무 xlsx 없음 — {tag}")
return path.name, path.read_bytes()
@pytest.fixture(scope="module")
def rates():
return {tag: ir.read_rates(*_file(tag)) for tag in FILES}
def test_707_2_요율_짝(rates):
dh = rates["대흥"]
assert dh["kind"] == "STmate" and rates["거창"]["kind"] == "EST"
assert len(dh["rates"]) == 13 # 율 줄 10 + 일반관리비 · 이윤 · 부가세
assert (dh["rates"]["indirect_labor"], dh["rates"]["safety"], dh["rates"]["profit"]) == (
14.5,
1.86,
15.0,
)
assert dh["estimate"]["safety_base"] == 5349000 # 「x1.86% + 5,349,000」
assert dh["estimate"]["procurement_fee_pct"] == 0.54 # 375,160 ÷ 69,474,220
gc = rates["거창"] # EST 요율 칸 · 건강보험 글 「13.595%」 대신 칸 3.595
assert (gc["rates"]["health"], gc["rates"]["care"], gc["rates"]["retirement"]) == (
3.595,
13.14,
2.3,
)
assert gc["estimate"]["safety_base"] == 0 # 글에 + 기초액 없음
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"])
def test_707_2_총액_대조(rates):
got = {tag: ir.check(r, {}, rate_table()) for tag, r in rates.items()}
assert got["대흥"]["grand"] == got["대흥"]["excel_grand"] == 1201879000
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"]}
assert lines["retirement"]["ours"] == lines["retirement"]["excel"] == 5603907
def test_707_3_단가표_읽기():
counts = {}
for tag in FILES:
got = ip.read_prices(*_file(tag))
counts[tag] = tuple(len(got[k]) for k in ip.KINDS)
assert counts == {"거창": (17, 52, 22), "영월": (16, 55, 29), "대흥": (12, 39, 25),
"소광": (14, 35, 24)} # fmt: skip
BASE = {
"labor": [
{"key": "LB1", "name": "석공", "spec": "", "unit": "인", "price": 268908},
{"key": "LB2", "name": "보통인부", "spec": "", "unit": "인", "price": 172068},
],
"machine": [
{
"key": "EQ1",
"name": "굴착기(무한궤도)",
"spec": "0.7㎥",
"labor": 1,
"material": 2,
"expense": 3,
},
{
"key": "EQ2",
"name": "굴착기(무한궤도)",
"spec": "0.2㎥",
"labor": 1,
"material": 2,
"expense": 3,
},
], # fmt: skip
"compare": [
{"key": "MO1", "name": "휘발유", "spec": "전국평균", "unit": "L", "applied": 1863.25},
{"key": "MT1", "name": "레미콘", "spec": "25-18-80", "unit": "㎥", "applied": 92330},
],
}
def _m(name, spec, n):
return {"name": name, "spec": spec, "unit": "hr", "labor": n, "material": n, "expense": n}
READ = {
"labor": [
{"name": "석 공", "spec": "", "unit": "인", "price": 258935},
{"name": "보통인부", "spec": "", "unit": "인", "price": 172068}, # 책과 같음
{"name": "벌 목 부", "spec": "", "unit": "인", "price": 253243},
],
"machine": [
_m("굴삭기(무한궤도)", "0.7㎥", 10),
_m("굴삭기(타이어형)", "0.7㎥", 11), # 타이어 = 다른 기계
_m("굴삭기+브레이카", "0.7㎥", 12),
_m("굴삭기(무한궤도)", "0.2㎥", 13),
_m("굴삭기(무한궤도)", "0.2㎥", 14), # 같은 책 줄에 둘 = 못 고름
],
"material": [
{"name": "휘 발 유", "spec": "", "unit": "ℓ", "price": 1473}, # 이름 · 단위(ℓ = L)
{"name": "레미콘(관급)", "spec": "25-18-80", "unit": "m3", "price": 92330},
],
}
def test_707_3_이음_규칙():
user = {"labor_cover": {"LB2": 1}, "materials": {"MO1": {"pick": "유가", "supply": "관급"}}}
out = ip.propose(READ, BASE, user)
patch = out["patch"]
assert patch["labor_cover"] == {"LB1": 258935} # 같은 값 = 옛 덮개 걷음
assert patch["machine_cover"] == {"EQ1": {"labor": 10, "material": 10, "expense": 10}}
assert patch["materials"]["MO1"] == {"supply": "관급", "applied": 1473} # pick 뺌
assert "MT1" not in patch["materials"] # 「(관급)」 이름 다름 = 짝 없음
assert "굴삭기(무한궤도) 0.2㎥(짝 여럿)" in out["missing"]["machine"]
assert out["counts"]["machine"] == {"read": 5, "linked": 1, "covered": 1, "missing": 4,
"book": 2} # fmt: skip
assert out["counts"]["labor"]["linked"] == 2 and out["missing"]["labor"] == ["벌 목 부"]
def test_707_3_덮개_새_판_뒤_남음(tmp_path):
"""[업데이트] = 단가 책만 새로 굳힘 — 사용자 값의 노임 · 기계 덮개는 그대로 · 셈은 덮개."""
cover = {"labor_cover": {"LB1": 258935, "": None},
"machine_cover": {"EQ1": {"labor": 10, "material": 10, "expense": 10},
"EQ2": {"labor": 1}}} # fmt: skip
saved = store.write_user(tmp_path, cover)
assert saved["labor_cover"] == {"LB1": 258935} # 빈 값 버림
assert saved["machine_cover"] == {"EQ1": {"labor": 10, "material": 10, "expense": 10}}
for price in (268908, 280000): # 옛 판 → 새 판 책
store.write_book(tmp_path, {"rows": {"LB1": {"key": "LB1", "price": price}}})
user = store.read_user(tmp_path)
assert user["labor_cover"] == saved["labor_cover"]
inputs = {"labor": [{"key": "LB1", "name": "석공", "price": price}]}
row = compute_base(inputs, user)["labor"][0]
assert (row["price"], row["book"], row["covered"]) == (258935, price, True)