Files
Aislo/tools/stmate-test/api_file.py
T
eomsangdonandClaude Opus 5.5 e987602f24 feat(stmate-test): 무손실 저장 — 손대지 않은 저장 = 원본 STC 바이트 동일 · 고친 칸만 다시 씀
- DBF 고친 칸만 다시 씀(한글 깨짐 · 2칸을 1칸으로 · 3칸 87.745% 덮어쓰기 없앰) · 단가 → 내역 행 → 소계 → 직공비 다시 셈 · 행 끼우기·지우기
- SIxMemo(COSTN.SMT · PRJLST.SMT) 블록 구조 유지 · 넘치면 뒤에 붙이고 가리키는 값 갱신
- STC 다시 묶기(같은 파일은 원본 바이트) · 검사 · 자동 백업·복원 · 새 공사 · 복사등록(차수 인계) · 해당분 압축
- BDQTY 역변환(쓰기 준비) · 모듈 dbf_rw · dbf_memo · dbf_crypt · stc_archive · stc_save · stc_read · stc_project · api_file · scr_file
- 지킴 시험 test_stmate_file_roundtrip · file_project · file_payload — 실무 STC 7건 왕복

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

141 lines
4.1 KiB
Python

# -*- coding: utf-8 -*-
"""파일 입출력 끼워 넣기 API — STC 검사 · 복사등록 · 해당분만 압축 · 새 공사 · 백업 목록/복원.
근거: 24_모듈구조_전수.md(ST_U_Archive Chk_STC_Ver·ComP_Data · wM_D_Restore) · 08번 §2 · 16번 §4.6 · 27번 §2
쓰기는 tools/stmate-test/work_dbf/ 아래만(server.WORK_DBF_ROOT).
"""
from __future__ import annotations
import pathlib
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from stc_archive import build_stc, check_stc
from stc_project import (
copy_register,
list_backups,
new_project,
pack_selected,
safe_name,
write_output,
)
from stc_read import Source
router = APIRouter()
def _root() -> pathlib.Path:
import server # 실행 중 바뀔 수 있는 작업 폴더(시험은 임시 폴더로 바꿈)
return server.WORK_DBF_ROOT
def _src(path: str) -> Source:
try:
return Source(path)
except FileNotFoundError:
raise HTTPException(404, f"경로 없음: {path}")
except ValueError as e:
raise HTTPException(400, str(e))
def _guard(fn, *a, **k):
try:
return fn(*a, **k)
except (ValueError, KeyError) as e:
raise HTTPException(400, str(e))
class PathReq(BaseModel):
source_path: str
class CopyRegReq(BaseModel):
source_path: str
src_pcode: str = "11"
mode: str = "copy" # copy · change · contract
pname: str = ""
rates: dict = {} # contract — {"M": %, "L": %, "S": %}
exclude_recnos: list[int] = []
target_folder_name: str = ""
class PackReq(BaseModel):
source_path: str
pcode: str = "11"
target_name: str = ""
class NewPrjReq(BaseModel):
template_path: str
pname: str
pnum: str = ""
target_folder_name: str = ""
class RestoreReq(BaseModel):
backup_path: str
target_folder_name: str
@router.post("/api/file/check")
def api_file_check(req: PathReq):
"""Chk_STC_Ver 대응 — ZIP·필수 엔트리·DBF 머리·SMT 서명·판 추정."""
s = _src(req.source_path)
b = s.path.read_bytes() if s.kind == "stc" else build_stc([], s.files)
return {"kind": s.kind, **check_stc(b)}
@router.post("/api/file/copy-register")
def api_file_copy_register(req: CopyRegReq):
s = _src(req.source_path)
files, pcode, changes = _guard(
copy_register,
s.files,
req.src_pcode,
req.mode,
req.pname,
req.rates,
req.exclude_recnos,
)
target = req.target_folder_name or f"{s.path.stem}_복사{pcode}"
names = dict(s.names, **{f"BOQ{pcode}.DBF": f"BOQ{pcode}.DBF"})
res = write_output(_root(), target, files, names, s.entries)
return {**res, "new_pcode": pcode, "change_count": len(changes)}
@router.post("/api/file/pack")
def api_file_pack(req: PackReq):
"""해당분만 압축 — 고른 공사(PCODE)만 담은 STC 를 work_dbf/ 에 만듦."""
s = _src(req.source_path)
files = _guard(pack_selected, s.files, req.pcode)
name = safe_name(req.target_name or f"{s.path.stem}_{req.pcode}")
out = _root() / f"{name}.STC"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_bytes(build_stc(s.entries, files))
return {"ok": True, "saved_stc": str(out), "stc_size": out.stat().st_size}
@router.post("/api/file/new")
def api_file_new(req: NewPrjReq):
s = _src(req.template_path)
files = _guard(new_project, s.files, req.pname, req.pnum)
res = write_output(_root(), req.target_folder_name or req.pname, files, s.names, s.entries)
return res
@router.get("/api/file/backups")
def api_file_backups(name: str = ""):
return {"backups": list_backups(_root(), safe_name(name) if name else "")}
@router.post("/api/file/restore")
def api_file_restore(req: RestoreReq):
"""wM_D_Restore 대응 — 백업 STC 를 작업본으로 되돌림(지금 작업본은 다시 백업)."""
p = pathlib.Path(req.backup_path).resolve()
if _root().resolve() not in p.parents or not p.exists():
raise HTTPException(400, "작업 폴더 백업만 복원")
s = _src(str(p))
return write_output(_root(), req.target_folder_name, s.files, s.names, s.entries)