Files
Aislo/resources/tester/test_stmate_conv_change.py
T
eomsangdonandClaude Opus 5.5 a4c0bc14a6 refactor(stmate-test): 원본에 없는 기능 걷음 — 제비율표 JB 대조 보기 · 보고서 CSV · 계약 저장 공사 대조 · 옛 화면 9개(표준카탈로그·레시피·REA 아키텍처 창·가짜 JDL/XML)
- 원본 대조 = 설치본 실행파일 52개 화면 이름·버튼 글·한글 문자열
- 지움: conv_jb.py · excel_builder.py · view_dan_ilwi.js · view_kanjub_prt.js · /api/conv/jb-* · /api/report/csv · /api/stage/projects · /api/catalog · /api/export-xlsx
- 낡은 시험 삭제(지운 기능 전용): conv_change 제비율표 3 · stage_api 공사별 설정 2 · report_sheets CSV 2줄
- 장부 F4-15 · F1-33 · F6-24 설명 고침 · stmate 시험 786 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 18:24:22 +09:00

118 lines
4.8 KiB
Python

"""STMATE 재현 앱 분리형 변경 STC(차수 인계) · 코드 일괄 변경 · STC 검사.
정답: 소광 원설계 STC(BOQ11·BOQ12) → 실제 「변경 1차분」 STC(16번 §4.6) · 원본 STC 바이트(코드 되돌림).
"""
import collections
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "tools/stmate-test"))
from bdqty import decode_bdqty # noqa: E402
from dbf_rw import DbfTable # noqa: E402
from smt_parser import extract_costn_memos, parse_dbf_bytes, parse_prjlst_smt # noqa: E402
from stc_archive import build_stc, check_stc # noqa: E402
from stc_change import split_change # noqa: E402
from stc_codechg import change_codes # noqa: E402
from stc_read import Source # noqa: E402
from stc_save import _leaf, apply_save # noqa: E402
DOCS = ROOT / "resources/knowledge/original/실무문서"
SO = DOCS / "25년 산불진화임도(기번8)(울진소광)"
ORIG = SO / "2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC"
CHG1 = SO / "변경 1차분-2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC"
STCS = sorted(DOCS.rglob("*.STC"))
def _kv(files):
_, rows, _ = parse_dbf_bytes(files["PRJLST.DBF"])
return parse_prjlst_smt(files["PRJLST.SMT"], rows[0]["PRJ_MEMO_HEX"])
def test_split_change_matches_real_next_file():
a, b = Source(str(ORIG)).files, Source(str(CHG1)).files
out, rep = split_change(a)
assert rep["from_pcode"] == "12" # 통합형 — BOQ12 가 현재안
assert set(k for k in out if k.startswith("BOQ1")) == {"BOQ11.DBF"}
pg, pb = DbfTable(out["PRJLST.DBF"]), DbfTable(b["PRJLST.DBF"])
assert len(pg) == 1
for f in ("PNAME", "PCODE", "K_ORD", "MCOST1", "LCOST1", "SCOST1", "TCOST1", "COSTW1"):
assert pg.get(0, f) == pb.get(0, f), f
g, t = DbfTable(out["BOQ11.DBF"]), DbfTable(b["BOQ11.DBF"])
def key(tb, i, n):
return (
tb.get(i, "NAME1"),
tb.get(i, "SIZE1"),
round(float(tb.get(i, f"QTY{n}") or 0), 4),
int(float(tb.get(i, f"M_AMT{n}") or 0)),
int(float(tb.get(i, f"L_AMT{n}") or 0)),
int(float(tb.get(i, f"S_AMT{n}") or 0)),
)
mine = collections.Counter(key(g, i, 1) for i in range(len(g)) if _leaf(g.get(i, "BDCODE1")))
real = collections.Counter(
key(t, i, 1)
for i in range(len(t))
if _leaf(t.get(i, "BDCODE1")) and float(t.get(i, "QTY1") or 0)
)
assert mine == real
assert all(key(g, i, 1) == key(g, i, 2) for i in range(len(g))) # 차수2 = 출발점(차수1 복사)
kv1, kv2 = _kv(out), _kv(b)
rk = [k for k in kv2 if k.startswith("1_R_")]
assert rk and all(kv1.get(k) == kv2[k] for k in rk)
assert any(k.startswith("2_R_") for k in kv1)
assert check_stc(build_stc(Source(str(ORIG)).entries, out))["ok"]
@pytest.mark.parametrize("stc", STCS, ids=[p.stem[:16] for p in STCS])
def test_code_change_everywhere_and_back(stc):
f = Source(str(stc)).files
memos = extract_costn_memos(f["COSTN.DBF"], f["COSTN.SMT"])
xc = next(r["code"] for m in memos.values() for r in m["refs"] if r["code"].startswith("X"))
out, log = change_codes(f, {xc: "X99999"})
assert xc not in {r["CODE"].strip() for r in parse_dbf_bytes(out["COSTN.DBF"])[1]}
dq = decode_bdqty(out["BDQTY.DBF"])
assert not any(xc in (r["CODE"], r["BDCODE"]) for r in dq) and any(
"X99999" in (r["CODE"], r["BDCODE"]) for r in dq
)
m2 = extract_costn_memos(out["COSTN.DBF"], out["COSTN.SMT"])
assert not any(f"&{xc}" in m["text"] for m in m2.values()) and any(
"&X99999" in m["text"] for m in m2.values()
)
back, _ = change_codes(out, {"X99999": xc})
assert all(back[k][32:] == f[k][32:] for k in f) # 머리 날짜 밖 원래 바이트
with pytest.raises(ValueError):
change_codes(
f,
{
xc: next(
r["CODE"].strip()
for r in parse_dbf_bytes(f["COSTN.DBF"])[1]
if r["CODE"].strip() != xc
)
},
)
@pytest.mark.parametrize("stc", STCS, ids=[p.stem[:16] for p in STCS])
def test_check_stc_rules(stc):
r = check_stc(stc.read_bytes())
assert r["ok"] and r["warnings"] == [], (r["problems"], r["warnings"])
def test_prjlst_rows_edit_other_project_row():
f = Source(str(ORIG)).files
rows = parse_dbf_bytes(f["PRJLST.DBF"])[1]
assert len(rows) == 2
edited = [dict(r) for r in rows]
edited[1]["PNAME"] = "둘째 공사 이름 고침"
out, log = apply_save(f, {"prjlst_rows": edited})
assert [(x["recno"], x["field"]) for x in log] == [(1, "PNAME")]
assert DbfTable(out["PRJLST.DBF"]).get(1, "PNAME") == "둘째 공사 이름 고침"
out2, log2 = apply_save(f, {"prjlst_rows": rows})
assert log2 == [] and out2["PRJLST.DBF"] is f["PRJLST.DBF"]