- 새 B09_Estimation_Labor_Rule: 새 의미 로직만 대가 블록 줄 `place`(줄 자리) · `out`(합밖 · 제외 단계) · 덧줄 = 수 하나 × 밑 합 꼴이면 `rows:` % 줄(줄.'이름' · 앞 덧줄 · 번지 · 단계 · 비목 합 · 793-2a 괄호 꼴) · 이름 없는 수 식 덧줄 = 고정 줄 · 블록 `rule` {끝전, 조정, 끝수} · 다른 꼴 = 빈 줄 그대로(틀린 값 대신 빈칸)
- Labor_Source: 품셈 블록에 표지 · 단가산출 블록이 줄을 바꿔도 표지 이어 받음(657 줄)
- Calc_Labor .py · .ts 짝: 줄 금액 `cut`(place) · `out` 줄은 비목 합 · part: 밑에서 뺌 · 블록 비목 합 `settle`(큰나머지 → 끝수 → 조정 → 원 버림 · master_cut.settle 차례) · 옛 블록 = 표지 없음 = 전과 같음
- 산근 글 로직 부르기 줄(중기 세 줄 · 하위 산근) 금액도 줄 자리 규칙(792-3 남은 것)
- test_792_3: 다섯째 길 B09 대가(SC → 굳힌 SK 수량 1 → 내역 단가 · 파이썬 = TS 거울) · 실무 봉화 D00102 · 북부청임도 18 을 793-2 길(임시 층)로 가져온 식 SK → 내역 단가 = 엑셀
- test_712_3 낡은 기대: 표 비율 0.78 → 단위 수 1,601 이상 · 0.76 — 782-1 · 782-2 · 782-3 계수 표 94 가 로직에 붙음(그중 단위 기준 25 · 시공능력 · 적용기준 계수 = 단위 기준 없음) · 712-3 때 2,023 중 1,601 그대로 · 마스터 자료 안 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
230 lines
10 KiB
Python
230 lines
10 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
|
"""792-3 · 792-4 엔진 새 의미 다섯 길 지킴 시험 — 같은 로직 JSON 이
|
|
① 엔진 직접(`master_cut.engine`) ·
|
|
② M02 단가산출 SK 셈(`M02_Sangeun_Calc.run`) · ③ 일위대가 SC 한 줄(수량 1 · `M02_Labor_Doc.calc`) ·
|
|
④ B09 산근 풀이(`B09_Estimation_Sangeun.Solver` · 단가 = 같은 값의 B09 목록표) ·
|
|
⑤ B09 일위대가 대가 블록(SC → 굳힌 SK 참조 수량 1 · `Labor_Source.Builder` → `Calc_Labor` 파이썬 ·
|
|
TS 짝 · 내역 단가 `daega_price`)에서 같은 금액.
|
|
실무 견본(봉화 D00102 · 북부청임도 18)은 793-2 길(가져온 식 SK · 임시 층)로 만들어 ⑤ = 엑셀.
|
|
|
|
견본 = 791-1 실무 손 옮김 넷(봉화 D00102 · 울진소광 D01489 · 동부청영월 D00003 · 북부청임도 18 =
|
|
엑셀 금액) + 합밖 · 제외 · 조정 단위 로직. 켜쌓기율 · 별도계상 뒤 다시 버림(`master_cut.plain`)도
|
|
엔진 셈법(줄 자리 · 끝전 · 조정)을 지킴. 옛 로직(새 칸 없음) 전후 같음 = `test_791_1` M01 지문.
|
|
"""
|
|
|
|
import copy
|
|
import importlib
|
|
import json
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
import resources.tester.test_791_1_engine_semantics as t
|
|
from B09_Estimation import B09_Estimation_Calc_Labor as dcalc
|
|
from B09_Estimation import B09_Estimation_Labor_Source as dsrc
|
|
from B09_Estimation import B09_Estimation_Sangeun as sg
|
|
from M02_MasterTemplete import M02_Item_Sangeun as item_sk
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as sc
|
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
|
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 임시 층
|
|
|
|
mcut = importlib.import_module("master_cut")
|
|
D = Decimal
|
|
COSTS = ("노무비", "재료비", "경비", "계")
|
|
PART = {"labor": "노무비", "material": "재료비", "expense": "경비", "sum": "계"}
|
|
EQ = "EQ990001"
|
|
|
|
|
|
def ms_uses(row) -> bool:
|
|
return importlib.import_module("master_formula_sum").uses(row)
|
|
|
|
|
|
def _units():
|
|
"""합밖 · 제외 단계 · 조정 · 큰나머지 · 줄 자리를 한 로직에(791-1 단위 견본 바탕)."""
|
|
master = t._base(
|
|
덧줄=[
|
|
{"이름": "공구손료", "식": "단계.'인부 소계'.노무비 * 3 / 100", "비목": "경비"},
|
|
{"이름": "잡", "식": "번지.A01.재료비 * 1.5 / 100", "비목": "재료비"},
|
|
{"이름": "덧", "식": "0.6", "비목": "노무비", "자리": {"방법": "올림", "자리": 0}},
|
|
],
|
|
단계=[
|
|
{"이름": "자재 소계", "단": 1, "줄": ["갑", "을"]},
|
|
{"이름": "인부 소계", "단": 1, "줄": ["보통", "특별"]},
|
|
{"이름": "경비 소계", "단": 1, "줄": ["공구손료"], "제외": True},
|
|
],
|
|
끝전="큰나머지",
|
|
조정={"배율": {"재료비": D("0.9")}},
|
|
)
|
|
row = master.logic("GX990001")
|
|
row["호표"][1]["합밖"] = True
|
|
return master, row, None
|
|
|
|
|
|
CASES = {"봉화": t.봉화, "소광": t.소광, "영월": t.영월, "임도": t.임도, "단위": _units}
|
|
|
|
|
|
def _key(row) -> str:
|
|
return row["키"]
|
|
|
|
|
|
def _engine(master, row) -> dict:
|
|
got = t._calc(master, _key(row))
|
|
return {k: got[k] for k in COSTS}
|
|
|
|
|
|
def _sk(master, row) -> dict:
|
|
doc = {"키": "SK-990001", "이름": "시험", "단위": "식", "바탕": {"로직": _key(row)}}
|
|
got = sc.run(sd.clean(doc, None, master), master)
|
|
return {k: got["기준"][k] for k in COSTS}
|
|
|
|
|
|
def _sc(master, row) -> dict:
|
|
mats = {
|
|
str(n): {"별도계상": "직접계산"} for n, x in enumerate(row["호표"]) if x["종류"] == "재료"
|
|
}
|
|
line = {"로직": _key(row), "입력값": {}, "수량": {"손": 1}, "자재표시": mats}
|
|
got = labor.calc({"담은로직": [line], "자재": []}, {}, whole=master, pills=[])
|
|
return {k: got["반영합"][k] for k in COSTS}
|
|
|
|
|
|
def _b09_master(master, row):
|
|
"""B09 = 기계 줄 단가를 중기목록표에서 — 기계경비 로직이 부르는 EQ 키를 잡게
|
|
시간당 로직에 한 줄."""
|
|
hour = copy.deepcopy(master.index["GX"].get("GX990009") or {})
|
|
if hour:
|
|
hour["호표"] = [t._mat(EQ, "1", "기계", 비목="경비")]
|
|
master.index["GX"]["GX990009"] = hour
|
|
master.index["EQ"] = {EQ: {"키": EQ, "이름": "시험 기계", "값": D(0)}}
|
|
return master
|
|
|
|
|
|
def _b09(build, row) -> dict:
|
|
master, row, _ = build()
|
|
master = _b09_master(master, row)
|
|
files = master.index
|
|
machine = next((x["값묶음"] for x in row["호표"] if "값묶음" in x), None)
|
|
sheets = {
|
|
"labor": [{"key": k, "price": float(r["값"])} for k, r in files.get("LB", {}).items()],
|
|
"compare": [
|
|
{"key": k, "applied": float(r["물가자료"])} for k, r in files.get("MT", {}).items()
|
|
],
|
|
"machine": [{"key": EQ, **{p: float(machine[c]) for p, c in PART.items() if c in machine}}]
|
|
if machine
|
|
else [],
|
|
}
|
|
block = sg.Solver(sheets).add(master, {"code": _key(row), "given": {}}, None)
|
|
assert block["why"] is None, block["why"]
|
|
texts = [x for ln in block["lines"] for x in ln.get("text") or []]
|
|
assert not [x for x in texts if "못" in x or "빈칸" in x], texts # 산근 글도 같은 셈
|
|
assert block["foot"].endswith(f"= {block['sum']:,.0f}"), block["foot"]
|
|
return {PART[p]: D(str(block[p])) for p in PART}
|
|
|
|
|
|
OWNER = "pipe@100.0" # `test_145_2._ts` 가 내역 단가를 읽는 구조물 id
|
|
|
|
|
|
def _daega(whole, sk: dict, sheets: dict) -> dict:
|
|
"""⑤ SC(굳힌 SK 참조 수량 1) → 대가 블록 → 파이썬 셈 = TS 짝 → 내역 단가."""
|
|
from resources.tester.test_145_2_b09_labor_calc import _same, _ts
|
|
|
|
sk_ref, sc_ref = {"층": "project", "키": "SK-990001"}, {"층": "project", "키": "SC-990001"}
|
|
sc = {"종류": refs.TITLE["precombo"], "이름": "SC", "단위": "식",
|
|
"담은로직": [{"참조": sk_ref, "수량": {"손": 1}}]} # fmt: skip
|
|
build = dsrc.Builder()
|
|
bid = build.precombo(whole, sc_ref, sc, {refs.ref_id(sk_ref): sk, refs.ref_id(sc_ref): sc})
|
|
daega = dsrc._plain({"blocks": list(build.blocks.values()), "owners": {OWNER: bid}})
|
|
py = dcalc.compute_labor(daega, sheets)
|
|
ts = _ts(daega, sheets, {})
|
|
assert _same(json.loads(json.dumps(py, ensure_ascii=False)), ts["out"]) # 거울
|
|
price = dcalc.daega_price(py, OWNER)
|
|
assert _same(price, ts["price"])
|
|
return {PART[p]: D(str(price[p])) for p in PART}
|
|
|
|
|
|
def _b09_daega(build) -> dict:
|
|
master, row, _ = build()
|
|
sk = {"종류": sd.TITLE, "키": "SK-990001", "이름": "시험", "단위": "식",
|
|
"바탕": {"로직": _key(row)}, "입력값": {}, "덮개": {}, "더한줄": [], "뺀줄": [],
|
|
item_sk.ROW: row, "정체": []} # fmt: skip
|
|
files = master.index
|
|
sheets = {
|
|
"labor": [{"key": k, "price": float(r["값"])} for k, r in files.get("LB", {}).items()],
|
|
"material": [
|
|
{"key": k, "price": float(r["물가자료"])} for k, r in files.get("MT", {}).items()
|
|
],
|
|
"machine": [],
|
|
"gaps": [],
|
|
}
|
|
return _daega(master, sk, sheets)
|
|
|
|
|
|
@pytest.mark.parametrize("name", list(CASES))
|
|
def test_다섯_길_같은_금액(name):
|
|
build = CASES[name]
|
|
master, row, want = build()
|
|
one = _engine(master, row)
|
|
if want is not None:
|
|
assert one == want # 엑셀 금액
|
|
assert _sk(*build()[:2]) == one
|
|
assert _sc(*build()[:2]) == one
|
|
assert _b09(build, row) == one
|
|
assert _b09_daega(build) == one # ⑤ B09 일위대가 대가 블록(792-4)
|
|
|
|
|
|
REAL = [("봉화", "D00102"), ("북부청임도", 18)]
|
|
|
|
|
|
@pytest.mark.parametrize(("tag", "code"), REAL)
|
|
def test_실무_가져온_식_SK_대가_내역_엑셀(tag, code, world):
|
|
"""793-2 길 — 경쟁사 블록 → 가져온 식 SK(임시 층) → 굳힘 → SC 줄 → B09 대가 →
|
|
내역 단가 = 엑셀."""
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
|
from resources.tester.test_793_2_sangeun_imported import _emitted
|
|
|
|
out = _emitted(tag, code)
|
|
with store.at(world["personal"]):
|
|
made = imp.create(out["로직"], out["원본"], f"{tag} {code}")
|
|
doc = store.read("sangeun", made["이름"])["문서"]
|
|
whole = m01.loaded()[1]
|
|
rid = "project||SK-990001"
|
|
frozen = item_sk.freeze({rid: sd.clean(doc, None, whole)})[rid]
|
|
key, row = frozen["바탕"]["로직"], json.loads(json.dumps(frozen[item_sk.ROW]))
|
|
assert ms_uses(row) # 새 의미 로직(줄 자리 · 번지 · 단계)
|
|
books = {"labor": [], "material": [], "machine": [], "gaps": []} # 엑셀 단가 = 고정 줄
|
|
got = _daega(sc._Over(whole, key, row), frozen, books)
|
|
excel = {k: D(str(round(v))) for k, v in out["원본"]["엑셀금액"].items()}
|
|
assert got == excel
|
|
|
|
|
|
def test_단위_로직_새_의미가_금액을_바꿈():
|
|
master, row, _ = _units()
|
|
got = _engine(master, row)
|
|
plain = copy.deepcopy(row)
|
|
for k in ("끝전", "조정", "단계"):
|
|
plain.pop(k)
|
|
for x in (*plain["호표"], *plain["덧줄"]):
|
|
for k in ("합밖", "자리", "번지"):
|
|
x.pop(k, None)
|
|
assert got != _engine(t._master([plain], t.LAB, t.MAT), plain) # 시험이 새 길을 거침
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["영월", "단위"])
|
|
def test_다시_버림도_셈법_지킴(name):
|
|
"""켜쌓기율 · 별도계상 뒤 다시 버림(`plain`) — 셈법이 있으면
|
|
줄 자리 · 합밖 · 끝전 · 조정 그대로."""
|
|
master, row, _ = CASES[name]()
|
|
out = labor.run_line(master, _key(row), {})
|
|
assert "셈법" in out and out["단계"]
|
|
again = mcut.plain(out)
|
|
assert again["기준"] == out["기준"]
|
|
assert [x["계"] for x in again["단계"]] == [x["계"] for x in out["단계"]]
|
|
old = mcut.plain({k: v for k, v in out.items() if k != "셈법"})
|
|
if name == "영월":
|
|
assert (
|
|
old["기준"]["계"] == 5470 and again["기준"]["계"] == 5471
|
|
) # 옛 다시 버림 = 1 원 모자람
|