Files
Aislo/tools/stmate-test/api_jdl.py
T
eomsangdonandClaude Opus 5.5 47a04b6df8 feat(stmate-test): REA MCP(Ghidra) 파일 입출력 — JDL 탭 줄·묶음 내보내기·검사 · 구버전→Win8 변환 · 엑셀 역등록 구성 줄 · 자재 집계표 엑셀 · 그림 코드
- JDL 줄 = 탭 구분 + \x05/\x07 덧붙임(검사기 0x504d20 · 고정폭 표는 죽은 고리 — 34 §17.9 바로잡음) · 단가 목록 7종·Main·GongJong 머리·Global.Ini 상수 키 · zip 받기
- 구버전 판 가리기(Chk_Data 0x459280) · 7.x/DOS 표별 필드 대응 · B→X 조건 = 단위 X
- 엑셀 역등록 PRPT「속성」·PRIN「대상」·PROUT「저장」 = PR/RR 줄만 · 원본 BDQTY 4건 일치
- 자재 = 재료비 수량집계표 엑셀 내보내기(주요건설자재현황 시트)
- 그림 코드 = COSTN I 코드 IFORM · SIZE = 그림 클래스 이름
- 지식DB 37번

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

83 lines
2.8 KiB
Python

# -*- coding: utf-8 -*-
"""조달청 JDL TEXT 끼워 넣기 API — 묶음 내보내기(JSON · zip) · 묶음 검사(검사기 꼴).
근거: jdl_format.py · jdl_export.py 머리(ST_U_Convert Conv_ST8_Jodal · JDLCHK.Tab_33.dll)
원본 STC·폴더는 읽기만 · 서버에 쓰지 않음(zip 은 응답으로만).
"""
from __future__ import annotations
import base64
import io
import zipfile
from fastapi import APIRouter, HTTPException
from fastapi.responses import Response
from jdl_export import export_bundle
from jdl_format import COUNTS, check_lines, columns_of
from pydantic import BaseModel
router = APIRouter()
class JdlReq(BaseModel):
source_path: str
series: int | None = None
class JdlCheckReq(BaseModel):
files: dict[str, str] = {} # 이름 → base64(또는 data: 주소)
def _bundle(req: JdlReq) -> dict:
try:
return export_bundle(req.source_path, req.series)
except FileNotFoundError:
raise HTTPException(404, f"경로 없음: {req.source_path}")
except (ValueError, KeyError) as e:
raise HTTPException(400, str(e))
@router.post("/api/jdl/export")
def jdl_export(req: JdlReq):
"""묶음 요약 — 파일별 줄 수 · 검사 결과 · 첫 줄 열 보기 · 못 만든 것(미확인)."""
r = _bundle(req)
first = {}
for name, data in r["files"].items():
if name in COUNTS and data:
first[name] = columns_of(name, data.split(b"\r\n", 1)[0])
ini = r["files"].get("Global.Ini", b"").decode("cp949", "replace")
keep = ("series", "pclass", "counts", "checks", "missing", "warnings")
return {k: r[k] for k in keep} | {"first": first, "global_ini": ini}
@router.post("/api/jdl/export.zip")
def jdl_export_zip(req: JdlReq):
"""묶음 파일 모음 zip(전송용 · 원본 .JDL 압축 형식은 미확인)."""
r = _bundle(req)
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for name, data in r["files"].items():
z.writestr(name, data)
disp = "attachment; filename=JDL_TEXT.zip"
return Response(
content=buf.getvalue(), media_type="application/zip", headers={"Content-Disposition": disp}
)
@router.post("/api/jdl/check")
def jdl_check(req: JdlCheckReq):
"""받은 묶음 파일마다 검사기 꼴 검사 — 열 개수 · 코드 글자."""
out = {}
for name, b64 in req.files.items():
key = next((k for k in COUNTS if k.upper() == name.upper().rsplit("/", 1)[-1]), None)
if not key:
out[name] = ["검사 대상 아님"]
continue
try:
data = base64.b64decode(b64.split(",", 1)[-1])
except ValueError:
raise HTTPException(400, f"base64 아님: {name}")
out[name] = check_lines(key, data)
return {"checks": out}