# -*- 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]]}