"""Persona catalog routes.""" from __future__ import annotations from typing import Any from fastapi import APIRouter, HTTPException, Response, status from pydantic import BaseModel from ..deps import CurrentPrincipal from ..persona_repository import CatalogPersona, list_catalog_personas router = APIRouter(prefix="/personas", tags=["personas"]) class PersonaSummary(BaseModel): code: str display_name: str difficulty: str theory_target: list[str] demographics: dict[str, Any] presenting_summary: str voice_preset: str | None = None source: str = "database" degraded: bool = False def _first_text_value(data: dict[str, Any]) -> str: for value in data.values(): if isinstance(value, str) and value.strip(): return value.strip() return "" def _summary(entry: CatalogPersona) -> PersonaSummary: card = entry.card return PersonaSummary( code=card.code, display_name=card.display_name, difficulty=card.difficulty, theory_target=card.theory_target, demographics=card.demographics, presenting_summary=_first_text_value(card.presenting), source=entry.source, degraded=entry.degraded, ) @router.get("", response_model=list[PersonaSummary]) async def list_personas(response: Response, _principal: CurrentPrincipal) -> list[PersonaSummary]: """Return latest approved personas from app.persona_card.""" try: personas = await list_catalog_personas() except Exception as exc: raise HTTPException( status.HTTP_503_SERVICE_UNAVAILABLE, detail="persona catalog database unavailable", ) from exc if any(entry.degraded for entry in personas): response.headers["X-Vignette-Degraded"] = "true" response.headers["X-Vignette-Catalog-Source"] = "seed_fallback" else: response.headers["X-Vignette-Catalog-Source"] = "database" return [_summary(entry) for entry in personas]