o2o-infinith-backend/app/api/analysis.py

89 lines
4.1 KiB
Python

import logging
import uuid6
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, UploadFile, status
from common.deps import verify_api_key
from common.db import fetchone, insert_instagram_row, insert_facebook_row, insert_naver_blog_row, insert_youtube_row, insert_gangnam_unni_row, insert_analysis_run
from models.analysis import AnalysisCreate, AnalysisStartResponse, AnalysisStatusResponse
from models.file import FileListItem, FileType, FileUploadResponse
from models.status import AnalysisStatus
from services.pipeline import run_pipeline
from services.file import get_analysis_files_response, handle_analysis_file_upload, soft_delete_analysis_file
router = APIRouter(prefix="/api/analysis", tags=["analysis"], dependencies=[Depends(verify_api_key)])
logger = logging.getLogger(__name__)
@router.post("", status_code=status.HTTP_202_ACCEPTED, response_model=AnalysisStartResponse)
async def start_analysis(body: AnalysisCreate, background_tasks: BackgroundTasks):
logger.info("POST /api/analysis clinic_id=%s", body.clinic_id)
analysis_run_id = str(uuid6.uuid7())
hospital_id = body.clinic_id
# 사실 hospital과 owner_user_id 비교 후 검증이 필요한 거지만 일단 PoC 니까. 나중에 바꿉니다.
hospital = await fetchone(
"SELECT owner_user_id, url FROM hospital_baseinfo WHERE hospital_id = %s",
(hospital_id,),
)
if not hospital:
raise HTTPException(status_code=409, detail="Clinic not found")
ig_id = await insert_instagram_row(hospital_id, body.channels.instagram) if body.channels.instagram else None
fb_id = await insert_facebook_row(hospital_id, body.channels.facebook) if body.channels.facebook else None
nb_id = await insert_naver_blog_row(hospital_id, body.channels.naver_blog) if body.channels.naver_blog else None
yt_id = await insert_youtube_row(hospital_id, body.channels.youtube) if body.channels.youtube else None
gu_id = await insert_gangnam_unni_row(hospital_id, body.channels.gangnam_unni) if body.channels.gangnam_unni else None
analysis_run_id = await insert_analysis_run(
analysis_run_id, hospital_id, hospital["owner_user_id"],
ig_id, fb_id, nb_id, yt_id, gu_id,
)
background_tasks.add_task(run_pipeline, analysis_run_id)
return AnalysisStartResponse(
analysis_run_id=analysis_run_id,
clinic_id=hospital_id,
status=AnalysisStatus.DISCOVERING,
estimated_seconds=90,
poll_url=f"/api/analysis/{analysis_run_id}/status",
)
@router.post("/{run_id}/files", status_code=status.HTTP_201_CREATED, response_model=FileUploadResponse)
async def upload_analysis_run_file(
run_id: str,
file: UploadFile = File(..., description="업로드할 파일"),
file_type: FileType = Form(default=FileType.FILE, description="파일 타입 (image/video/audio/document/file)"),
) -> FileUploadResponse:
logger.info("POST /api/analysis/%s/files name=%s file_type=%s", run_id, file.filename, file_type.value)
return await handle_analysis_file_upload(run_id, file, file_type)
@router.get("/{run_id}/files", response_model=list[FileListItem])
async def get_analysis_run_files(run_id: str) -> list[FileListItem]:
logger.info("GET /api/analysis/%s/files", run_id)
return await get_analysis_files_response(run_id)
@router.delete("/{run_id}/files/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_analysis_run_file(run_id: str, file_id: int) -> None:
logger.info("DELETE /api/analysis/%s/files/%s", run_id, file_id)
await soft_delete_analysis_file(analysis_run_id=run_id, file_id=file_id)
return None
@router.get("/{run_id}/status", response_model=AnalysisStatusResponse)
async def get_analysis_status(run_id: str):
logger.info("GET /api/analysis/%s/status", run_id)
row = await fetchone("SELECT status FROM analysis_runs WHERE analysis_run_id = %s", (run_id,))
if not row:
raise HTTPException(status_code=404, detail="Run not found")
return AnalysisStatusResponse(
analysis_run_id=run_id,
status=AnalysisStatus(row["status"]),
progress=50.0,
current_step="",
channel_errors={},
completed_at=None,
)