from typing import Any from fastapi import APIRouter, Body, Depends from sqlalchemy.orm import Session from app.api.schemas import AttributeDefinitionIn, page, parse_page from app.domain.serialize import jsonable from app.errors import AppError from app.persistence.db import get_session from app.persistence.repositories import CatalogRepository from app.services.ingestion_service import IngestionService router = APIRouter(prefix="/api/v1") @router.post("/attribute-definitions", status_code=201) def create_attribute_definition(payload: AttributeDefinitionIn, session: Session = Depends(get_session)): definition = IngestionService(session).create_definition(payload.model_dump()) return jsonable(definition.__dict__) @router.get("/attribute-definitions") def list_attribute_definitions(limit: int = 50, offset: int = 0, session: Session = Depends(get_session)): limit, offset = parse_page(limit, offset) items, total = CatalogRepository(session).list_definitions(limit, offset) return page([jsonable(item.__dict__) for item in items], limit, offset, total) @router.get("/sources") def list_sources(session: Session = Depends(get_session)): items = CatalogRepository(session).list_sources() return { "items": [{"id": item.id, "name": item.name, "adapter_type": item.adapter_type} for item in items], "limit": len(items), "offset": 0, "total": len(items), } @router.get("/scoring-models/{version}") def get_scoring_model(version: str, session: Session = Depends(get_session)): model = CatalogRepository(session).get_scoring_model(version) if model is None: raise AppError(404, "NOT_FOUND", "scoring model not found", {"version": version}) return { "version": model.version, "algorithm_id": model.algorithm_id, "definition": model.definition, "definition_hash": model.definition_hash, } @router.post("/profiles", status_code=201) def create_profile(payload: dict[str, Any] = Body(...), session: Session = Depends(get_session)): if not isinstance(payload, dict): raise AppError(422, "PROFILE_INVALID", "profile must be an object") row = IngestionService(session).create_profile(payload) return { "id": row.id, "version": row.version, "name": row.name, "category": row.category, "scoring_version": row.scoring_version, "definition": row.definition, "definition_hash": row.definition_hash, "created_at": row.created_at, } @router.get("/profiles") def list_profiles(limit: int = 50, offset: int = 0, session: Session = Depends(get_session)): limit, offset = parse_page(limit, offset) items, total = CatalogRepository(session).list_profiles(limit, offset) return page( [ { "id": row.id, "version": row.version, "name": row.name, "category": row.category, "scoring_version": row.scoring_version, "definition_hash": row.definition_hash, "created_at": row.created_at, } for row in items ], limit, offset, total, ) @router.get("/profiles/{profile_id}") def get_profile(profile_id: str, version: int | None = None, session: Session = Depends(get_session)): if version is None: raise AppError(422, "VERSION_REQUIRED", "query parameter version is required") profile = CatalogRepository(session).get_profile(profile_id, version) if profile is None: raise AppError(404, "NOT_FOUND", "profile version not found") return profile.definition