"""처리 머신 보드 API — GET /api/queue/overview (plan ds-processing-ui-6an). 홈 stage 평면 테이블을 "머신 관점 보드(누가 일하나)"로 — 집계 로직은 services/queue_overview.py (순수 판정부 분리). 응답 스키마는 FE 와 계약 고정. 응답에 raw 모델명 노출 금지 — 머신 label 만. """ from typing import Annotated, Literal from fastapi import APIRouter, Depends from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from core.auth import get_current_user from core.database import get_session from models.user import User from services.queue_overview import build_overview router = APIRouter() class CurrentItem(BaseModel): """머신이 지금 처리 중인 문서 (최대 2건).""" document_id: int title: str stage: str class MachineCard(BaseModel): """머신 카드 — stage 귀속 합산 + 완료 실적(summarize 는 풀 분리) + state.""" key: Literal["gpu", "macmini", "macbook"] label: str state: Literal["active", "deferred", "idle"] stages: list[str] pending: int processing: int failed: int done_1h: int done_today: int deferred_pending: int current: list[CurrentItem] class SummarizeEta(BaseModel): """summarize 풀 ETA — done > inflow 일 때만 eta_minutes 산출.""" pending: int done_rate_1h: int inflow_rate_1h: int eta_minutes: int | None class TrendBucket(BaseModel): """summarize 24h 추이 버킷 — hour 는 KST "HH:00" 라벨.""" hour: str inflow: int done: int class Totals(BaseModel): """전 stage 합계.""" pending: int processing: int failed: int class StageRow(BaseModel): """단계별 현황 행 — '단계 상세' 패널용 (완료 가시화).""" stage: str pending: int processing: int failed: int done_today: int oldest_pending_age_sec: int | None class QueueOverviewResponse(BaseModel): machines: list[MachineCard] stages: list[StageRow] summarize_eta: SummarizeEta trend_24h: list[TrendBucket] totals: Totals @router.get("/overview", response_model=QueueOverviewResponse) async def get_queue_overview( user: Annotated[User, Depends(get_current_user)], session: Annotated[AsyncSession, Depends(get_session)], ): """머신 관점 처리 보드 + summarize ETA 집계 (라이브 계산, 신규 테이블 0)""" return QueueOverviewResponse.model_validate(await build_overview(session))