- M02_Labor_Spec 새 파일 — 규격 칸 정리 · 열 끼우기 · 사본 굳히기 · 쓰는 사본 찾기 - settle(파이썬 · TS) 에 col — 그 열 수량이 있는 줄 = 손 자리 · 규격 없음 = 그대로 - 계산값 규격합 = 열마다 반영합(고르기 창 열 단추) - 거울 시험 — 규격 열 · 모르는 열 = 기본 · 꼴 틀림 400 · 열 없는 옛 문서 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
153 lines
5.3 KiB
Python
153 lines
5.3 KiB
Python
"""일위대가 수량 · 반영 · 합 거울 시험 — `M02_Labor_Doc.settle`(파이썬) ↔
|
|
`M02_MasterTemplete_Labor_Calc.ts` `settle`(TS · Node) 가 같은 답(CLAUDE.md 5장 ①).
|
|
|
|
기준은 서버가 한 번 셈(`calc`) · 그 답에 다른 알약 값을 넣어 둘이 같은지 봄.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
TEN, THOUSAND, PLAIN, MAT = "GC000421", "GF000017", "GC000422", "MT000008"
|
|
|
|
|
|
def _ts(answer: dict, doc: dict, values: dict) -> dict:
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_m02_labor_calc.cjs")],
|
|
input=json.dumps({"답": answer, "문서": doc, "값": values}, ensure_ascii=False),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def _same(a, b) -> None:
|
|
if isinstance(a, dict):
|
|
assert set(a) == set(b)
|
|
for k in a:
|
|
_same(a[k], b[k])
|
|
elif isinstance(a, list):
|
|
assert len(a) == len(b)
|
|
for x, y in zip(a, b):
|
|
_same(x, y)
|
|
elif isinstance(a, (int, float)) and not isinstance(a, bool):
|
|
assert b == pytest.approx(a, rel=1e-9)
|
|
else:
|
|
assert a == b
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"values",
|
|
[
|
|
{}, # 수량 없음 — 반영 null · 손만
|
|
{"사석 수상고르기|㎡": 2.5, f"자재|{MAT}": "3"},
|
|
{"사석 수상고르기|㎡": "1,200", "나무식재|본": 7, f"자재|{MAT}": "글"}, # 쉼표 · 수 아님
|
|
],
|
|
)
|
|
def test_파이썬과_TS_가_같은_답(values):
|
|
doc = labor.clean(
|
|
{
|
|
"열": "rv",
|
|
"담은로직": [
|
|
{"로직": TEN, "수량": None},
|
|
{"로직": THOUSAND, "수량": {"손": 4}},
|
|
{"로직": PLAIN, "수량": None},
|
|
],
|
|
"자재": [{"코드": MAT, "수량": None}],
|
|
}
|
|
)
|
|
answer = labor.calc(doc, {})
|
|
keys = {line["알약"] for line in answer["줄"]}
|
|
assert {"사석 수상고르기|㎡", "나무식재|본"} <= keys # 알약 키가 시험 값과 맞음
|
|
py = labor.settle(answer, doc, values)
|
|
_same(py, _ts(answer, doc, values))
|
|
assert py == labor.calc(doc, values) # settle 만 다시 = 처음부터 다시
|
|
|
|
|
|
# ── 규격 열(704-4) — 열마다 줄 수량 · 열 없는 옛 문서는 위 시험 그대로 ──
|
|
SPEC_DOC = {
|
|
"열": "rv",
|
|
"담은로직": [
|
|
{"로직": TEN, "수량": None},
|
|
{"로직": THOUSAND, "수량": {"손": 4}},
|
|
{"로직": PLAIN, "수량": None},
|
|
],
|
|
"자재": [{"코드": MAT, "수량": {"손": 1}, "별도계상": "직접계산"}],
|
|
"규격": {
|
|
"이름": "H",
|
|
"열": ["1.5", "2.0", "2.5"],
|
|
"기본": "2.0",
|
|
"수량": {
|
|
THOUSAND: {"1.5": 3, "2.0": "4.5", "2.5": 6},
|
|
MAT: {"1.5": 0.8, "2.5": 1.4}, # 2.0 빈 열 = 공통 손 1
|
|
"없는줄": {"1.5": 9}, # 버림
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("col", [None, "1.5", "2.0", "2.5", "모름"])
|
|
def test_규격_열_파이썬과_TS_가_같은_답(col):
|
|
doc = labor.clean(SPEC_DOC)
|
|
assert doc["규격"]["수량"] == {
|
|
THOUSAND: {"1.5": 3, "2.0": 4.5, "2.5": 6},
|
|
MAT: {"1.5": 0.8, "2.5": 1.4},
|
|
}
|
|
answer = labor.calc(doc, {})
|
|
values = {"사석 수상고르기|㎡": 2.5}
|
|
py = labor.settle(answer, doc, values, col)
|
|
_same(py, _ts(answer, doc, values) if col is None else _ts_col(answer, doc, values, col))
|
|
use = col if col in ("1.5", "2.0", "2.5") else "2.0" # 모름 = 기본
|
|
line = next(x for x in py["줄"] if x["로직"] == THOUSAND)
|
|
assert line["수량"] == {"1.5": 3, "2.0": 4.5, "2.5": 6}[use] and line["수량출처"] == "손"
|
|
mat = py["자재"][0]
|
|
assert mat["수량"] == {"1.5": 0.8, "2.0": 1, "2.5": 1.4}[use]
|
|
|
|
|
|
def _ts_col(answer: dict, doc: dict, values: dict, col: str) -> dict:
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_m02_labor_calc.cjs")],
|
|
input=json.dumps({"답": answer, "문서": doc, "값": values, "열": col}, ensure_ascii=False),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_규격합_은_열마다_다시_센_반영합():
|
|
doc = labor.clean(SPEC_DOC)
|
|
out = labor.calc(doc, {"사석 수상고르기|㎡": 2.5})
|
|
sums = labor.spec_sums(doc, out)["규격합"]
|
|
assert list(sums) == ["1.5", "2.0", "2.5"]
|
|
for col, total in sums.items():
|
|
assert total == labor.settle(out, doc, {"사석 수상고르기|㎡": 2.5}, col)["반영합"]
|
|
assert sums["1.5"]["계"] < sums["2.0"]["계"] < sums["2.5"]["계"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"bad",
|
|
[
|
|
{"이름": "", "열": ["1.5"]},
|
|
{"이름": "H", "열": ["1.5", "1.5"]},
|
|
{"이름": "H", "열": ["1.5"], "수량": {THOUSAND: {"1.5": "글"}}},
|
|
],
|
|
)
|
|
def test_규격_칸_꼴이_틀리면_400(bad):
|
|
with pytest.raises(labor.store.StoreError) as got:
|
|
labor.clean({**SPEC_DOC, "규격": bad})
|
|
assert got.value.status == 400
|
|
|
|
|
|
def test_열_없는_규격_은_옛_문서():
|
|
assert "규격" not in labor.clean({**SPEC_DOC, "규격": {"이름": "H", "열": []}})
|
|
assert "규격" not in labor.clean({k: v for k, v in SPEC_DOC.items() if k != "규격"})
|