import uuid from datetime import date from fastapi import APIRouter, HTTPException, status from fastapi.responses import Response from sqlalchemy import select, func from app.deps import CurrentUser, DB from app.models.report import Report, ReportType from app.models.daily_report import DailyReport from app.models.weather import WeatherAlert from app.models.project import Project from app.schemas.report import ReportGenerateRequest, ReportResponse from app.services.report_gen import generate_weekly_report, generate_monthly_report from app.services.pdf_service import generate_report_pdf router = APIRouter(prefix="/projects/{project_id}/reports", tags=["공정보고서"]) # Report schemas (inline for simplicity) from pydantic import BaseModel from app.models.report import ReportType, ReportStatus class ReportGenerateRequest(BaseModel): report_type: ReportType period_start: date period_end: date class ReportResponse(BaseModel): id: uuid.UUID project_id: uuid.UUID report_type: ReportType period_start: date period_end: date ai_draft_text: str | None status: ReportStatus pdf_s3_key: str | None model_config = {"from_attributes": True} async def _get_project_or_404(project_id: uuid.UUID, db: DB) -> Project: result = await db.execute(select(Project).where(Project.id == project_id)) p = result.scalar_one_or_none() if not p: raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다") return p def _compute_overall_progress(tasks) -> float: if not tasks: return 0.0 total = sum(t.progress_pct for t in tasks) return total / len(tasks) @router.get("", response_model=list[ReportResponse]) async def list_reports(project_id: uuid.UUID, db: DB, current_user: CurrentUser): result = await db.execute( select(Report) .where(Report.project_id == project_id) .order_by(Report.period_start.desc()) ) return result.scalars().all() @router.post("/generate", response_model=ReportResponse, status_code=status.HTTP_201_CREATED) async def generate_report(project_id: uuid.UUID, data: ReportGenerateRequest, db: DB, current_user: CurrentUser): """AI-generate weekly or monthly report draft.""" project = await _get_project_or_404(project_id, db) # Get daily reports in period daily_result = await db.execute( select(DailyReport).where( DailyReport.project_id == project_id, DailyReport.report_date >= data.period_start, DailyReport.report_date <= data.period_end, ).order_by(DailyReport.report_date) ) daily_reports = daily_result.scalars().all() # Get tasks for progress from app.models.task import Task tasks_result = await db.execute(select(Task).where(Task.project_id == project_id)) tasks = tasks_result.scalars().all() overall_progress = _compute_overall_progress(tasks) if data.report_type == ReportType.WEEKLY: # Get weather alerts in period alerts_result = await db.execute( select(WeatherAlert).where( WeatherAlert.project_id == project_id, WeatherAlert.alert_date >= data.period_start, WeatherAlert.alert_date <= data.period_end, ) ) weather_alerts = alerts_result.scalars().all() ai_text, content_json = await generate_weekly_report( project_name=project.name, period_start=str(data.period_start), period_end=str(data.period_end), daily_reports=daily_reports, overall_progress_pct=overall_progress, weather_alerts=weather_alerts, ) else: ai_text, content_json = await generate_monthly_report( project_name=project.name, period_start=str(data.period_start), period_end=str(data.period_end), daily_reports=daily_reports, overall_progress_pct=overall_progress, ) report = Report( project_id=project_id, report_type=data.report_type, period_start=data.period_start, period_end=data.period_end, content_json=content_json, ai_draft_text=ai_text, ) db.add(report) await db.commit() await db.refresh(report) return report @router.get("/{report_id}", response_model=ReportResponse) async def get_report(project_id: uuid.UUID, report_id: uuid.UUID, db: DB, current_user: CurrentUser): result = await db.execute(select(Report).where(Report.id == report_id, Report.project_id == project_id)) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=404, detail="보고서를 찾을 수 없습니다") return report @router.get("/{report_id}/pdf") async def download_report_pdf(project_id: uuid.UUID, report_id: uuid.UUID, db: DB, current_user: CurrentUser): """공정보고서 PDF 다운로드""" r = await db.execute(select(Report).where(Report.id == report_id, Report.project_id == project_id)) report = r.scalar_one_or_none() if not report: raise HTTPException(status_code=404, detail="보고서를 찾을 수 없습니다") project = await _get_project_or_404(project_id, db) pdf_bytes = generate_report_pdf(report, project) filename = f"report_{report.report_type.value}_{report.period_start}.pdf" return Response(content=pdf_bytes, media_type="application/pdf", headers={"Content-Disposition": f"attachment; filename*=UTF-8''{filename}"}) @router.delete("/{report_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_report(project_id: uuid.UUID, report_id: uuid.UUID, db: DB, current_user: CurrentUser): result = await db.execute(select(Report).where(Report.id == report_id, Report.project_id == project_id)) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=404, detail="보고서를 찾을 수 없습니다") await db.delete(report) await db.commit()