174 lines
6.8 KiB
Python
174 lines
6.8 KiB
Python
"""STMATE 재현 앱 분리형 변경 STC(차수 인계) · 코드 일괄 변경 · JB_* 기준 제비율표 · STC 검사.
|
|
|
|
정답: 소광 원설계 STC(BOQ11·BOQ12) → 실제 「변경 1차분」 STC(16번 §4.6) · 원본 STC 바이트(코드 되돌림) ·
|
|
10번 §4 문서의 RATE ↔ JB 일치 사례(설치 폴더에 JB_* 가 있을 때만).
|
|
"""
|
|
|
|
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 conv_jb import JB_DIR, list_jb, match_project # 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"]
|
|
|
|
|
|
needs_jb = pytest.mark.skipif(not JB_DIR.exists(), reason="STMATE 설치 폴더 JB_* 없음")
|
|
|
|
|
|
@needs_jb
|
|
def test_jb_files_shapes():
|
|
files = list_jb()
|
|
assert len(files) >= 43 and all(f["shape_ok"] for f in files)
|
|
kinds = collections.Counter(f["kind"] for f in files)
|
|
assert (
|
|
kinds["JB_1"] >= 1 and kinds["JB_M"] >= 1 and kinds["JB_R"] >= 27 and kinds["JB_RA"] >= 14
|
|
)
|
|
jr = {f["date"]: f["cols"] for f in files if f["kind"] == "JB_R"}
|
|
assert jr["2020-01-02"] == 20 and jr["2020-05-27"] == 35 # 2020-05-27 확장(10번 §4)
|
|
|
|
|
|
@needs_jb
|
|
@pytest.mark.parametrize(
|
|
"rel,table,date",
|
|
[
|
|
(
|
|
"05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.STC",
|
|
"RATE_R.DBF",
|
|
"2023-01-02",
|
|
),
|
|
(
|
|
"05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.STC",
|
|
"RATE.DBF",
|
|
"2011-01-12",
|
|
),
|
|
(
|
|
"04.산불 봉화/01. 내역서/13-2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.STC",
|
|
"RATE_R.DBF",
|
|
"2024-03-15",
|
|
),
|
|
(
|
|
"2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).STC",
|
|
"RATE_R.DBF",
|
|
"2020-05-27",
|
|
),
|
|
],
|
|
)
|
|
def test_jb_match_documented_cases(rel, table, date):
|
|
m = match_project(Source(str(DOCS / rel)).files)
|
|
assert m[table]["exact"] and m[table]["date"] == date
|
|
|
|
|
|
@needs_jb
|
|
def test_jb_sogwang_five_cells():
|
|
m = match_project(Source(str(ORIG)).files)
|
|
assert (
|
|
m["RATE_R.DBF"]["date"] == "2025-01-01" and m["RATE_R.DBF"]["diff_cells"] == 5
|
|
) # 10번 §4 「5개 셀만 다름」
|