vignette/apps/api/app/routes/personas.py
2026-06-26 14:47:00 +09:00

66 lines
2 KiB
Python

"""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]