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>
This commit is contained in:
@@ -9,10 +9,12 @@ from sqlalchemy import select
|
||||
from app.deps import CurrentUser, DB
|
||||
from app.models.project import Project
|
||||
from app.models.daily_report import DailyReport, DailyReportPhoto
|
||||
from app.services.vision_service import classify_photo, analyze_safety
|
||||
from app.services.vision_service import classify_photo, analyze_safety, compare_with_drawing
|
||||
|
||||
router = APIRouter(prefix="/projects/{project_id}/vision", tags=["Vision AI"])
|
||||
|
||||
COMPARISON_TYPES = {"rebar": "철근 배근", "formwork": "거푸집", "general": "일반 비교"}
|
||||
|
||||
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp", "image/heic"}
|
||||
MAX_SIZE_MB = 10
|
||||
|
||||
@@ -91,6 +93,41 @@ async def classify_field_photo(
|
||||
return JSONResponse(content=result)
|
||||
|
||||
|
||||
@router.post("/compare-drawing")
|
||||
async def compare_drawing(
|
||||
project_id: uuid.UUID,
|
||||
db: DB,
|
||||
current_user: CurrentUser,
|
||||
field_photo: UploadFile = File(..., description="현장 사진"),
|
||||
drawing: UploadFile = File(..., description="설계 도면 이미지"),
|
||||
comparison_type: str = Form("rebar", description="rebar/formwork/general"),
|
||||
):
|
||||
"""
|
||||
Vision AI Level 3 — 설계 도면 vs 현장 사진 비교 보조 판독
|
||||
철근 배근, 거푸집 치수 등을 도면과 1차 비교합니다.
|
||||
⚠️ 최종 합격/불합격 판정은 현장 책임자가 합니다.
|
||||
"""
|
||||
await _get_project_or_404(project_id, db)
|
||||
|
||||
if comparison_type not in COMPARISON_TYPES:
|
||||
raise HTTPException(status_code=400, detail=f"comparison_type은 {list(COMPARISON_TYPES.keys())} 중 하나여야 합니다")
|
||||
|
||||
field_data = await field_photo.read()
|
||||
drawing_data = await drawing.read()
|
||||
|
||||
if len(field_data) > MAX_SIZE_MB * 1024 * 1024 or len(drawing_data) > MAX_SIZE_MB * 1024 * 1024:
|
||||
raise HTTPException(status_code=400, detail=f"파일 크기가 {MAX_SIZE_MB}MB를 초과합니다")
|
||||
|
||||
result = await compare_with_drawing(
|
||||
field_photo=field_data,
|
||||
drawing_image=drawing_data,
|
||||
comparison_type=comparison_type,
|
||||
field_media_type=field_photo.content_type or "image/jpeg",
|
||||
drawing_media_type=drawing.content_type or "image/jpeg",
|
||||
)
|
||||
return JSONResponse(content=result)
|
||||
|
||||
|
||||
@router.post("/safety-check")
|
||||
async def safety_check(
|
||||
project_id: uuid.UUID,
|
||||
|
||||
Reference in New Issue
Block a user