from __future__ import annotations from typing import Optional from fastapi import APIRouter, Depends, Query from fastapi.responses import JSONResponse from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from ...core.database import get_db from ...core.response import error_response, success_response from ...dependencies import require_manager_user from ...models import User from ...services.semantic_fact_service import SemanticFactService router = APIRouter(prefix="/semantic-facts", tags=["semantic_facts"]) class SemanticFactsBackfillRequest(BaseModel): entity_types: Optional[list[str]] = Field(default=None) only_missing: bool = Field(default=True) limit_per_type: int = Field(default=200, ge=1, le=1000) @router.get("/admin/overview") async def get_semantic_facts_overview( db: AsyncSession = Depends(get_db), current_user: User = Depends(require_manager_user), ): service = SemanticFactService(db, tenant_id=int(current_user.tenant_id)) return success_response(await service.get_admin_overview()) @router.get("/admin/objects") async def list_semantic_fact_objects( entity_type: str | None = Query(default=None), q: str | None = Query(default=None), status: str | None = Query(default=None), db: AsyncSession = Depends(get_db), current_user: User = Depends(require_manager_user), ): service = SemanticFactService(db, tenant_id=int(current_user.tenant_id)) items = await service.list_admin_objects( entity_types=[entity_type] if entity_type else None, q=q, status=status, ) return success_response({"items": items}) @router.get("/admin/objects/{entity_type}/{entity_id}") async def get_semantic_fact_object_detail( entity_type: str, entity_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_manager_user), ): service = SemanticFactService(db, tenant_id=int(current_user.tenant_id)) try: detail = await service.get_admin_detail(entity_type=entity_type, entity_id=int(entity_id)) except ValueError as exc: return JSONResponse(status_code=404, content=error_response(404, str(exc))) return success_response(detail) @router.post("/admin/objects/{entity_type}/{entity_id}/reextract") async def reextract_semantic_fact_object( entity_type: str, entity_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_manager_user), ): service = SemanticFactService(db, tenant_id=int(current_user.tenant_id)) row = await service.enqueue_entity_event( entity_type=entity_type, entity_id=int(entity_id), operation="extract", trigger_type="manual_retry", ) await db.commit() return success_response( { "queued": True, "event_id": int(row.id), "entity_type": str(entity_type), "entity_id": int(entity_id), } ) @router.post("/admin/backfill") async def enqueue_semantic_fact_backfill( body: SemanticFactsBackfillRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_manager_user), ): service = SemanticFactService(db, tenant_id=int(current_user.tenant_id)) result = await service.enqueue_backfill( entity_types=body.entity_types, only_missing=bool(body.only_missing), limit_per_type=int(body.limit_per_type), ) await db.commit() return success_response(result)