first commit

This commit is contained in:
kyy
2025-10-27 09:18:24 +09:00
commit a5e23e8da5
20 changed files with 1283 additions and 0 deletions

127
router/ocr_api_router.py Normal file
View File

@@ -0,0 +1,127 @@
import json
import logging
from datetime import datetime
from celery.result import AsyncResult
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from tasks import (
celery_app,
run_ocr_pipeline, # 🔁 새로 만든 체인 함수 임포트
)
from utils.checking_keys import create_key
from utils.redis_utils import get_redis_client
router = APIRouter(prefix="/ocr", tags=["OCR"])
redis_client = get_redis_client()
logger = logging.getLogger(__name__)
@router.post("", summary="🔍 presigned URL 기반 비동기 OCR 처리")
async def ocr_endpoint(file_requests: dict):
"""
Presigned URL과 OCR 모델을 지정하여 비동기 OCR 작업을 요청합니다.
- **`file_url`**: OCR을 수행할 파일에 접근할 수 있는 Presigned URL
- **`filename`**: 원본 파일의 이름
- **`ocr_model`**: 사용할 OCR 모델 (`tesseract`, `pp-ocr`, `pp-structure`, `upstage` 중 선택)
요청이 접수되면, 작업 추적을 위한 `request_id`와 `task_id`가 즉시 반환됩니다.
"""
results = []
file_url = file_requests.get("file_url")
filename = file_requests.get("filename")
ocr_model = file_requests.get("ocr_model")
if not file_url or not filename:
raise HTTPException(status_code=400, detail="file_url, filename 필수")
request_id = create_key()
task_id = create_key()
run_ocr_pipeline(file_url, filename, request_id, task_id, ocr_model)
# Redis에 request_id → task_id 매핑 저장
try:
redis_client.hset("ocr_task_mapping", request_id, task_id)
except Exception as e:
raise HTTPException(status_code=500, detail=f"작업 정보 저장 오류: {str(e)}")
# 작업 로그 redis에 기록
try:
log_entry = {
"status": "작업 접수",
"timestamp": datetime.now().isoformat(),
"task_id": task_id,
"initial_file": filename,
}
redis_client.rpush(f"ocr_status:{request_id}", json.dumps(log_entry))
except Exception:
pass
# 작업을 등록한 후, 실제 OCR 처리를 기다리지 않고 즉시 응답
results.append(
{
"message": "OCR 작업이 접수되었습니다.",
"request_id": request_id,
"task_id": task_id,
"status_check_url": f"/ocr/progress/{request_id}",
}
)
return JSONResponse(content={"results": results})
# 실제 OCR 결과는 GET /ocr/progress/{request_id} 엔드포인트를 통해 별도로 조회
@router.get("/progress/{request_id}", summary="📊 OCR 진행 상태 및 결과 조회")
async def check_progress(request_id: str):
"""
`request_id`를 이용해 OCR 작업의 진행 상태와 최종 결과를 조회합니다.
- **`celery_status`**: Celery 작업의 현재 상태 (`PENDING`, `STARTED`, `SUCCESS`, `FAILURE` 등)
- **`progress_logs`**: 작업 접수부터 완료까지의 단계별 진행 상황 로그
- **`final_result`**: OCR 처리가 성공적으로 완료되었을 때, 추출된 텍스트와 좌표 정보가 포함된 최종 결과
"""
# request_id → task_id 매핑 확인
task_id = redis_client.hget("ocr_task_mapping", request_id)
if not task_id:
raise HTTPException(
status_code=404, detail=f"Meeting ID {request_id} 작업 없음"
)
# Celery 작업 상태 조회
result = AsyncResult(task_id, app=celery_app)
status = result.status
# 작업 로그 조회
try:
logs = redis_client.lrange(f"ocr_status:{request_id}", 0, -1)
parsed_logs = [json.loads(log) for log in logs]
except Exception as e:
parsed_logs = [
{
"status": "로그 가져오기 실패",
"error": str(e),
"timestamp": datetime.now().isoformat(),
}
]
# 최종 결과 Redis에서 조회
final_result = None
try:
result_str = redis_client.get(f"ocr_result:{task_id}")
if result_str:
final_result = json.loads(result_str)
except Exception as e:
final_result = {"error": f"결과 조회 실패: {str(e)}"}
return JSONResponse(
content={
"request_id": request_id,
"task_id": task_id,
"celery_status": status,
"progress_logs": parsed_logs,
"final_result": final_result,
}
)