- 시험 615 통과 · 브라우저 화면 13개·메뉴 90개 오류 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.6 KiB
Python
91 lines
3.6 KiB
Python
"""STMATE 재현 앱 보호표시(UC) 일괄 손질 제외 · 단가 성분 검사(TAMT = 재+노+경 · 코드종류별 비목).
|
|
|
|
보호 = wM_Boxa 단가 격자 「보호」 열(√/공란 = True/False) → 기초·견적단가(M·L·S·W) 행 COSTN UC.
|
|
일괄 손질(가격지 채택 · 기준단가 수정 · 일괄할증 · 일괄변경 · 후보 채택)은 보호 행을 건너뜀(22 §3.1 안내문).
|
|
B·D 행 UC 는 산근 편집기 「결과값에 낙찰율적용」(Ck_UC) 자리 — 실무 7건 UC=T ⇔ NAGCD>0.
|
|
성분 검사 = 10 §3 검증 조건식 — 실무 7건 COSTN·내역 어긋남 0 · 일부러 어긋낸 칸만 잡힘.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "tools/stmate-test"))
|
|
import server # noqa: E402
|
|
from stc_save import apply_save # noqa: E402
|
|
|
|
HERE = Path(__file__).parent
|
|
STCS = sorted((ROOT / "resources/knowledge/original/실무문서").rglob("*.STC"))
|
|
IDS = [p.stem[:16] for p in STCS]
|
|
_CACHE: dict = {}
|
|
|
|
|
|
def _f(v):
|
|
return float(v or 0)
|
|
|
|
|
|
def run(stc):
|
|
if stc not in _CACHE:
|
|
d = server.api_load(server.LoadReq(source_path=str(stc)))
|
|
# 보호 대상 = 바뀔 수 있는 기초단가 몇 건(후보 있는 재료 · 노무 · 경비) — 자료에서 고름
|
|
mls = [r for r in d["costn_rows"] if r["CODE"][:1] in "MLS" and _f(r["TAMT1"]) > 0]
|
|
cand = [r for r in mls if any(_f(r.get(f"AMT_{i}1")) > 0 for i in range(1, 6))]
|
|
prot = [r["CODE"].strip() for r in (cand[:3] + [r for r in mls if r not in cand][:2])]
|
|
d["prot"] = prot
|
|
out = subprocess.check_output(
|
|
["node", str(HERE / "helper_stmate_ui_protect.cjs")],
|
|
input=json.dumps(d, ensure_ascii=False, default=str),
|
|
text=True,
|
|
encoding="utf-8",
|
|
)
|
|
_CACHE[stc] = (d, json.loads(out))
|
|
return _CACHE[stc]
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS, ids=IDS)
|
|
def test_batch_edits_skip_protected_rows(stc):
|
|
d, r = run(stc)
|
|
prot = set(d["prot"])
|
|
assert prot
|
|
moved = 0
|
|
for name, o in r["ops"].items():
|
|
assert o["untouched"], name # 보호 행 단가 그대로
|
|
assert o["marked"] == sorted(set(o["plain"]) - prot), name # 나머지는 보호 없을 때와 같음
|
|
moved += len(set(o["plain"]) & prot)
|
|
assert moved > 0 # 보호가 없었으면 바뀌었을 행이 실제로 있었음
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS, ids=IDS)
|
|
def test_unprotect_auto_hal_and_bd_uc_is_bid_rate_flag(stc):
|
|
d, r = run(stc)
|
|
a = r["autoHal"]
|
|
assert a["uc"] == "F" and a["got"] is not None and a["got"] == a["want"]
|
|
assert r["bdProtected"] == 0 # 산근·일위 UC=T 는 보호로 보지 않음
|
|
for row in d["costn_rows"]: # 근거 — UC=T ⇔ 낙찰율 열(NAGCD) > 0
|
|
if row["CODE"][:1] in "BD":
|
|
assert (str(row.get("UC") or "").strip() == "T") == (_f(row.get("NAGCD")) > 0)
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS, ids=IDS)
|
|
def test_protect_mark_saves_only_uc(stc):
|
|
d, r = run(stc)
|
|
files = server.read_project_files(str(stc))
|
|
_out, log = apply_save(files, r["payload"])
|
|
got = {(x["table"], x["field"]) for x in log}
|
|
assert got == {("COSTN.DBF", "UC")}
|
|
assert len(log) == len(d["prot"])
|
|
|
|
|
|
@pytest.mark.parametrize("stc", STCS, ids=IDS)
|
|
def test_component_check_clean_on_real_and_catches_broken(stc):
|
|
_d, r = run(stc)
|
|
assert r["check"] == []
|
|
assert r["checkBoq"] == [], r["checkBoq"][:3]
|
|
b = r["broken"]
|
|
hits = {(x["code"], x["slot"], x["rule"]) for x in b["res"]}
|
|
assert hits == {(b["m"], 1, "own"), (b["m"], 1, "sum"), (b["b"], 1, "sum")}
|