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:
sinmb79
2026-03-24 22:02:29 +09:00
parent 48f1027f08
commit 5a044a3882
17 changed files with 1350 additions and 8 deletions

View File

@@ -1,8 +1,8 @@
"""
EVMS (Earned Value Management System) 계산 서비스
PV, EV, AC, SPI, CPI 산출
EVMS (Earned Value Management System) — Phase 3 완전 자동화
PV, EV, AC, SPI, CPI 산출 + 공정 지연 예측 AI + 기성청구 자동 알림
"""
from datetime import date
from datetime import date, timedelta
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -10,6 +10,62 @@ from app.models.task import Task
from app.models.project import Project
async def predict_delay(
db: AsyncSession,
project_id,
spi: float,
planned_end: date | None,
snapshot_date: date,
) -> dict:
"""
SPI 기반 공기 지연 예측.
spi < 1 이면 지연, 남은 기간을 SPI로 나눠 예상 준공일 계산.
"""
if not planned_end or spi is None or spi <= 0:
return {"delay_days": None, "predicted_end": None, "status": "예측 불가"}
remaining_days = (planned_end - snapshot_date).days
if remaining_days <= 0:
return {"delay_days": 0, "predicted_end": str(planned_end), "status": "준공 예정일 경과"}
predicted_remaining = remaining_days / spi
predicted_end = snapshot_date + timedelta(days=int(predicted_remaining))
delay_days = (predicted_end - planned_end).days
if delay_days > 0:
status = f"{delay_days}일 지연 예상"
elif delay_days < -3:
status = f"{abs(delay_days)}일 조기 준공 예상"
else:
status = "정상 진행"
return {
"delay_days": delay_days,
"predicted_end": str(predicted_end),
"status": status,
}
async def compute_progress_claim(
total_budget: float,
actual_progress: float,
already_claimed_pct: float = 0.0,
) -> dict:
"""
기성청구 가능 금액 산출.
기성청구 가능 금액 = 총예산 × (실제 공정률 - 기청구 공정률)
"""
claimable_pct = max(0.0, actual_progress - already_claimed_pct)
claimable_amount = total_budget * (claimable_pct / 100)
return {
"actual_progress": actual_progress,
"already_claimed_pct": already_claimed_pct,
"claimable_pct": round(claimable_pct, 1),
"claimable_amount": round(claimable_amount, 0),
"claimable_amount_formatted": f"{claimable_amount:,.0f}",
}
def _clamp(v: float, lo: float, hi: float) -> float:
return max(lo, min(hi, v))