Files
Aislo/resources/tester/test_704_logic_standard.py
T
eomsangdonandClaude Opus 5.5 cddb28b63a feat(714-2): 공용 단가산출 계산서 틀 — A00_Common/calc_sheet.ts(704-2 계산서 줄 올림 · 묶음 머리 변수 칸 · 바꾸면 onInput) · M02 계산서 보기 · M01 「인력·재료·경비」 보기가 한 벌로 씀(장비 한 줄 · 기준단위 · 환산 근거는 714-1 뒤)
- 704-2 Labor_Sheet 줄 · 소계 · 계 = calcSheetRows(표본 셋 DOM 전후 같음)
- 새 M01_MasterData_UI_LogicLab_Est.ts — /text 묶음 그대로 · 묶음이 쓰는 시험 입력(714-3 usedInputs) = 머리 변수 칸 · 「준비 중」 자리 걷음
- 낡은 시험 test_704_logic_standard 고침 — 계산서가 계산 줄 부품을 공용 계산서 안에서 씀(import 자리만 바뀜)
- 새 지킴 시험 test_714_2_calc_sheet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:03:36 +09:00

142 lines
6.3 KiB
Python

# -*- coding: utf-8 -*-
"""PLAN 704-1 · 704-2 — M02 기준 목록(대표 규격 펼침 · 금액 = M01 엔진) · 계산서 보기.
① 세 항목(돌쌓기 메쌓기 · 파형강관 · 구조물터파기) 줄 금액 = M01 시험 계산(`store.calc`)
② 대표 규격 — 관경 고르기 11 · 뒷길이 표 줄 3 · 지역 = 자동값 한 벌
③ 실무 낱말 — 「석축」 찾으면 돌쌓기 · 찰쌓기(서버 찾기 맞춤 `hit` = 화면 `matchLogic` 거울 · 208-1)
④ 계산서 — 계 = 엔진 값 · 비목 소계 합 = 계 · 규격 축 아닌 입력은 400
⑤ 목록 저장본 — 임시 storage `_derived/` · 같은 지문이면 다시 안 셈
⑥ 화면 — 고르기 창이 기준 목록 표 · 계산서 [담기] = 지금 담기 동작(`done(logic)`)
"""
from __future__ import annotations
from pathlib import Path
import pytest
from M01_MasterData import M01_MasterData_Store as m01
from M01_MasterData.M01_MasterData_Store_Find import hit
from M02_MasterTemplete import M02_Logic_Standard as st
ROOT = Path(__file__).resolve().parents[2]
THREE = ("GF000220", "GC000404", "GF000101") # 돌쌓기 메쌓기(장비) · 파형강관 부설 · 구조물터파기
def _rows(key: str) -> list[dict]:
whole = m01.loaded()[1]
return st._rows_of(whole, whole.logic(key), False)
def _given(key: str, change: dict) -> dict:
whole = m01.loaded()[1]
return st._given(whole, whole.logic(key), change)[0]
@pytest.mark.parametrize("key", THREE)
def test_줄_금액은_M01_시험_계산에_끝수만_버림(key: str) -> None:
rows = _rows(key)
assert rows
for row in rows:
got = m01.calc(key, _given(key, row["바꿈"]))
assert got["ok"], got
gap = float(got["sums"]["계"]) - row["금액"] # 줄 · 비목 버림(B09 산근 규칙) — 최대 3 원
assert -0.01 <= gap < 3.2, row["규격"]
def test_대표_규격_펼침() -> None:
pipe = _rows("GC000404")
assert len({r["규격"] for r in pipe}) == len(pipe) # 줄마다 규격이 다름
assert sum(1 for r in pipe if not r["바꿈"] or "관경" in r["바꿈"]) == 11
assert pipe[0]["규격"].startswith("관경 250") and pipe[0]["단위"] == "본"
stone = _rows("GF000220")
backs = [r for r in stone if not r["바꿈"] or "뒷길이" in r["바꿈"]]
assert [r["규격"].split(" · ")[0] for r in backs] == [
"뒷길이 35㎝ 이하",
"뒷길이 55㎝ 이하",
"뒷길이 75㎝ 이하",
]
assert all("지역" not in r["바꿈"] for r in stone) # 지역 = 자동값 한 벌
hand = _rows("GF000219") # 메쌓기(인력) — 견치돌 · 뒷길이 25 같은 표 밖 조합은 0 원 → 뺌
assert all(r["금액"] != 0 for r in hand[1:])
assert stone[0]["공종"] == "돌공" and stone[0]["단위"] == "㎡"
assert _rows("GF000101")[0]["공종"] == "토공"
def test_석축_찾으면_쌓기() -> None:
hits = [
r["이름"]
for _, data in m01._logic_files(m01.loaded()[0])
for logic in data.get("줄") or []
for r in [{**logic, "별칭": ", ".join(st.words_of(logic))}]
if hit(r, "석축")
]
assert "돌쌓기 메쌓기(장비)" in hits
assert any("찰쌓기" in name for name in hits)
assert "석축" in _rows("GF000220")[0]["별칭"]
@pytest.mark.parametrize("key", THREE)
def test_계산서_계는_엔진_값(key: str) -> None:
row = _rows(key)[-1]
got = st.sheet(key, row["바꿈"])
assert got["계"] == pytest.approx(row["금액"], abs=0.01)
assert sum(g["소계"] for g in got["묶음"]) == pytest.approx(got["계"], abs=0.01)
assert got["판"].endswith("노임") and got["출처"]
assert all(line["글"] for g in got["묶음"] for line in g["줄"])
def test_계산서_규격_축_아닌_입력은_거절() -> None:
with pytest.raises(m01.StoreError) as e:
st.sheet("GF000220", {"지역": "서울"})
assert e.value.status == 400
with pytest.raises(m01.StoreError):
st.sheet("GF000220", {"뒷길이": 40}) # 대표 값 아님
def test_목록_저장본_같은_지문이면_안_셈(tmp_path, monkeypatch) -> None:
calls = []
def fake(files, workers=1):
calls.append(workers)
return {"판": "2026 상반기 노임", "공종": ["돌공"], "줄": [{"키": "GF000220"}]}
monkeypatch.setattr(st.layers, "storage_root", lambda: tmp_path)
monkeypatch.setattr(st, "build", fake)
st._MEMO.clear()
first = st.listing()
assert first["준비"] is False and first["줄"] == [] # 셈 도중 = 빈 줄
st._MEMO["job"].join()
assert st.listing()["줄"] == [{"키": "GF000220"}]
st._MEMO.clear() # 서버 다시 켬 — 저장본에서 읽음
assert st.listing()["준비"] is True and len(calls) == 1
assert list((tmp_path / "_derived" / st.KIND).glob("*.json"))
st._MEMO.clear()
def test_화면_기준_목록_계산서_담기() -> None:
pick = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor_Pick.ts").read_text("utf-8")
assert 'from "@ui/ui_template_data_table"' in pick and "fetchStandard()" in pick
assert "openStandardSheet(row, () => {\n close();\n done(logic);" in pick
assert "(!work.select.value || x.공종 === work.select.value)" in pick
sheet = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor_Sheet.ts").read_text("utf-8")
# 줄 · 소계 · 계 = 공용 단가산출 계산서(714-2 · 계산 줄 부품을 그 안에서 씀)
assert 'from "../A00_Common/calc_sheet"' in sheet and "createSourceView(" in sheet
for name in ("Labor_Pick", "Labor_Sheet"):
text = (ROOT / f"M02_MasterTemplete/M02_MasterTemplete_{name}.ts").read_text("utf-8")
assert len(text.splitlines()) <= 700
router = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Router_Combos.py").read_text("utf-8")
assert '@router.get("/logic-lib/standard", dependencies=[Depends(verify_session)])' in router
assert '@router.post("/logic-lib/sheet", dependencies=[Depends(verify_session)])' in router
def test_내역서_낱말_측구파기로_측구터파기를_찾음() -> None:
logic = next(
logic
for _, data in m01._logic_files(m01.loaded()[0])
for logic in data.get("줄") or []
if logic.get("키") == "GF000099" # 9-12-1 측구터파기 토사
)
row = {**logic, "별칭": ", ".join(st.words_of(logic))}
assert hit(row, "측구파기")