Files
Aislo/resources/tester/test_793_2_san_emit.py
T
eomsangdonandClaude Sonnet 5.5 702ee2a30d test(827-5): 전부터 깨진 시험 열 개 가름 — 일곱 고침 · 셋 코드 몫(㉰) xfail 표시
- ㉮ 낡음(일곱): 797-1 유가·전력 기본값(814-1 전국평균 · 일반용(갑)Ⅰ 저압 자동 이음) · 797-1 안쪽 기계 줄 = 값묶음 한 줄(814-1 세 줄 묶기) · 793-2 san_emit 글줄 노·재·경 셋 = 엔진 줄 하나(합으로 맞댐) 다섯 · 778-5 표 X 칸(기계 자료 좋아짐 187→188 · 못 이음 2→1) = 수 박지 않고 바닥값(이어짐 이상 · 고를 것 이하)으로
- ㉰ 코드 몫(셋): 793-2 가져온 식 검산(M02_Sangeun_Imported._compare)이 기계 세 줄 묶음에서 같음 False 둘 · 778-5 자재 어닐링철선(결속선) 다섯 줄 827-2 뒤 이어짐→고를 것 하나 — xfail(strict) 로 표시 · 코드 · 금액 그대로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 14:37:24 +09:00

231 lines
10 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.
"""793-2a 지킴 — 경쟁사 산근 → 우리 엔진 로직 줄 꼴(`M02_San_Emit`) · 엔진 셈 = 엑셀.
STmate 233 블록을 로직 JSON 으로(EST 엑셀 = 812-1 엑셀 읽기기 → 원파일 길 · `test_812_1` 이 지킴) 내보내 `master_formula.run` →
`master_cut.engine` 으로 셈 — 노 · 재 · 경 · 계 = 엑셀(STmate 계 꼬리 · EST 총괄표) =
참조 평가기(`M02_San_Eval`) · 줄마다(STmate 금액 줄 · EST 돈 · 값 줄) · 계표시(단계) 같음.
단가 전파 견본 = `_분석_649-G2` 4 장(보통인부 → 180,000) · 계약단가(낙찰률)는 B09 몫 — 엔진 계 ×
낙찰률 원 버림 = 엑셀 머리만 확인. 엑셀 = `helper_m02_hopyo_golden.FILES`(읽기만 · 없으면 skip).
"""
from __future__ import annotations
import importlib
import math
from functools import lru_cache
import helper_m02_hopyo_golden as golden
import pytest
from M01_MasterData import M01_MasterData_Store as m01 # noqa: F401 — scripts 길을 둠
from M02_MasterTemplete import M02_San_Book as book
from M02_MasterTemplete import M02_San_Emit as em
from M02_MasterTemplete import M02_San_Eval as ev
from M02_MasterTemplete import M02_San_Stmate as stm
mf = importlib.import_module("master_formula")
mcut = importlib.import_module("master_cut")
ms = importlib.import_module("master_formula_sum")
STMATE = { # 파일: (블록, 금액 줄, 계표시 줄, 끝전)
"봉화": (62, 349, 184, "비목버림"),
"동부청영월": (69, 411, 230, "합버림_큰나머지"),
"울진대흥": (47, 261, 128, "비목버림"),
"영덕계류": (15, 86, 41, "비목버림"),
"울진소광": (40, 208, 112, "비목버림"),
}
K = ("노무비", "재료비", "경비", "계")
LABOR = {"종류": "노임", "이름": "보통인부", "규격": "", "열쇠비목": "L"}
def engine(out: dict) -> dict:
"""내보낸 로직 → 엔진 셈 — 단가 줄은 SK 서버처럼 값을 얹음(요소 대신 값묶음 {비목: 단가})."""
logic = {**out["로직"], "키": "GX990001"}
items = []
for item in logic["호표"]:
one = {k: v for k, v in item.items() if k not in ("이음", "자원", "단가", "규격")}
if "단가" in item:
money = dict.fromkeys(K[:3], 0) | {item["비목"]: item["단가"]}
one |= {"종류": "로직", "값묶음": money}
del one["비목"]
items.append(one | {"요소": None})
logic["호표"] = items
master = mf.Master({"로직.json": {"그룹": "로직", "줄": [logic]}})
return mcut.engine(master, logic["키"], mf.run(master, logic["키"], {}))
def hard(out: dict) -> list[str]:
"""못 옮김 표지만(「참고 · …」 사람 확인 표지 · 797-1a 는 뺌 — test_797_1a 가 지킴)."""
return [m for m in out["원본"]["표지"] if em.NOTE not in m]
def money(got: dict) -> tuple:
return tuple(int(got[k]) for k in K)
@lru_cache(maxsize=8)
def _load(tag: str) -> dict:
path = golden.FILES[tag]
if not path.exists():
pytest.skip(f"실무 엑셀 없음 {path.name}")
sheets = book.sheets(path.name, path.read_bytes())
return {"블록": book.stmate_blocks(sheets), "단가": book.stmate_prices(sheets)}
def _stmate(tag: str, blk: dict) -> tuple[list, dict]:
nodes = stm.parse_block(blk["몸"])
return nodes, em.stmate(blk, nodes, _load(tag)["단가"], STMATE[tag][3])
def _lines_equal(out: dict, got: dict) -> tuple[list, int, int]:
"""원본 글줄마다 엔진 줄 금액(계) = 엑셀 · 단계 = 계표시 줄 → (틀린 것, 줄 수, 단계 수)."""
lines = {x["이름"]: x for x in got["줄"]}
steps = {x["이름"]: x for x in got.get("단계", [])}
bad, n_line, n_step = [], 0, 0
seen: dict[
tuple, list
] = {} # 814-1 세 줄 묶기 — 기계 노 · 재 · 경 글줄 셋 = 엔진 줄 하나(합으로 맞댐)
for g in out["원본"]["글줄"]:
if g.get("값") is None or not g.get("자리") or g["자리"].startswith("mid:"):
continue
if g["자리"].startswith("단계:"):
n_step += 1
have = float(steps[g["자리"][3:]]["계"])
if abs(have - g["값"]) >= 0.15:
bad.append((g["행"], g["글"].strip(), have, g["값"]))
else:
n_line += 1
seen.setdefault(tuple(g.get("자리들") or [g["자리"]]), []).append(g)
for names, group in seen.items():
have = sum(float(lines[n]["금액"] or 0) for n in names)
want = sum(g["값"] for g in group)
if abs(have - want) >= 0.05 * len(group):
bad.append((group[0]["행"], group[0]["글"].strip(), have, want))
return bad, n_line, n_step
@pytest.mark.parametrize("tag", list(STMATE))
def test_stmate_engine_equals_excel(tag: str) -> None:
data = _load(tag)
blocks, n_lines, n_steps, rule = STMATE[tag]
n, wrong, lines, steps = 0, [], 0, 0
for blk in data["블록"]:
if not any(ln["G"].strip() for ln in blk["몸"]):
continue
n += 1
nodes, out = _stmate(tag, blk)
got = engine(out)
ref = ev.stmate_block(blk, nodes, data["단가"], rule)["계"]
want = out["원본"]["엑셀금액"]
same = money(got) == tuple(round(want[k]) for k in K)
same &= money(got) == (ref["L"], ref["M"], ref["S"], ref["T"])
bad, a, b = _lines_equal(out, got)
bad += ms.check(out["로직"], blk["코드"]) + hard(out)
lines, steps = lines + a, steps + b
if deal := out["원본"].get("계약"): # B09 몫 — 엔진 계 × 낙찰률 원 버림 = 엑셀 머리
cut = [math.floor(round(int(got[k]) * deal["낙찰률"] / 100, 6)) for k in K[:3]]
same &= cut == [deal["금액"][k] for k in K[:3]]
if not same or bad:
wrong.append((blk["코드"], money(got), bad[:3]))
assert (n, wrong) == (blocks, [])
assert (lines, steps) == (n_lines, n_steps)
@pytest.mark.parametrize(
"tag, code, labor, before, after",
[
("봉화", "D00102", "L00007", (34119, 11192, 341), (34697, 11192, 346)),
("동부청영월", "D00003", "L00002", (4764, 310, 397), (5097, 309, 397)),
("울진소광", "D01489", "L00016", (182223, 230359, 8152), (185067, 230359, 8209)),
],
)
def test_stmate_price_propagates(tag, code, labor, before, after) -> None:
blk = next(b for b in _load(tag)["블록"] if b["코드"] == code)
_, out = _stmate(tag, blk)
assert money(engine(out))[:3] == before
assert em.reprice(out["로직"], {"코드": labor}, {"L": 180000}) >= 1
assert money(engine(out))[:3] == after
def test_link_keys_m01() -> None:
"""호표 줄 = 793-2 꼴(종류 · 단가 · 이음 = 1차 줄 판정 M01 키) · 덧칸 자원 · 원본."""
tag = "봉화"
blk = next(b for b in _load(tag)["블록"] if b["코드"] == "D00102")
out = em.stmate(blk, stm.parse_block(blk["몸"]), _load(tag)["단가"], "비목버림", True)
labor = next(x for x in out["로직"]["호표"] if x["이름"] == "보통인부")
assert {k: v for k, v in labor.items() if k != "자원"} == {
"이름": "보통인부",
"규격": "",
"단위": "인",
"수량": "(2 / 50)",
"종류": "인력",
"비목": "노무비",
"단가": 165545,
"이음": "LB000002",
"번지": "A02",
}
assert labor["자원"] == {
"꼴": "STmate",
"갈래": "인력",
"코드": "L00007",
"이름": "보통인부",
"규격": None,
"단위": "인",
"단가": {"L": 165545, "M": 0, "S": 0},
"비목": "L",
"옮김": None,
}
src = out["원본"]
assert {k: src[k] for k in ("시트", "갈래", "코드", "행")} == {
"시트": "단가산출근거",
"갈래": "STmate",
"코드": "D00102",
"행": [2912, 2937],
}
assert src["엑셀금액"] == {"노무비": 34119, "재료비": 11192, "경비": 341, "계": 45652}
assert next(g for g in src["글줄"] if g.get("자리") == "보통인부")["값"] == 6621.8
# ── 엑셀 없이 도는 문법 견본(793-1 `test_stmate_grammar_sample` + 다시 대입 · 뒤 줄) ──
def test_stmate_grammar_sample_engine() -> None:
prices = {
"M00081": {"T": 11468, "L": 0, "M": 11468, "S": 0, "meta": {}, "이름": "합판(내수)"},
"L00007": {"T": 165545, "L": 165545, "M": 0, "S": 0, "meta": {}, "이름": "보통인부"},
"L00099": {"T": 2, "L": 2, "M": 0, "S": 0, "meta": {"UNT": "L%"}, "이름": "공구"},
}
text = [
"'합판(내수) :'&M00081& * 1.03 * 0.327 :=A01",
";;Q = 2.5 / 3 =",
"'보통인부' &L00007& * [Q] / 4 :=A02",
"&PA01M:M& * 11'%' =",
"&PA02L:S& * 1'%' = // 공구손료",
"(=)",
"&L00099& * 2 =",
"(-=)",
"'별도' &M00081& * 2 =",
"Q = 1 / 3 =",
"&L00099& * [Q] =",
"'뒤 인부' &L00007& * 0.01 =",
]
rows = [{"행": i, "G": g} for i, g in enumerate(text)]
nodes = stm.parse_block(rows)
block = {"코드": "D00001", "이름": "견본", "꼬리": [], "몸": rows}
block["머리"] = {"A": "", "B": "", "C": 0, "D": 0, "E": 0, "F": 0}
out = em.stmate(block, nodes, prices, "비목버림")
logic = out["로직"]
assert logic["중간"] == [
{"이름": "q", "식": "(2.5 / 3)", "표시": 2},
{"이름": "q_2", "식": "(1 / 3)", "표시": 2}, # 다시 대입 = 새 이름
]
assert ms.check(logic, "견본") == [] and out["원본"]["표지"] == []
got = engine(out)
lines, tot = ev.run_stmate(nodes, prices)
want = ev.block_total(tot, "비목버림")
assert money(got) == (want["L"], want["M"], want["S"], want["T"])
by = {x["이름"]: x for x in got["줄"]}
assert by["보통인부"]["비목"]["노무비"] == 34350.5 # 165,545 × 0.83 / 4
assert by["공구"]["비목"]["노무비"] == 687 # 노무 누계 × 2 % · (-=) 로 합에서 뺌
assert logic["덧줄"][-1]["식"] == "(노무비 - 줄.'뒤 인부') * ((q_2)) / 100" # 뒤 줄을 뺀 누계
assert (
float(by["공구_2"]["비목"]["노무비"]) == lines[10]["값"]["L"] == 113.3
) # 34,350.5 × 0.33 %