Files
Aislo/resources/tester/test_stmate_jdl_rea.py
T

137 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""STMATE 재현 앱 — 조달청 JDL TEXT 형식(지식DB REA 분석/37 §1 · 34 §17.9 바로잡음) 지킴 시험 · 할증율 4바이트 계수 ·
변경 4차 칸.
근거: JDLCHK.Tab_33.dll 0x5051e0(고정폭 탭 넣기 고리를 0x505218 jmp 가 건너뜀) · 0x504d20(첫 0x05 앞을 탭으로 ·
뒤를 0x07 로 나눔 · 열마다 Trim) · 열 개수 검사(0x405bfc 뒤 cmp) · FieldDefs(0x491dec) · L.Txt 오류 203002
만드는 쪽 ST_U_Convert 0x4b0904(코드 줄) · 0x4bc1a9(Main.TXT) · DP_Common EXTTOSTR 0x453e4c
할증율 = ST_Boq 0x4e9b20 fstp dword · 0x4e9bde fmul dword · 변경 4차 = PRJLST MCOST5·XX5NM · ST_Prt 0x4b3183
"""
import json
import math
import struct
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 jdl_format as J # noqa: E402
from dbf_rw import DbfTable # noqa: E402
from jdl_export import code_line, export_bundle # noqa: E402
from kanjub_cfg import stage_slots # noqa: E402
from stc_read import Source # noqa: E402
# 검사기 cmp 값(열 개수)
CMP = {"Main.Txt": 3, "M.Txt": 0x13, "L.Txt": 7, "E.Txt": 9, "W.Txt": 9, "J.Txt": 9, "GongJong.Txt": 4}
CMP |= {"Unit.Txt": 10, "JUnit.Txt": 10, "Danga.Txt": 10, "MainPart.Txt": 0x1F, "UnitPart.Txt": 0x1C}
CMP |= {"JPart.Txt": 0x13}
STC = next((ROOT / "tools/stmate-test/work_dbf").glob("*.STC"), None)
TAB, EXT, SUB, CRLF = "\t", "\x05", "\x07", b"\r\n"
def test_counts_and_column_tables():
assert J.COUNTS == CMP
for name, cols in J.COLUMNS.items():
assert len(cols) == CMP[name], name
def test_split_follows_checker():
"""탭 나눔 · 끝 탭 = 빈 열 · 0x05 뒤는 덧붙임 칸(0x07) · 열마다 #0~#32 걷음."""
raw = f"A{TAB} 이름 {TAB}\x01{TAB}{EXT}B{SUB}True{SUB} "
cols, ext = J.split_line(raw.encode("cp949"))
assert cols == ["A", "이름", "", ""] and ext == ["B", "True", ""]
assert J.split_line(f"X{TAB}Y") == (["X", "Y"], [])
line = J.build_line(["M00001", "철근", "", "TON", 1665.0, "", 0, ""], ["Q", True])
assert J.split_line(line) == (["M00001", "철근", "", "TON", "1665", "", "0", ""], ["Q", "True"])
def test_check_lines_counts_and_code_chars():
good = J.build_line(["L00001", "보통인부", "", "인", 150000, "", ""], ["", False, J.Raw(" ")])
assert J.check_lines("L.Txt", good + CRLF) == []
bad = J.build_line(["l0001", f"가{TAB}나", "", "인", 1, "", ""])
errs = J.check_lines("L.Txt", good + CRLF + bad)
assert any("203002" in e and "Line : 2" in e for e in errs) and any("코드 글자" in e for e in errs)
def test_num_text_exttostr():
got = [J.num_text(v) for v in (268058.0, 0, 0.1 + 0.2, 1e20, 1.5e-7, -12.5)]
assert got == ["268058", "0", "0.3", "100000000000000000000", "0.00000015", "-12.5"]
def test_export_bundle_lines_follow_rules():
if STC is None:
pytest.skip("표본 STC 없음")
src = Source(str(STC))
r = export_bundle(str(STC))
assert r["series"] == 1 and all(not v for v in r["checks"].values())
t = DbfTable(src.files["COSTN.DBF"])
want = {"L": "L.Txt", "M": "M.Txt", "S": "E.Txt", "W": "W.Txt", "B": "Unit.Txt", "X": "JUnit.Txt"}
want["D"] = "Danga.Txt"
lines = {k: v.split(CRLF) for k, v in r["files"].items()}
seen: dict[str, int] = {}
for i in range(len(t)):
code = t.get(i, "CODE")
name = want.get(code[:1])
if not name or t.deleted(i):
continue
cols, ext = J.split_line(lines[name][seen.get(name, 0)])
seen[name] = seen.get(name, 0) + 1
assert cols[0] == code and len(cols) == CMP[name]
k = code[0]
lam, mam, sam = (float(t.get(i, f) or 0) for f in ("LAMT1", "MAMT1", "SAMT1"))
if k == "L":
assert cols[4] == J.num_text(lam) and ext[0] == t.get(i, "BQCODE")
if k == "M":
assert cols[4] == J.num_text(mam) and cols[6] == J.num_text(float(t.get(i, "AMT_11") or 0))
if k == "S":
assert cols[4:7] == ["0", "0", J.num_text(sam)]
if k in "BXD":
assert cols[4:7] == [J.num_text(mam), J.num_text(lam), J.num_text(sam)]
if k == "X":
assert cols[3] == "시간"
assert sum(seen.values()) == sum(r["counts"].get(n, 0) for n in set(want.values()))
main = J.split_line(r["files"]["Main.Txt"].split(CRLF)[0])[0]
p = DbfTable(src.files["PRJLST.DBF"])
assert main == [p.get(0, "PNUM"), p.get(0, "PNAME"), "02"]
ini = r["files"]["Global.Ini"].decode("cp949")
assert "MAKER=STM" in ini and "JDL VERSION=3.3" in ini and f"TCOST={p.get(0, 'TCOST1')}" in ini
def test_unit_rules():
rec = {"CODE": "B00001", "NAME": "a", "SIZE": "", "UNIT": "l.s.", "EQCODE": "", "BQCODE": "", "UC": False}
rec |= {"L": 1.0, "M": 2.0, "E": 3.0, "AMT": [0] * 5, "PG": [""] * 5, "PG_J": "", "PG_S": ""}
assert J.split_line(code_line(rec, False, "")[1])[0][3] == "l.s.3"
assert J.split_line(code_line(rec | {"CODE": "W00001", "UNIT": "T%"}, False, "")[1])[0][3] == "T%*100"
assert J.split_line(code_line(rec | {"CODE": "S00001", "UNIT": " $ "}, False, "")[1])[0][3] == "$"
assert code_line(rec | {"CODE": "I00001"}, False, "") is None
def _f32(x):
return struct.unpack("<f", struct.pack("<f", x))[0]
def test_hal_rate_float32_factor():
"""할증 계수 4바이트 실수 — 70%·115% 에서 십진 계산보다 1원 적음."""
helper = Path(__file__).with_name("helper_stmate_kanjub_rea.cjs")
rows = [{"BDCODE1": "M00001", "_recno": 1, "SORTCODE": "1", "ENDCODE": "0"}]
for pct, base in ((70, 1000), (115, 1000), (107.5, 2000)):
want = math.trunc(base * _f32(pct / 100))
out = json.loads(
subprocess.check_output(
["node", str(helper)],
input=json.dumps({"mode": "halbase", "rows": rows, "pct": pct, "base": base}),
text=True,
encoding="utf-8",
)
)
assert out == [want] * 3, (pct, out)
assert math.trunc(1000 * _f32(0.7)) == 699 and math.trunc(1000 * _f32(1.15)) == 1149
def test_fifth_series_slot():
"""변경 4차 = 계열 5 — 저장 칸 5 를 차수로 읽음."""
assert stage_slots({"1_C_KANNO": 1, "5_C_KANNO": 2}) == [1, 5]