- 보고서: 원본 STMATE 엑셀과 셀 대조(목록표·자재단가대비·중기·일위대가·산근 목록·설계내역서·원가계산서) · 조달청 XML 공식 XSD 검증 · PM_Prt 화면 - 제잡비: 원본 계산 순서 4형식(T/S/TA/SA) · 실무 7건 저장 결과키 전수 1원 · 요율표 조회 · 선택지 계산 · 제잡비·제비율 화면 - 식언어·수량: 함수·조건식·금액변수·문법 검사·수량변수·QCODE · 산근·구성·중기공식 편집 화면 · 순환 알림 - 화면: 원본 메뉴 체계·단축키 · 설계내역서 편집(행 넣기·지우기·이동·레벨·보관함) · 초기값 · 단가목록표 - 저장: 초기값(Start8x.Ini) 바뀐 키 저장 · 노임 환산 직종 = 초기값 CONLD · 산근·구성 결과에 적용단가 칸 채우지 않음(원본 0) - DBF 글 칸 앞 빈칸 보존 · 시험 도우미 표준입력 utf8(한글 조각 깨짐 원인) - 실무 5건 재계산 후 저장 = 바뀐 칸 0 · 시험 332건 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.4 KiB
Python
85 lines
3.4 KiB
Python
"""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} 없음"
|