Files
Aislo/resources/tester/test_stmate_kanjub_stc.py
T
eomsangdonandClaude Opus 5.5 3d8fddfc34 feat(stmate-test): 보고서·제잡비·식언어·수량산출·화면 묶음 합침 · 재계산 후 저장 무변경
- 보고서: 원본 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>
2026-11-13 19:12:04 +09:00

127 lines
5.7 KiB
Python

"""STMATE 재현 앱 원가계산서 — 실무 STC 7건의 차수별 저장값(PRJLST.DBF n칸 · PRJLST.SMT [n_KANJUB])과 1원 대조.
정답은 STC 안 원본 저장값에서 읽음(코드에 박지 않음). 직접비는 내역 저장 단가 기준.
변경설계 2건은 당초(1)·변경(2) 차수 모두 대조 — D_ 당초액 유지 · 안전관리비 비례 · 산림조합 부가세 포함.
"""
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
HELPER = Path(__file__).with_name("helper_stmate_calc.cjs")
DOCS = ROOT / "resources/knowledge/original/실무문서"
STCS = {
"봉화2024": "04.산불 봉화/01. 내역서/13-2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.STC",
"울진2025": "25년 산불진화임도(기번8)(울진소광)/2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC",
"영월2024": "05. 동부청 영월/내역서/2024년 임도시설사업2차(산불진화_기번6)-영월.STC",
"대흥2024": "2024년 간선임도(기번3-울진.대흥)(공통)/1. 설계원본/01 내역자료/2024년 간선임도사업(기번3).STC",
"소광원설계": "25년 산불진화임도(기번8)(울진소광)/1. 설계원본/03-내역/22-2025년 산불진화임도사업(울진 금강송 소광).STC",
"영덕변경": "20250527 2025년 계류보전사업(기번1-영덕.병곡.영리.산214-1)(변경)/01.내역자료/2025년 계류보전사업(기번1 영덕.병곡.영.산214)(변경).STC",
"소광변경1차": "25년 산불진화임도(기번8)(울진소광)/변경 1차분-2025년 산불진화임도 신설사업(기번8 울진.금강송.소광.산29외).STC",
}
SMT_KEYS = {
"KANNO": "C_KANNO",
"SANBO": "C_SANBO",
"KOBO": "C_KOBO",
"KUNBO": "C_KUNBO",
"SLVBO": "C_SLVBO",
"YUNBO": "C_YUNBO",
"ANJUN": "C_ANJUN",
"KITA": "C_T_KITA",
"HWAN": "C_HWAN",
"EQIP": "C_EQIPSU",
"ILBAN": "C_ILBAN",
"IYUN_M": "C_IYUN_M",
"GOV": "C_UPDNKWN",
}
DBF_KEYS = {"dM": "MCOST", "dL": "LCOST", "dS": "SCOST", "dT": "TCOST", "GRAND": "COSTW"}
@pytest.mark.parametrize("name", list(STCS))
def test_kanjub_matches_stc_to_the_won(name):
loaded = server.api_load(server.LoadReq(source_path=str(DOCS / STCS[name])))
out = json.loads(
subprocess.check_output(
["node", str(HELPER)],
input=json.dumps(loaded["sim_state"], ensure_ascii=False),
text=True,
encoding="utf-8",
)
)
assert out, f"{name} 원가계산서 없음"
diffs = {}
for n, got in out.items():
expected = {k: loaded["prjlst_smt"][f"{n}_{v}"] for k, v in SMT_KEYS.items()}
expected |= {k: loaded["prjlst"][f"{v}{n}"] for k, v in DBF_KEYS.items()}
for k, v in expected.items():
if abs(got[k] - float(v)) >= 0.5:
diffs[f"{n}:{k}"] = (got[k], float(v))
assert not diffs, f"{name} 불일치 {diffs}"
KEYS_HELPER = Path(__file__).with_name("helper_stmate_kanjub_keys.cjs")
# 계산이 내는 저장키 — 실무 7건 모든 차수에서 나와야 하고 저장값과 1원 일치
REQUIRED_KEYS = (
"C_KANNO C_SANBO C_KOBO C_KUNBO C_SLVBO C_YUNBO C_TBUGUM C_ANJUN C_T_KITA C_HWAN C_EQIPSU"
" C_HADOSU C_KONGSU C_ILBAN C_IYUN_M C_KN_SA C_KONGSA_J C_KONGSA C_UPDNKWN C_KWANGUP"
" C_KWANGUP_D C_KWANGUP_K C_T_JODAL C_S_SUDO C_S_BOKRI C_S_SOMO C_S_TONGSIN C_S_SEGUM C_S_PEGI"
" C_S_DOSEO C_S_JIGUP C_S_SIGONG C_ANJUNP C_EIS_SUPPAY C_EIS_HOLPAY C_EIS_SOCINS C_EIS_INCENT"
" C_DOSEOH C_ANJUN_B C_PUMJIL_B C_PSAMT_B C_EXCEPT_B C_BUSAN_B C_PURCHAS C_T_PEGI C_T_KOLJAE"
" C_NBUGAM I_ANJUN_B"
).split()
def _keys_out(loaded):
return json.loads(
subprocess.check_output(
["node", str(KEYS_HELPER)],
input=json.dumps(loaded["sim_state"], ensure_ascii=False),
text=True,
encoding="utf-8",
)
)
@pytest.mark.parametrize("name", list(STCS))
def test_kanjub_every_stored_key(name):
"""계산 결과 저장키(C_KN_SA · C_KONGSA_J · C_KWANGUP · 관급 절상 · 안전 1.2배 표시 I_ANJUN_B · B_BUGA_UP …) 전수 대조."""
loaded = server.api_load(server.LoadReq(source_path=str(DOCS / STCS[name])))
kv = loaded["prjlst_smt"]
for n, got in _keys_out(loaded).items():
keys = got["keys"]
missing = [k for k in REQUIRED_KEYS if k not in keys]
assert not missing, f"{name} {n}차수 계산 안 된 키 {missing}"
diffs = {
k: (v, kv[f"{n}_{k}"])
for k, v in keys.items()
if f"{n}_{k}" in kv and abs(float(v) - float(kv[f"{n}_{k}"] or 0)) >= 0.5
}
assert not diffs, f"{name} {n}차수 저장키 불일치 {diffs}"
# 식 템플릿 재평가 = 항목 후보(F2-57)
bad = {c: v for c, v in got["tpl"].items() if abs(v[0] - v[1]) >= 0.5}
assert not bad, f"{name} {n}차수 식 템플릿 불일치 {bad}"
@pytest.mark.parametrize("name", list(STCS))
def test_kanjub_smt_round_trip(name):
"""PRJLST.SMT → 설정 → 되돌린 저장 사전이 원본 [n_KANJUB]·ADD_* 키를 값 그대로 담음(F2-58)."""
from kanjub_cfg import build_kanjub_config, smt_updates, stage_slots
kv = server.api_load(server.LoadReq(source_path=str(DOCS / STCS[name])))["prjlst_smt"]
back = {}
for n in stage_slots(kv):
back |= smt_updates(build_kanjub_config(kv, n), n)
for k, v in kv.items():
assert k in back, f"{name} 되돌린 키 없음 {k}"
try:
assert abs(float(back[k]) - float(v or 0)) < 1e-6, (k, back[k], v)
except ValueError:
assert back[k] == v, (k, back[k], v)