"""Assembles Person API responses from repository data.""" from __future__ import annotations import uuid from sqlalchemy.ext.asyncio import AsyncSession from app.core.ids import aurora_id, parse_aurora_id from app.models.enums import ArtworkType from app.repositories import movie_repository, person_repository, polymorphic from app.schemas.person import ( FilmographyCreditOut, PersonCreditsOut, PersonDetailOut, PersonSummaryOut, ) from app.services.common import primary_artwork_path, to_artwork_out class PersonNotFoundError(Exception): pass def _resolve_person_id(aid: str) -> uuid.UUID: try: return uuid.UUID(parse_aurora_id(aid, expected_type="person")) except ValueError as exc: raise PersonNotFoundError(str(exc)) from exc async def get_person_detail(session: AsyncSession, person_aurora_id: str) -> PersonDetailOut: person_id = _resolve_person_id(person_aurora_id) person = await person_repository.get_person_by_id(session, person_id) if person is None: raise PersonNotFoundError(person_aurora_id) profiles = await polymorphic.get_artwork(session, "person", person_id, ArtworkType.PROFILE) external_ids = await polymorphic.get_external_ids(session, "person", person_id) return PersonDetailOut( id=aurora_id("person", str(person.id)), name=person.name, aliases=list(person.aliases or []), biography=person.biography, birthday=person.birthday, deathday=person.deathday, birthplace=person.birthplace, gender=person.gender, known_for_department=person.known_for_department, popularity=person.popularity, profile_images=[to_artwork_out(a) for a in profiles], external_ids=external_ids, created_at=person.created_at, updated_at=person.updated_at, ) async def get_person_credits(session: AsyncSession, person_aurora_id: str) -> PersonCreditsOut: person_id = _resolve_person_id(person_aurora_id) person = await person_repository.get_person_by_id(session, person_id) if person is None: raise PersonNotFoundError(person_aurora_id) credits_ = await person_repository.get_person_credits(session, person_id) movie_ids = [c.entity_id for c in credits_ if c.entity_type == "movie"] movies = {m.id: m for m in await movie_repository.get_movies_by_ids(session, movie_ids)} cast: list[FilmographyCreditOut] = [] crew: list[FilmographyCreditOut] = [] for credit in credits_: if credit.entity_type != "movie": # Series/episode filmography follows the same shape; movies are # implemented first per the Core 0.1 milestone (section 34). continue movie = movies.get(credit.entity_id) if movie is None: continue posters = await polymorphic.get_artwork( session, "movie", movie.id, ArtworkType.POSTER ) item = FilmographyCreditOut( id=aurora_id("credit", str(credit.id)), media_id=aurora_id("movie", str(movie.id)), media_type="movie", title=movie.title, department=credit.department, job=credit.job, character=credit.character, release_date=movie.primary_release_date, poster_path=primary_artwork_path(posters, ArtworkType.POSTER), ) if credit.department == "Acting": cast.append(item) else: crew.append(item) return PersonCreditsOut(id=aurora_id("person", str(person.id)), cast=cast, crew=crew) async def build_person_summary( session: AsyncSession, person_id: uuid.UUID ) -> PersonSummaryOut | None: person = await person_repository.get_person_by_id(session, person_id) if person is None: return None profiles = await polymorphic.get_artwork(session, "person", person_id, ArtworkType.PROFILE) return PersonSummaryOut( id=aurora_id("person", str(person.id)), name=person.name, known_for_department=person.known_for_department, profile_path=primary_artwork_path(profiles, ArtworkType.PROFILE), popularity=person.popularity, ) async def build_person_summaries( session: AsyncSession, person_ids: list[uuid.UUID] ) -> list[PersonSummaryOut]: out: list[PersonSummaryOut] = [] for person_id in person_ids: summary = await build_person_summary(session, person_id) if summary is not None: out.append(summary) return out