Files
Aislo/resources/tester/test_stmate_conv_xml.py
T

144 lines
5.5 KiB
Python

"""STMATE 재현 앱 조달청 XML 가져오기 — 실무 STC → report_pps 내보내기 → 가져오기 왕복 · XML 변환 설정.
정답 = 원본 STC(말단 내역 행 · PRJLST 차수 직접비 · 총공사비 · COSTN 코드). 코드에 값을 박지 않음.
설정 선택지·칸 차례는 공식 XSD(V1.0.5)에서 읽은 값과 견줌.
"""
import collections
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 conv_xml import read_xml, xml_to_project # noqa: E402
from conv_xml_cfg import apply_settings, building_fields, options, validate # noqa: E402
from dbf_rw import DbfTable # noqa: E402
from stc_project import write_output # noqa: E402
from stc_read import Source # noqa: E402
from stc_save import _leaf # noqa: E402
HELPER = Path(__file__).with_name("helper_stmate_report.cjs")
DOCS = ROOT / "resources/knowledge/original/실무문서"
STCS = {
"봉화2024": (
"04.산불 봉화/01. 내역서/13-2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.STC",
1,
),
"영월2024": ("05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.STC", 1),
"대흥2024": (
"2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).STC",
1,
),
"울진2025": (
"25년 산불진화임도(기번8)(울진소광)/2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC",
1,
),
"영덕변경": (
"20250527 2025년 계류보전사업(기번1-영덕.병곡.영리.산214-1)(변경)/01.내역자료/2025년 계류보전사업(기번1 영덕.병곡.영.산214)(변경).STC",
2,
),
}
_cache: dict = {}
def _xml(name):
if name not in _cache:
rel, n = STCS[name]
load = server.api_load(server.LoadReq(source_path=str(DOCS / rel)))
out = subprocess.check_output(
["node", str(HELPER)],
input=json.dumps({"load": load, "stage": n, "mode": "pps"}, ensure_ascii=False),
text=True,
encoding="utf-8",
)
_cache[name] = json.loads(out)["xml"]
return _cache[name]
@pytest.mark.parametrize("name", list(STCS))
def test_xml_roundtrip_reproduces_boq_and_costs(name, tmp_path):
rel, n = STCS[name]
s = Source(str(DOCS / rel))
xml = _xml(name)
data = read_xml(xml)
bt = DbfTable(s.files["BOQ11.DBF"])
def key(code, q, m, lab, sv):
return (
code,
round(float(q or 0), 4),
int(float(m or 0)),
int(float(lab or 0)),
int(float(sv or 0)),
)
want = collections.Counter(
key(
bt.get(i, "BDCODE1"),
*(bt.get(i, f"{c}{n}") for c in ("QTY", "M_AMT", "L_AMT", "S_AMT")),
)
for i in range(len(bt))
if _leaf(bt.get(i, "BDCODE1")) and float(bt.get(i, f"QTY{n}") or 0) != 0
)
got = collections.Counter(
key(r["BDCODE1"], r["QTY1"], r["M_AMT1"], r["L_AMT1"], r["S_AMT1"])
for r in data["boq"]
if _leaf(str(r["BDCODE1"])) and float(r["QTY1"] or 0) != 0
)
assert want == got, (
f"{name} 말단 행 다름 {list((want - got).items())[:3]} / {list((got - want).items())[:3]}"
)
codes = {
DbfTable(s.files["COSTN.DBF"]).get(i, "CODE")
for i in range(len(DbfTable(s.files["COSTN.DBF"])))
}
assert {r["CODE"] for r in data["costn"]} <= codes
out, rep = xml_to_project(s.files, xml)
pa, pb = DbfTable(s.files["PRJLST.DBF"]), DbfTable(out["PRJLST.DBF"])
for c in ("MCOST", "LCOST", "SCOST", "TCOST"):
assert pb.get(0, f"{c}1") == pa.get(0, f"{c}{n}"), c
assert pb.get(0, "COSTW1") == pa.get(0, f"COSTW{n}")
assert pb.get(0, "PNAME") == pa.get(0, "PNAME")
res = write_output(tmp_path, "xml", out, s.names, s.entries)
load = server.api_load(server.LoadReq(source_path=res["saved_stc"]))
assert load["costn_count"] == rep["costn_count"] and load["boq_count"] == rep["boq_count"]
def test_xml_settings_from_xsd():
o = options()
assert (
len(o["prj_types_xsd"]) == 9
and "국가유산수리공사" in o["prj_types"]
and len(o["prj_types"]) == 8
)
names = [f["name"] for f in building_fields()]
assert len(names) == 36 and names[0] == "연면적" and "공사현장지역코드" in names
region = next(f for f in building_fields() if f["name"] == "공사현장지역코드")["enum"]
assert len(region) > 200 and {s["code"] for s in o["sido"]} == {c[:2] for c in region}
xml = _xml("봉화2024")
y = apply_settings(xml, {"prj_type": "조경공사"})
assert (
validate(y)["ok"]
and "<주공사>조경공사</주공사>" in y
and "<공사구분>조경공사</공사구분>" in y
)
z = apply_settings(
xml,
{
"prj_type": "건축공사",
"building": {"연면적": 1234.5, "공사현장지역코드": region[5], "공사현장주소": "시험"},
},
)
v = validate(z)
assert v["ok"], v["errors"][:2]
assert "<건축공사사업내용작성여부>true" in z and "<연면적>1234.5</연면적>" in z
with pytest.raises(ValueError):
apply_settings(xml, {"prj_type": "건축공사", "building": {"공사현장지역코드": "99999"}})
with pytest.raises(ValueError):
apply_settings(xml, {"prj_type": "임도공사"})