"""Formal acquisition API routes.""" from __future__ import annotations from collections import defaultdict from typing import Any from uuid import UUID from fastapi import APIRouter, Depends from acquisition.repositories.base import AcquisitionRepository from app.dependencies import get_acquisition_repository from app.schemas import ( AcquisitionRunSummarySchema, CandidateItemSchema, ItemClassificationSchema, MediaAssetSchema, QueryBatchSchema, QuerySchema, ) router = APIRouter(prefix="/api", tags=["acquisition"]) def _model_dump(row: Any) -> dict[str, Any]: if isinstance(row, dict): return dict(row) if hasattr(row, "model_dump"): return row.model_dump() return dict(row) def _normalize_query_detail(detail: dict[str, Any]) -> dict[str, Any]: media_by_item: dict[Any, list[dict[str, Any]]] = defaultdict(list) for media in detail.get("media_assets") or []: media_by_item[media["item_id"]].append(media) classification_by_item: dict[Any, dict[str, Any]] = {} for row in detail.get("classifications") or []: classification_by_item.setdefault(row["item_id"], row) decode_by_item: dict[Any, dict[str, Any]] = {} for row in detail.get("decode_summaries") or []: decode_by_item.setdefault(row["item_id"], row) items: list[dict[str, Any]] = [] platforms: dict[str, dict[str, Any]] = {} for raw_job in detail.get("jobs") or []: job = _model_dump(raw_job) platform = job.get("platform") if not platform: continue platforms.setdefault( platform, { "platform": platform, "status": job.get("status") or "pending", "attempt_count": job.get("attempt_count"), "display_limit": job.get("display_limit"), "search_limit": job.get("search_limit"), "error_message": job.get("error_message"), "items": [], }, ) for row in detail.get("items") or []: item = _model_dump(row) item["media_assets"] = [ MediaAssetSchema.model_validate(media).model_dump(mode="json") for media in media_by_item.get(item["id"], []) ] cls = classification_by_item.get(item["id"]) item["classification"] = ( ItemClassificationSchema.model_validate(cls).model_dump(mode="json") if cls else None ) item["decode_summary"] = _model_dump(decode_by_item[item["id"]]) if item["id"] in decode_by_item else None payload = CandidateItemSchema.model_validate(item).model_dump(mode="json") items.append(payload) group = platforms.setdefault( payload["platform"], {"platform": payload["platform"], "status": "done", "items": []}, ) if group.get("status") in {None, "pending"}: group["status"] = "done" group["items"].append(payload) return { "query": QuerySchema.model_validate(detail["query"]).model_dump(mode="json"), "jobs": [_model_dump(job) for job in detail.get("jobs") or []], "items": items, "platforms": platforms, } @router.get("/query-batches/{batch_id}") def query_batch( batch_id: UUID, repo: AcquisitionRepository = Depends(get_acquisition_repository), ) -> dict[str, Any]: batch = repo.get_query_batch(batch_id) queries = repo.list_queries_for_batch(batch_id) return { "batch": QueryBatchSchema.model_validate(batch).model_dump(mode="json"), "queries": [QuerySchema.model_validate(query).model_dump(mode="json") for query in queries], } @router.get( "/acquisition/runs/{run_id}/summary", response_model=AcquisitionRunSummarySchema, ) def acquisition_run_summary( run_id: UUID, repo: AcquisitionRepository = Depends(get_acquisition_repository), ) -> AcquisitionRunSummarySchema: return AcquisitionRunSummarySchema.model_validate(repo.get_run_summary(run_id)) @router.get("/acquisition/runs/{run_id}/queries/{query_id}") def acquisition_query_detail( run_id: UUID, query_id: UUID, repo: AcquisitionRepository = Depends(get_acquisition_repository), ) -> dict[str, Any]: return _normalize_query_detail(repo.get_query_detail(run_id=run_id, query_id=query_id))