Files
Aislo/M02_MasterTemplete/M02_San_Bdqty.py
T
eomsangdonandClaude Opus 5.5 53932e964f feat(828-1): STmate 원파일 구성표(BDQTY) 읽기 — 거울 · 스스로 검사 · 물러남 · 일위 구성 줄 꼴 · 낙찰률 줄 2 칸
- M02_San_Bdqty = tools/stmate-test/bdqty.py 거울(믿을 칸만 · 검사 ① 자원 표 ② 부모 머리 ③ DEVD1)
- M02_San_Ilwi = 일위 구성 줄 꼴 · 다시 셈(smt_eval evaluateComposition 짝 + 닫기 뒤 전체 % B00038) · 중기 구성 · HP 호표
- M02_San_Stc.read 일위 = 구성 줄(7 건 B 216 · X 다시 셈 = 저장 단가 전부) · 낙찰률 줄(NAGCD > 0) = 2 칸 원단가
- test_810_1_stc 낡은 칸 고침: 거울 판 smt_parser 가 글 칸 앞 빈칸을 남김(3d8fddfc3) → 대조 때 걷음 · 일위 = 구성 줄 · 산근 = 계(계약 전)

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

120 lines
6.3 KiB
Python

"""경쟁사 STmate 원파일 구성표(BDQTY.DBF) 읽기(PLAN 828-1 · 요구 813 · 814 · 읽기만 · 서버 단독).
짝 = `tools/stmate-test/bdqty.py`(`decode_bdqty` · 변환 표 그대로 · 시험 `test_828_1_stc_composition.py` 가
실무 STC 일곱에서 두 답 1:1 대조) · 새로 암호를 풀지 않음 · 쓰기(`dbf_crypt.py`)는 옮기지 않음.
읽는 칸 = 믿을 칸만(`TRUSTED` — 부모 · 자식 코드 · 1 차 수량 · 나누기 · 존재 · 율 밑줄 표시 · 율 밑수 ·
대상 비목 · 관급 · BL_AMT). 못 믿는 칸(QTY2~4 · BIMOK · EQCODE · X_CODE · NAME2 · SIZE2 · 글자 칸)은 안 읽음.
스스로 검사(`check`) — ① 자식 코드 = 자원 표에 있음 · 제어 코드(PR · RR · T · C · 빈) ② 부모 머리 ∈ B · X · D
③ DEVD1 ∈ {F · 빈} — 하나라도 어긋나면 구성표를 안 씀(까닭 글 · `M02_San_Stc.read` 가 옛 표지 길로 물러남).
"""
from __future__ import annotations
import re
import struct
_TABLE = (
"1A3.53 2A0.238 3A7.233 4A4.228 5A7.17 6A3.133 7A7.33 8A5.43 9A6.170 10A5.123 11A4.156 "
"12A1.167 133X4.229 134X0.87 135A7.198 136A5.155 137A2.254 138A7.217 139A5.123 140A7.193 "
"141B1.167 142A4.132 143A3.133 144B0.8 145B3.150 146B1.155 147A1.63 148B7.226 149A6.130 "
"150A3.253 151B2.85 152A5.235 153B6.91 154X0.231 155X0.231 156X4.0 157X1.223 158X6.130 "
"159A5.27 160B3.245 161B1.107 162X5.200 163B3.230 164B5.74 165X6.94 166X1.151 167B0.207 "
"168B5.10 169B7.206 170A0.166 171A7.198 172X6.98 173X1.63 174X0.103 175X0.233 176X1.223 "
"177X3.107 178A3.173 179B0.120 180X5.145 181B5.24 182X2.225 183A7.119 184X0.144 185A2.239 "
"186X4.27 187X4.180 188X4.229 189A5.241 190A2.41 191A5.232 192A6.154 193X3.143 194A7.70 "
"195X2.62 196X3.6 197X0.215 198A0.232 199B1.207 200X5.136 201A5.179 202B7.78 203X2.33 "
"204A6.154 205B3.214 206B5.74 207A1.176 208B6.174 209B6.110 210X5.249 211A6.154 212B0.194 "
"213B7.99 214A2.174 215A5.107 216B7.206 217X7.228 218B1.239 219B3.181 220X7.173 221A2.209 "
"222A1.155 223A1.155 224A1.191 225B2.62 226X4.128 227A6.209 228B6.64 229X0.105 230X6.167 "
"231X0.215 232A3.128 233B6.107 234A2.209 235A6.90 236B5.154 237A5.107 238A1.185 239B6.20 "
"240B2.85 241X1.22 242X1.166 243B7.66 244B7.114 245X7.22 246X3.167 247A2.70 248B3.167 "
"249B6.168 250A1.39 251X2.0 252A5.147 253A3.85 254A6.106 255A3.93 256X0.208 257A7.65 "
"258A5.203 259A7.41 260A3.117 261A6.34 262A3.181 263B5.101 264A7.177 265A7.1 266A1.55 "
"267B1.151 268B7.133 269A0.175 270X6.98 271B1.151 272B1.139 273A5.203 274A7.113 275A2.238 "
"276X7.193 277A6.74 278A5.83 279A7.97 280B5.154 281B3.174 282A2.70 283A7.233 284B6.104 "
"285B5.24 286X1.63 287B2.94 288B5.92 289B4.201 290A3.197 291B6.168 292B1.203 293B5.26 "
"294A5.123 295A3.253 296X6.178 297X2.241 298X5.136 299A3.197 300A4.12 301B5.89 302B6.15 "
"303A3.183 304X6.167 305B0.112 306B5.209 307A4.165 308X0.215 309B7.142 310B1.49 311A3.253 "
"312A2.74 313X7.230 314X2.161 315A1.231 316B3.165 317A0.230 318B7.14 319A4.115 320A3.128 "
"321B5.74 322A2.41 323A1.159 324A4.100 325A7.17 326B3.4 327X6.159 328A0.175 329B1.239 "
"330A0.38 331B1.239 332X1.93 333X2.58 334X5.63 335X3.96 336X0.247 337X6.183 338X1.102 "
"339A0.150 340X0.96 341X2.55 342A5.125 343A2.209 344B4.137 345A3.119 346B6.247 347X5.54 "
"348A6.26 349X2.225 350X5.127 351A5.177 352B2.103 353A6.99 354B2.85 355B6.183 356X6.167 "
"357X3.136 358A6.238 359A6.154 360X1.38 361A4.124 362X0.41 374A1.239 375A4.92 376X6.55 "
"377B2.62 378X7.49 379B7.226 380B2.129 381A7.145 382X1.214 383A6.94 384B4.222 385B5.152 "
"386A7.17 387B2.125 388A3.64 389A6.222 390A7.198 391A1.119 392B1.99 393A3.213 394A5.134 "
"395B5.81 396A7.198 397B4.95 398A5.107 399B7.178 400X2.55 401X5.74 402B3.174 403A0.134 "
"404A3.53 405A1.191 406X4.229 407X1.63 408X5.182 409B7.178 410B6.42 411B6.137 412A2.218 "
"413A5.235 414B7.162 "
)
#: 믿을 칸(분석 813 1-1 · 엑셀 메타 대조 100 %)
TRUSTED = ("BDCODE", "CODE", "QTY1", "DEVD1", "EXIST1", "PRPT", "PRIN", "PROUT", "KWNGP", "BL_AMT")
_CONTROL = re.compile(r"^(?:PR|RR|[TC]\d)?$")
def _params() -> dict[int, tuple[str, int, int]]:
out = {}
for tok in _TABLE.split():
i = next(j for j, ch in enumerate(tok) if ch in "ABX")
r, k = tok[i + 1 :].split(".")
out[int(tok[:i])] = (tok[i], int(r), int(k))
return out
PARAMS = _params()
def _ror(v: int, r: int) -> int:
return ((v >> r) | (v << (8 - r))) & 0xFF if r else v
def _dec(c: int, p: tuple[str, int, int]) -> int:
kind, r, k = p
if kind == "A":
return _ror((c - k) & 0xFF, r)
if kind == "B":
return (_ror(c, r) - k) & 0xFF
return _ror(c ^ k, r)
def decode(b: bytes) -> list[dict[str, str]]:
"""BDQTY.DBF → 행(레코드 차례) · 믿을 칸 가운데 표 안 칸만 · 글 = CP949 strip."""
if len(b) < 32:
return []
n, hlen, rlen = struct.unpack_from("<IHH", b, 4)
fields, o, off = [], 32, 1
while o + 17 <= min(hlen, len(b)) and b[o] != 0x0D:
name = b[o : o + 11].split(b"\x00")[0].decode("ascii", "ignore")
if name in TRUSTED:
fields.append((name, off, b[o + 16]))
off += b[o + 16]
o += 32
rows = []
for i in range(n):
rec = b[hlen + i * rlen : hlen + (i + 1) * rlen]
if len(rec) < rlen:
break
row = {}
for name, foff, ln in fields:
span = range(foff, foff + ln)
if all(j in PARAMS for j in span):
raw = bytes(_dec(rec[j], PARAMS[j]) for j in span)
row[name] = raw.decode("cp949", "replace").strip()
rows.append(row)
return rows
def check(rows: list[dict[str, str]], codes: set[str]) -> str | None:
"""스스로 검사 ① ~ ③ — 어긋남 까닭 글(첫 것) · 통과 = None."""
if not rows:
return "구성 행 없음"
for r in rows:
parent, child = r.get("BDCODE") or "", r.get("CODE") or ""
if parent[:1] not in ("B", "X", "D"):
return f"부모 코드 머리 다름 「{parent[:8]}」"
if child not in codes and not _CONTROL.match(child):
return f"자식 코드가 자원 표에 없음 「{child[:8]}」"
if (r.get("DEVD1") or "") not in ("F", ""):
return f"나누기 칸 값 다름 「{(r.get('DEVD1') or '')[:4]}」"
return None