- 보고서: 원본 STMATE 엑셀과 셀 대조(목록표·자재단가대비·중기·일위대가·산근 목록·설계내역서·원가계산서) · 조달청 XML 공식 XSD 검증 · PM_Prt 화면 - 제잡비: 원본 계산 순서 4형식(T/S/TA/SA) · 실무 7건 저장 결과키 전수 1원 · 요율표 조회 · 선택지 계산 · 제잡비·제비율 화면 - 식언어·수량: 함수·조건식·금액변수·문법 검사·수량변수·QCODE · 산근·구성·중기공식 편집 화면 · 순환 알림 - 화면: 원본 메뉴 체계·단축키 · 설계내역서 편집(행 넣기·지우기·이동·레벨·보관함) · 초기값 · 단가목록표 - 저장: 초기값(Start8x.Ini) 바뀐 키 저장 · 노임 환산 직종 = 초기값 CONLD · 산근·구성 결과에 적용단가 칸 채우지 않음(원본 0) - DBF 글 칸 앞 빈칸 보존 · 시험 도우미 표준입력 utf8(한글 조각 깨짐 원인) - 실무 5건 재계산 후 저장 = 바뀐 칸 0 · 시험 332건 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""보고서 내보내기 끼워 넣기 (api_report.py) — 시트 모형 → 엑셀 · CSV · 조달청 XML 검증.
|
||
|
||
근거: 지식DB STmate/old/20_분석 19 §3 · 20 §3 · 30(조달청 XSD).
|
||
시트 모형은 화면(scr_report.js)이 report_*.js 로 만들어 보냄 — 계산은 브라우저 엔진과 같은 코드.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pathlib
|
||
import urllib.parse
|
||
|
||
from fastapi import APIRouter, HTTPException
|
||
from fastapi.responses import Response
|
||
from pydantic import BaseModel
|
||
|
||
from report_xlsx import build_csv, build_report_xlsx
|
||
|
||
HERE = pathlib.Path(__file__).resolve().parent
|
||
XSD = (
|
||
HERE.parent.parent
|
||
/ "resources/knowledge/original/경쟁사 프로그램/STmate/old/30_원자료/조달청_공사원가호환규정.xsd"
|
||
)
|
||
router = APIRouter()
|
||
|
||
|
||
class ReportReq(BaseModel):
|
||
sheets: list[dict] = []
|
||
filename: str = "STMATE_보고서.xlsx"
|
||
|
||
|
||
class XmlReq(BaseModel):
|
||
xml: str
|
||
|
||
|
||
def _disposition(name: str) -> str:
|
||
return f"attachment; filename*=UTF-8''{urllib.parse.quote(name)}"
|
||
|
||
|
||
@router.post("/api/report/xlsx")
|
||
def api_report_xlsx(req: ReportReq):
|
||
if not req.sheets:
|
||
raise HTTPException(status_code=400, detail="시트 모형 없음")
|
||
return Response(
|
||
content=build_report_xlsx(req.sheets),
|
||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
headers={"Content-Disposition": _disposition(req.filename)},
|
||
)
|
||
|
||
|
||
@router.post("/api/report/csv")
|
||
def api_report_csv(req: ReportReq):
|
||
if not req.sheets:
|
||
raise HTTPException(status_code=400, detail="시트 모형 없음")
|
||
body = "" + build_csv(req.sheets[0])
|
||
name = req.filename if req.filename.endswith(".csv") else req.filename + ".csv"
|
||
return Response(
|
||
content=body.encode("utf-8"),
|
||
media_type="text/csv; charset=utf-8",
|
||
headers={"Content-Disposition": _disposition(name)},
|
||
)
|
||
|
||
|
||
@router.post("/api/report/validate-pps")
|
||
def api_validate_pps(req: XmlReq):
|
||
"""조달청 호환규정 XSD(V1.0.5) 검증 — 위반 줄을 돌려줌."""
|
||
from lxml import etree
|
||
|
||
schema = etree.XMLSchema(etree.parse(str(XSD)))
|
||
try:
|
||
doc = etree.fromstring(req.xml.encode("utf-8"))
|
||
except etree.XMLSyntaxError as e:
|
||
return {"ok": False, "errors": [str(e)]}
|
||
ok = schema.validate(doc)
|
||
return {"ok": ok, "errors": [str(e) for e in list(schema.error_log)[:20]]}
|