Skip to main content

리포트 서비스

리포트 서비스

리포트 서비스(report-svc)는 학생의 시험 결과 데이터를 수집하고, Jinja2 HTML 템플릿으로 렌더링한 뒤 Playwright Chromium을 통해 고품질 JPG 이미지로 출력합니다. RabbitMQ report.generation-request 큐를 소비하고, 결과를 report.generation.callback 라우팅 키로 백엔드에 콜백합니다. 하위 호환성을 위한 동기식 HTTP 엔드포인트도 함께 제공합니다.

RabbitMQ 메시지 계약

인바운드 큐: report.generation-request

백엔드에서 발행하는 리포트 생성 요청 메시지 형식입니다.

class ReportCommand(BaseModel):
jobId: int
examId: int
studentId: int
tenantSlug: str
reportIndex: int # 이 리포트의 배치 내 순서 (0-based)
totalReports: int # 배치 전체 리포트 수
schemaVersion: int # 기본값 1

아웃바운드 라우팅 키: report.generation.callback

성공·실패 모두 동일한 라우팅 키로 백엔드에 콜백합니다.

성공 페이로드

class SuccessCallbackPayload(BaseModel):
jobId: int
examId: int
studentId: int
tenantSlug: str
reportIndex: int
totalReports: int
success: bool = True
reportBytes: str # base64 인코딩된 JPG 바이트

실패 페이로드

class FailureCallbackPayload(BaseModel):
jobId: int | None
examId: int | None
studentId: int | None
tenantSlug: str | None
reportIndex: int | None
totalReports: int | None
success: bool = False
error: str
reportBytes: None = None

기술 스택

라이브러리버전용도
Playwright1.50.0Headless Chromium 기반 HTML → JPG 변환
Jinja23.1.3HTML 템플릿 렌더링
httpx0.27.0내부 서비스 비동기 HTTP 통신
FastAPI0.115.0HTTP API 서버
Uvicorn0.30.0ASGI 서버
aio_pikaRabbitMQ 비동기 소비자

한국어 폰트는 Noto CJK 패밀리를 사용하며, 컨테이너에 사전 설치됩니다.

HTTP API 엔드포인트

POST /v1/reports/students/\{student_id\}/exams/\{exam_id\} — 동기식 리포트 생성

하위 호환성을 위해 유지되는 동기식 엔드포인트입니다. 특정 학생의 특정 시험에 대한 리포트를 JPG 이미지로 반환합니다.

요청 헤더

헤더설명
X-Tenant-Slug테넌트 식별자

경로 파라미터

파라미터설명
student_id학생 고유 ID (UUID)
exam_id시험 고유 ID (UUID)

응답

  • Content-Type: image/jpeg
  • 출력 해상도: 794×1123px (기본 뷰포트) × 4배 스케일 = 3176×4492px

데이터 수집 플로우

리포트 생성은 4단계 파이프라인으로 구성됩니다. MQ 경로와 HTTP 동기식 경로 모두 동일한 파이프라인을 사용합니다.

Phase 1: 병렬 데이터 조회

httpx를 사용한 비동기 병렬 요청으로 두 서비스를 동시에 호출합니다. 모든 호출은 HMAC 서명과 X-Tenant-Slug 헤더를 포함합니다.

조회 대상내부 경로내용
학생 기본 정보/internal/students/{id}이름, 학년, 소속 반 등
시험 통계 정보/internal/exams/{id}/stats전체 평균, 표준편차, 등급 컷 등

Phase 2: 순차 데이터 조회

Phase 1에서 반환된 result_id를 사용하여 문항별 세부 결과를 조회합니다. 이 단계는 Phase 1이 완료된 후에만 실행 가능합니다.

조회 대상내부 경로내용
문항별 결과/internal/exams/{id}/results/{result_id}선택 답안, 정오, 배점, 정답률 등

Phase 3: HTML 렌더링

수집된 데이터를 Jinja2 템플릿에 주입하여 HTML 문서를 생성합니다. 템플릿은 리포트 레이아웃, 차트 데이터, 문항 분석 영역을 포함합니다.

Phase 4: HTML → JPG 변환

Playwright의 Headless Chromium으로 렌더링된 HTML을 캡처하여 JPG로 변환합니다.

Chromium 설정

# 컨테이너 환경에서 필요한 플래그
--no-sandbox

# 뷰포트 설정
width: 794 # A4 너비 (96dpi 기준)
height: 1123 # A4 높이
device_scale_factor: 4 # 고해상도 출력

Chromium 브라우저 인스턴스는 서비스 **시작 시 미리 워밍업(pre-warm)**되어 첫 번째 요청에서도 지연 없이 응답합니다.

환경 변수

변수기본값설명
RABBITMQ_HOSTlocalhostRabbitMQ 호스트
RABBITMQ_PORT5672RabbitMQ 포트
RABBITMQ_QUEUEreport.generation-request소비 큐 이름
RABBITMQ_PREFETCH_COUNT2동시 처리 메시지 수
LUMIE_BACKEND_URL— (필수)백엔드 콜백 및 데이터 조회 URL
LUMIE_INTERNAL_HMAC_SECRET— (필수)HMAC 서명 비밀키
MINIO_ENDPOINTlocalhost:9000MinIO 엔드포인트
MINIO_BUCKETlumieMinIO 버킷

Kubernetes 리소스

resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "2Gi"

Playwright Chromium은 메모리를 상당히 소비하므로 메모리 한도를 충분히 확보해야 합니다. prefetch_count=2는 Playwright Chromium 스폰 비용을 반영한 보수적인 값입니다.

컨테이너 설정 주의사항

컨테이너 환경에서는 반드시 다음 사항을 확인해야 합니다.

  1. --no-sandbox 플래그: Chromium은 컨테이너 내에서 샌드박스 없이 실행되어야 합니다. root 권한 없이도 동작하도록 설정되어 있습니다.
  2. Noto CJK 폰트: 한국어 텍스트 렌더링을 위해 컨테이너 이미지에 Noto CJK 폰트가 포함되어야 합니다.
  3. Playwright 브라우저 사전 설치: Docker 빌드 시 playwright install chromium 명령으로 브라우저를 설치합니다.

헬스체크

GET /health
{ "status": "ok" }

관련 문서