acquisition.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """Formal acquisition API routes."""
  2. from __future__ import annotations
  3. from collections import defaultdict
  4. from typing import Any
  5. from uuid import UUID
  6. from fastapi import APIRouter, Depends
  7. from acquisition.repositories.base import AcquisitionRepository
  8. from app.dependencies import get_acquisition_repository
  9. from app.schemas import (
  10. AcquisitionRunSummarySchema,
  11. CandidateItemSchema,
  12. ItemClassificationSchema,
  13. MediaAssetSchema,
  14. QueryBatchSchema,
  15. QuerySchema,
  16. )
  17. router = APIRouter(prefix="/api", tags=["acquisition"])
  18. def _model_dump(row: Any) -> dict[str, Any]:
  19. if isinstance(row, dict):
  20. return dict(row)
  21. if hasattr(row, "model_dump"):
  22. return row.model_dump()
  23. return dict(row)
  24. def _normalize_query_detail(detail: dict[str, Any]) -> dict[str, Any]:
  25. media_by_item: dict[Any, list[dict[str, Any]]] = defaultdict(list)
  26. for media in detail.get("media_assets") or []:
  27. media_by_item[media["item_id"]].append(media)
  28. classification_by_item: dict[Any, dict[str, Any]] = {}
  29. for row in detail.get("classifications") or []:
  30. classification_by_item.setdefault(row["item_id"], row)
  31. decode_by_item: dict[Any, dict[str, Any]] = {}
  32. for row in detail.get("decode_summaries") or []:
  33. decode_by_item.setdefault(row["item_id"], row)
  34. items: list[dict[str, Any]] = []
  35. platforms: dict[str, dict[str, Any]] = {}
  36. for raw_job in detail.get("jobs") or []:
  37. job = _model_dump(raw_job)
  38. platform = job.get("platform")
  39. if not platform:
  40. continue
  41. platforms.setdefault(
  42. platform,
  43. {
  44. "platform": platform,
  45. "status": job.get("status") or "pending",
  46. "attempt_count": job.get("attempt_count"),
  47. "display_limit": job.get("display_limit"),
  48. "search_limit": job.get("search_limit"),
  49. "error_message": job.get("error_message"),
  50. "items": [],
  51. },
  52. )
  53. for row in detail.get("items") or []:
  54. item = _model_dump(row)
  55. item["media_assets"] = [
  56. MediaAssetSchema.model_validate(media).model_dump(mode="json")
  57. for media in media_by_item.get(item["id"], [])
  58. ]
  59. cls = classification_by_item.get(item["id"])
  60. item["classification"] = (
  61. ItemClassificationSchema.model_validate(cls).model_dump(mode="json")
  62. if cls
  63. else None
  64. )
  65. item["decode_summary"] = _model_dump(decode_by_item[item["id"]]) if item["id"] in decode_by_item else None
  66. payload = CandidateItemSchema.model_validate(item).model_dump(mode="json")
  67. items.append(payload)
  68. group = platforms.setdefault(
  69. payload["platform"],
  70. {"platform": payload["platform"], "status": "done", "items": []},
  71. )
  72. if group.get("status") in {None, "pending"}:
  73. group["status"] = "done"
  74. group["items"].append(payload)
  75. return {
  76. "query": QuerySchema.model_validate(detail["query"]).model_dump(mode="json"),
  77. "jobs": [_model_dump(job) for job in detail.get("jobs") or []],
  78. "items": items,
  79. "platforms": platforms,
  80. }
  81. @router.get("/query-batches/{batch_id}")
  82. def query_batch(
  83. batch_id: UUID,
  84. repo: AcquisitionRepository = Depends(get_acquisition_repository),
  85. ) -> dict[str, Any]:
  86. batch = repo.get_query_batch(batch_id)
  87. queries = repo.list_queries_for_batch(batch_id)
  88. return {
  89. "batch": QueryBatchSchema.model_validate(batch).model_dump(mode="json"),
  90. "queries": [QuerySchema.model_validate(query).model_dump(mode="json") for query in queries],
  91. }
  92. @router.get(
  93. "/acquisition/runs/{run_id}/summary",
  94. response_model=AcquisitionRunSummarySchema,
  95. )
  96. def acquisition_run_summary(
  97. run_id: UUID,
  98. repo: AcquisitionRepository = Depends(get_acquisition_repository),
  99. ) -> AcquisitionRunSummarySchema:
  100. return AcquisitionRunSummarySchema.model_validate(repo.get_run_summary(run_id))
  101. @router.get("/acquisition/runs/{run_id}/queries/{query_id}")
  102. def acquisition_query_detail(
  103. run_id: UUID,
  104. query_id: UUID,
  105. repo: AcquisitionRepository = Depends(get_acquisition_repository),
  106. ) -> dict[str, Any]:
  107. return _normalize_query_detail(repo.get_query_detail(run_id=run_id, query_id=query_id))