Files
conai/backend/app/api/documents.py
sinmb79 5a044a3882 feat: Phase 3 구현 — 완전 자동화, 준공도서, Vision L3, 발주처 포털
EVMS 완전 자동화:
- 공기 지연 AI 예측 (SPI 기반 준공일 예측)
- 기성청구 가능 금액 자동 산출
- 매일 자정 EVMS 스냅샷 자동 생성 (APScheduler)
- 매일 07:00 GONGSA 아침 브리핑 자동 생성

준공도서 패키지:
- 준공 요약 + 품질시험 목록 + 검측 이력 + 인허가 현황 → ZIP 번들
- 준공 준비 체크리스트 API
- 4종 HTML 템플릿 (WeasyPrint PDF 출력)

Vision AI Level 3:
- 설계 도면 vs 현장 사진 비교 보조 판독 (Claude Vision)
- 철근 배근, 거푸집 치수 1차 분석

설계도서 파싱:
- PDF 이미지/텍스트에서 공종·수량·규격 자동 추출
- Pandoc HWP 출력 지원

발주처 전용 포털:
- 토큰 기반 읽기 전용 API
- 공사 현황 대시보드, 공정률 추이 차트

에이전트 협업 고도화:
- 협업 시나리오 (concrete_pour, excavation, weekly_report)
- GONGSA→PUMJIL→ANJEON 순차 처리

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 22:02:29 +09:00

103 lines
3.0 KiB
Python

"""
설계도서 파싱 + HWP 출력 API
"""
import uuid
from fastapi import APIRouter, UploadFile, File, HTTPException, Form
from fastapi.responses import Response
from pydantic import BaseModel
from sqlalchemy import select
from app.deps import CurrentUser, DB
from app.models.project import Project
from app.services.document_parser import (
parse_design_document_text,
parse_design_document_image,
convert_to_hwp,
)
from app.services.pdf_service import _render_html, _html_to_pdf
router = APIRouter(prefix="/projects/{project_id}/documents", tags=["설계도서"])
ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp"}
MAX_SIZE_MB = 20
async def _get_project_or_404(project_id: uuid.UUID, db: DB) -> Project:
r = await db.execute(select(Project).where(Project.id == project_id))
p = r.scalar_one_or_none()
if not p:
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다")
return p
@router.post("/parse-image")
async def parse_document_image(
project_id: uuid.UUID,
db: DB,
current_user: CurrentUser,
file: UploadFile = File(..., description="도면 이미지 (JPG/PNG)"),
):
"""
설계 도면 이미지 → 공종/수량/규격 자동 추출 (Claude Vision)
"""
await _get_project_or_404(project_id, db)
content_type = file.content_type or "image/jpeg"
if content_type not in ALLOWED_IMAGE_TYPES:
raise HTTPException(status_code=400, detail="JPG, PNG, WEBP 이미지만 지원합니다")
image_data = await file.read()
if len(image_data) > MAX_SIZE_MB * 1024 * 1024:
raise HTTPException(status_code=400, detail=f"파일 크기가 {MAX_SIZE_MB}MB를 초과합니다")
result = await parse_design_document_image(image_data, content_type)
return result
@router.post("/parse-text")
async def parse_document_text(
project_id: uuid.UUID,
db: DB,
current_user: CurrentUser,
file: UploadFile = File(..., description="설계도서 텍스트 파일 (TXT/MD)"),
):
"""
설계도서 텍스트 파일 → 공종/수량/규격 자동 추출
"""
await _get_project_or_404(project_id, db)
content = await file.read()
text = content.decode("utf-8", errors="replace")
result = await parse_design_document_text(text)
return result
class HWPRequest(BaseModel):
html_content: str
filename: str = "document.hwp"
@router.post("/export-hwp")
async def export_hwp(
project_id: uuid.UUID,
data: HWPRequest,
db: DB,
current_user: CurrentUser,
):
"""
HTML → HWP 변환 (Pandoc 필요)
보고서나 일보를 HWP 형식으로 내보냅니다.
"""
await _get_project_or_404(project_id, db)
try:
hwp_bytes = convert_to_hwp(data.html_content)
except RuntimeError as e:
raise HTTPException(status_code=500, detail=str(e))
return Response(
content=hwp_bytes,
media_type="application/octet-stream",
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{data.filename}"},
)