채점 서비스
채점 서비스
채점 서비스(grading-svc)는 시험지 이미지에서 수험생의 답안을 자동으로 인식·채점하는 OMR(Optical Mark Recognition) 서비스입니다. OpenCV 기반의 이미지 처리 파이프라인으로 답안지를 분석하고, 등급과 총점을 산출합니다. RabbitMQ 소비자로 동작하며, lumie-backend가 grading.omr-request 큐에 작업을 발행하면 처리 후 grading.omr.callback 라우팅 키로 콜백합니다.
기술 스택
| 라이브러리 | 버전 | 용도 |
|---|---|---|
| OpenCV | 4.8.1.78 | 이미지 전처리, 마킹 감지 |
| NumPy | 1.24.3 | 행렬 연산, 밀도 분석 |
| Pillow | 10.3.0 | 이미지 포맷 변환 |
| FastAPI | 0.115.0 | HTTP API 서버 |
| Uvicorn | 0.30.0 | ASGI 서버 |
| aio_pika | — | RabbitMQ 비동기 소비자 |
| httpx | 0.27.0 | 백엔드 콜백 HTTP 통신 |
| redis (aioredis) | — | 중복 처리 방지 (Redis Deduper) |
CPU 집약적인 연산(이미지 처리, 바코드 디코딩)은 FastAPI의 스레드 풀(asyncio.to_thread)에서 실행되어 이벤트 루프를 블로킹하지 않습니다.
RabbitMQ 메시지 계약
인바운드 큐: grading.omr-request
백엔드에서 발행하는 채점 요청 메시지 형식입니다.
class OMRCommand(BaseModel):
jobId: int
examId: int
tenantSlug: str
imageKey: str # MinIO 오브젝트 키
imageIndex: int # 이 이미지의 배치 내 순서 (0-based)
totalImages: int # 배치 전체 이미지 수
schemaVersion: int # 기본값 1
아웃바운드 라우팅 키: grading.omr.callback
성공·실패 모두 동일한 라우팅 키로 백엔드에 콜백합니다.
성공 페이로드
class SuccessCallbackPayload(BaseModel):
jobId: int
examId: int
tenantSlug: str
imageKey: str
imageIndex: int
totalImages: int
success: bool = True
error: None = None
phoneNumber: str
totalScore: int
grade: int
results: list[QuestionResult]
실패 페이로드
class FailureCallbackPayload(BaseModel):
jobId: int | None
examId: int | None
tenantSlug: str | None
imageKey: str | None
imageIndex: int | None
totalImages: int | None
success: bool = False
error: str
phoneNumber: None = None
totalScore: int = 0
grade: int = 0
results: None = None
처리 파이프라인
libs.common.mq.universal_process
RabbitMQ DLQ, 재시도, 콜백 생명주기는 libs/common/mq.py의 universal_process가 처리합니다. grading 소비자는 build_grade_handler 콜백을 주입하여 사용합니다. 에러 발생 시 메시지는 설정된 횟수만큼 재시도 후 DLQ로 이동합니다.
이미지 처리 파이프라인 (OpenCV)
1단계: 이미지 크기 조정
입력 이미지를 2480×3508px (A4 300dpi) 기준으로 리사이징합니다.