decode.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. """Formal decode API routes."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from uuid import UUID
  5. from fastapi import APIRouter, Body, Depends, HTTPException
  6. from app.dependencies import _env_file, get_acquisition_repository, get_decode_repository
  7. from app.schemas import (
  8. CandidateItemSchema,
  9. DecodeResultSchema,
  10. IngestItemRequest,
  11. IngestItemResponse,
  12. IngestItemResultSchema,
  13. IngestRecordSchema,
  14. KnowledgeParticleSchema,
  15. MediaAssetSchema,
  16. PayloadDraftSchema,
  17. ScopeResultSchema,
  18. )
  19. from core.config import IngestApiConfig
  20. from decode_content.ingest import KnowledgeIngestClient, ingest_payload_draft
  21. router = APIRouter(prefix="/api/decode", tags=["decode"])
  22. @router.get("/items/{item_id}/result", response_model=DecodeResultSchema)
  23. def decode_result(
  24. item_id: UUID,
  25. repo: Any = Depends(get_decode_repository),
  26. ) -> DecodeResultSchema:
  27. getter = getattr(repo, "get_decode_result_for_item", None)
  28. if getter is None:
  29. raise HTTPException(status_code=501, detail="decode result reader is not implemented")
  30. return DecodeResultSchema.model_validate(getter(item_id))
  31. @router.get("/items/{item_id}/detail")
  32. def decode_item_detail(
  33. item_id: UUID,
  34. acquisition_repo: Any = Depends(get_acquisition_repository),
  35. decode_repo: Any = Depends(get_decode_repository),
  36. ) -> dict[str, Any]:
  37. try:
  38. item = acquisition_repo.get_candidate_item(item_id)
  39. media = acquisition_repo.list_media_assets_for_item(item_id)
  40. except Exception as exc:
  41. raise HTTPException(status_code=404, detail=f"candidate item not found: {exc}") from exc
  42. try:
  43. result = decode_repo.get_decode_result_for_item(item_id)
  44. except Exception as exc:
  45. result = None
  46. list_particles = getattr(decode_repo, "list_knowledge_particles", None)
  47. list_scopes = getattr(decode_repo, "list_scope_results", None)
  48. list_payloads = getattr(decode_repo, "list_payload_drafts", None)
  49. list_ingests = getattr(decode_repo, "list_ingest_records", None)
  50. raw_item = item.model_dump() if hasattr(item, "model_dump") else dict(item)
  51. item_payload = CandidateItemSchema.model_validate(
  52. {
  53. **raw_item,
  54. "media_assets": [
  55. MediaAssetSchema.model_validate(row).model_dump(mode="json")
  56. for row in media
  57. ],
  58. }
  59. ).model_dump(mode="json")
  60. return {
  61. "item": item_payload,
  62. "decode_result": (
  63. DecodeResultSchema.model_validate(result).model_dump(mode="json")
  64. if result is not None
  65. else None
  66. ),
  67. "knowledge_particles": [
  68. KnowledgeParticleSchema.model_validate(row).model_dump(mode="json")
  69. for row in (list_particles(item_id=item_id) if list_particles else [])
  70. ],
  71. "scope_results": [
  72. ScopeResultSchema.model_validate(row).model_dump(mode="json")
  73. for row in (list_scopes(item_id=item_id) if list_scopes else [])
  74. ],
  75. "payload_drafts": [
  76. PayloadDraftSchema.model_validate(row).model_dump(mode="json")
  77. for row in (list_payloads(item_id=item_id) if list_payloads else [])
  78. ],
  79. "ingest_records": [
  80. IngestRecordSchema.model_validate(row).model_dump(mode="json")
  81. for row in (list_ingests(item_id=item_id) if list_ingests else [])
  82. ],
  83. }
  84. @router.post("/items/{item_id}/jobs")
  85. def enqueue_decode_job(
  86. item_id: UUID,
  87. repo: Any = Depends(get_decode_repository),
  88. ) -> dict[str, Any]:
  89. create = getattr(repo, "create_decode_job", None)
  90. if create is None:
  91. raise HTTPException(status_code=501, detail="decode job writer is not implemented")
  92. job = create(item_id=item_id, status="pending")
  93. return {"job": job.model_dump(mode="json") if hasattr(job, "model_dump") else job}
  94. @router.post("/items/{item_id}/ingest", response_model=IngestItemResponse)
  95. def ingest_item_payloads(
  96. item_id: UUID,
  97. request: IngestItemRequest | None = Body(default=None),
  98. repo: Any = Depends(get_decode_repository),
  99. ) -> IngestItemResponse:
  100. request = request or IngestItemRequest()
  101. list_payloads = getattr(repo, "list_payload_drafts", None)
  102. if list_payloads is None:
  103. raise HTTPException(status_code=501, detail="payload draft reader is not implemented")
  104. drafts = list_payloads(item_id=item_id)
  105. client = None
  106. if not request.dry_run:
  107. config = IngestApiConfig.from_env(_env_file())
  108. if not config.url:
  109. raise HTTPException(status_code=400, detail="CK_INGEST_API_URL 未配置,真实 ingest 已关闭")
  110. client = KnowledgeIngestClient(config)
  111. records = []
  112. for draft in drafts:
  113. records.append(ingest_payload_draft(repo, draft, dry_run=request.dry_run, client=client))
  114. results = [
  115. IngestItemResultSchema(
  116. payload_draft_id=record.payload_draft_id,
  117. ingest_record=IngestRecordSchema.model_validate(record),
  118. )
  119. for record in records
  120. ]
  121. ingested_count = sum(1 for record in records if record.status == "ingested")
  122. failed_count = sum(1 for record in records if record.status == "failed")
  123. return IngestItemResponse(
  124. item_id=item_id,
  125. dry_run=request.dry_run,
  126. total=len(drafts),
  127. ingested_count=ingested_count,
  128. failed_count=failed_count,
  129. results=results,
  130. )