Files
Aislo/resources/tester/test_stmate_ilwi_eval.py
T
eomsangdonandClaude Opus 5.5 217e20a6e0 feat(stmate-test): 일위대가·중기 재계산 — BDQTY 구성표 복호 · 연쇄 재계산 기초단가부터 1원
- bdqty.py — BDQTY.DBF 자리별 고정 변환(회전 + 더하기) 표 283자리로 복호(사용자 승인) · /api/load 에 bdqty_rows
- smt_eval.js evaluateComposition — 행 소수1 버림 · CHBNM 총액우선(행 합계액 기준) · PR 줄(PRPT 밑수 → PROUT)
  · % 단위 자원 · C* 뒤 % = 전체 비율 · 중기 운전원 CONLD 시간 환산 · '$' 단위 환율 · SBMOK 경비
- 연쇄 재계산 = X·D·B 참조 순서 · 봉화 기준 표준 레시피 이름 맞추기(자기 대조) 걷음
- 원설계 5건 일위대가·중기 291개 + 영덕 변경 31개 1원 · 기초단가부터 다시 셈해도 전 단가·총공사비 그대로
- 지킴 시험 test_stmate_ilwi_eval.py · 장부 32.2% → 33.5%

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 14:29:14 +09:00

75 lines
3.3 KiB
Python

"""STMATE 재현 앱 일위대가(B)·중기(X) 재계산과 연쇄 재계산(계산완료) — 실무 STC 저장 단가와 1원 대조.
구성 = BDQTY.DBF 복호(tools/stmate-test/bdqty.py · 사용자 승인) · 정답 = STC 의 COSTN 저장 단가.
연쇄 시험은 기초단가(L·M·S)만 두고 중기 → 산근 → 일위대가 → 내역 → 원가계산서를 다시 셈.
"""
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
HERE = Path(__file__).parent
DOCS = ROOT / "resources/knowledge/original/실무문서"
ORIGINAL = {
"봉화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",
}
CHANGED_OK = {
"영덕변경": "20250527 2025년 계류보전사업(기번1-영덕.병곡.영리.산214-1)(변경)/01.내역자료/2025년 계류보전사업(기번1 영덕.병곡.영.산214)(변경).STC",
}
def load(rel):
return server.api_load(server.LoadReq(source_path=str(DOCS / rel)))
def node(helper, payload):
out = subprocess.check_output(
["node", str(HERE / helper)],
input=json.dumps(payload, ensure_ascii=False),
text=True,
encoding="utf-8",
)
return json.loads(out)
def stored(row, slot):
return [float(row[f"{k}AMT{slot}"] or 0) for k in "MLS"]
@pytest.mark.parametrize("name", list(ORIGINAL) + list(CHANGED_OK))
def test_b_and_x_compositions_match_stored(name):
d = load({**ORIGINAL, **CHANGED_OK}[name])
out = node(
"helper_stmate_ilwi.cjs", {k: d[k] for k in ("costn_rows", "bdqty_rows", "start_ini")}
)
rows = {r["CODE"].strip(): r for r in d["costn_rows"]}
assert len(out) > 20, f"{name} 구성 호표 {len(out)}"
diffs = {}
for code, got in out.items():
slot = 2 if float(rows[code].get("NAGCD") or 0) > 0 else 1
if got != stored(rows[code], slot):
diffs[code] = (got, stored(rows[code], slot))
assert not diffs, f"{name} 불일치 {len(diffs)}/{len(out)} {list(diffs.items())[:4]}"
@pytest.mark.parametrize("name", list(ORIGINAL))
def test_full_cascade_keeps_every_unit_price_and_total(name):
d = load(ORIGINAL[name])
before = {r["CODE"].strip(): stored(r, 1) for r in d["costn_rows"]}
out = node("helper_stmate_cascade.cjs", d)
assert out["count"]["xCount"] > 0 and out["count"]["dCount"] > 0 and out["count"]["bCount"] > 0
moved = {c: (v[:3], before[c]) for c, v in out["prices"].items() if v[:3] != before[c]}
assert not moved, f"{name} 재계산 뒤 바뀐 단가 {list(moved.items())[:4]}"
assert abs(out["grand"] - float(d["prjlst"]["COSTW1"])) < 0.5