query_generation.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """Formal query-generation preview API routes."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from uuid import UUID
  5. from fastapi import APIRouter, Depends, Query
  6. from acquisition.queries.builder import QueryBuildOptions, TREES, build_creation_query_batch
  7. from app.dependencies import _env_file, get_acquisition_repository
  8. from app.routes.acquisition import _model_dump
  9. from app.schemas import QuerySchema
  10. from core.config import Settings
  11. router = APIRouter(prefix="/api/query-generation", tags=["query-generation"])
  12. def _summary(generated: dict[str, Any]) -> dict[str, Any]:
  13. families = generated.get("families") or []
  14. return {
  15. "family_count": len(families),
  16. "family_keys": [family.get("key") for family in families],
  17. "query_count": sum(len(family.get("items") or []) for family in families),
  18. "kept_count": sum(
  19. 1
  20. for family in families
  21. for item in family.get("items") or []
  22. if item.get("keep", True)
  23. ),
  24. }
  25. def _normalize_light_query_detail(detail: dict[str, Any]) -> dict[str, Any]:
  26. media_by_item = {media["item_id"]: media for media in detail.get("media_assets") or []}
  27. classification_by_item = {row["item_id"]: row for row in detail.get("classifications") or []}
  28. decode_by_item = {row["item_id"]: row for row in detail.get("decode_summaries") or []}
  29. platforms: dict[str, dict[str, Any]] = {}
  30. for raw_job in detail.get("jobs") or []:
  31. job = _model_dump(raw_job)
  32. platform = job.get("platform")
  33. if not platform:
  34. continue
  35. platforms.setdefault(
  36. platform,
  37. {
  38. "platform": platform,
  39. "status": job.get("status") or "pending",
  40. "attempt_count": job.get("attempt_count"),
  41. "display_limit": job.get("display_limit"),
  42. "search_limit": job.get("search_limit"),
  43. "error_message": job.get("error_message"),
  44. "items": [],
  45. },
  46. )
  47. items: list[dict[str, Any]] = []
  48. for row in detail.get("items") or []:
  49. item = _model_dump(row)
  50. item_id = item["id"]
  51. classification = classification_by_item.get(item_id)
  52. payload = {
  53. "id": item_id,
  54. "platform": item.get("platform"),
  55. "title": item.get("title"),
  56. "raw_summary": item.get("raw_summary"),
  57. "status": item.get("status"),
  58. "content_mode": item.get("content_mode"),
  59. "metadata": item.get("metadata") or {},
  60. "classification": _model_dump(classification) if classification else None,
  61. "decode_summary": _model_dump(decode_by_item[item_id]) if item_id in decode_by_item else None,
  62. "media_assets": [],
  63. }
  64. if item_id in media_by_item:
  65. payload["media_assets"] = [_model_dump(media_by_item[item_id])]
  66. items.append(payload)
  67. platform = payload["platform"]
  68. group = platforms.setdefault(
  69. platform,
  70. {"platform": 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. "run": _model_dump(detail["run"]) if detail.get("run") else None,
  78. "jobs": [_model_dump(job) for job in detail.get("jobs") or []],
  79. "items": items,
  80. "platforms": platforms,
  81. }
  82. @router.get("/preview")
  83. def query_generation_preview(
  84. per: int = Query(default=0, ge=0, le=10000),
  85. batch_n: int = Query(default=0, ge=0, le=1000),
  86. enable_query_filter: bool = Query(default=False),
  87. ) -> dict[str, Any]:
  88. """Preview the currently active formal query families without writing DB rows."""
  89. settings = Settings.from_env(_env_file())
  90. generated = build_creation_query_batch(
  91. settings,
  92. tree_path=TREES,
  93. options=QueryBuildOptions(
  94. per=per,
  95. batch_n=batch_n,
  96. enable_query_filter=enable_query_filter,
  97. active_family_keys=("f1", "f2"),
  98. ),
  99. )
  100. generated["summary"] = _summary(generated)
  101. return generated
  102. @router.get("/latest-singleton")
  103. def latest_singleton_overview(repo: Any = Depends(get_acquisition_repository)) -> dict[str, Any]:
  104. """Return links from the query preview board to the latest real singleton run."""
  105. getter = getattr(repo, "get_latest_singleton_overview", None)
  106. if getter is None:
  107. return {"batch": None, "run": None, "queries": [], "decoded_items": []}
  108. return getter()
  109. @router.get("/latest/queries/{query_id}")
  110. def latest_query_detail(
  111. query_id: UUID,
  112. repo: Any = Depends(get_acquisition_repository),
  113. ) -> dict[str, Any]:
  114. """Return search material for one query in the latest real query board batch."""
  115. detail_getter = getattr(repo, "get_latest_query_result_list", None)
  116. if detail_getter is None:
  117. return {"query": None, "jobs": [], "items": [], "platforms": {}}
  118. return _normalize_light_query_detail(detail_getter(query_id))