"""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": "임도공사"})