"""STMATE 재현 앱 조달청 공사원가호환규정 XML — 공식 XSD(V1.0.5) 검증 · 금액은 STC 저장값과 1원 대조. XSD = 지식DB STmate/old/30_원자료/조달청_공사원가호환규정.xsd(STMATE 설치본 Valid_Xml.vsl 사본). 정답 = STC 안 PRJLST.DBF 차수별 저장값(MCOST·LCOST·SCOST·COSTW). 코드에 박지 않음. """ import json import subprocess import sys from pathlib import Path import pytest from lxml import etree ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "tools/stmate-test")) import server # noqa: E402 HELPER = Path(__file__).with_name("helper_stmate_report.cjs") DOCS = ROOT / "resources/knowledge/original/실무문서" XSD = ( ROOT / "resources/knowledge/original/경쟁사 프로그램/STmate/old/30_원자료/조달청_공사원가호환규정.xsd" ) 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, ), } def pps(name): rel, stage = 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": stage, "mode": "pps"}, ensure_ascii=False), text=True, encoding="utf-8", ) return load, stage, etree.fromstring(json.loads(out)["xml"].encode("utf-8")) @pytest.mark.parametrize("name", list(STCS)) def test_pps_xml_is_valid_and_totals_match_stc(name): load, n, doc = pps(name) schema = etree.XMLSchema(etree.parse(str(XSD))) assert schema.validate(doc), f"{name} XSD 위반 {[str(e) for e in list(schema.error_log)[:3]]}" def amount(code): for it in doc.iter("제경비항목"): if it.findtext("세부공종코드") == code: return float(it.findtext("산출금액")) return None prj = load["prjlst"] assert abs(amount("700") - float(prj[f"COSTW{n}"])) < 0.5, f"{name} 총공사비" sums = {"재료비": 0.0, "노무비": 0.0, "경비": 0.0} for g in doc.iter("내역서"): if g.findtext("공종구분") != "일반공종": continue for it in g.findall("세부공종"): for k in sums: sums[k] += float(it.findtext(k)) for k, col in (("재료비", "MCOST"), ("노무비", "LCOST"), ("경비", "SCOST")): assert abs(sums[k] - float(prj[f"{col}{n}"])) < 0.5, f"{name} 직접{k} {sums[k]}" codes = {r["CODE"].strip()[0] for r in load["costn_rows"]} for tag, p in (("일위대가", "B"), ("기계경비", "X"), ("자재", "M"), ("노무", "L")): if p in codes: assert doc.find(f".//{tag}") is not None, f"{name} {tag} 없음"