"""Person ingestion pipeline (section 15). Movie/series ingestion already creates a stub `Person` row (name + gender) for each cast/crew credit via `get_or_create_person`. This module fetches and upserts a *full* profile (biography, birthday, images, ...) for one person, either standalone (admin-triggered) or as a follow-up job. """ from __future__ import annotations import uuid from sqlalchemy import delete from sqlalchemy.ext.asyncio import AsyncSession from app.core.logging import get_logger from app.models.artwork import Artwork from app.models.enums import ArtworkType, MatchConfidence from app.models.person import Person from app.providers.base import MetadataProvider from app.repositories import reference_repository as refs from app.repositories.provider_repository import get_or_create_provider, upsert_provider_record from app.services.metadata.matcher import match_entity from app.services.metadata.normalizer import normalize_person logger = get_logger(__name__) async def ingest_person( session: AsyncSession, provider: MetadataProvider, external_id: str, *, ingestion_job_id: uuid.UUID | None = None, ) -> uuid.UUID: tmdb_provider = await get_or_create_provider(session, provider.slug, provider.name) aurora_id_existing, confidence = await match_entity( session, provider_id=tmdb_provider.id, entity_type="person", external_id=external_id ) raw = await provider.get_person(external_id) normalized = normalize_person(raw) person: Person | None = None if aurora_id_existing is not None: person = await session.get(Person, aurora_id_existing) if person is None: person = Person(name=normalized.name) session.add(person) await session.flush() aurora_id_existing = None person.name = normalized.name person.biography = normalized.biography person.birthday = normalized.birthday person.deathday = normalized.deathday person.birthplace = normalized.birthplace person.gender = normalized.gender person.known_for_department = normalized.known_for_department person.popularity = normalized.popularity person.aliases = normalized.aliases await session.flush() await session.execute( delete(Artwork).where(Artwork.entity_type == "person", Artwork.entity_id == person.id) ) for art in normalized.artwork: session.add( Artwork( entity_type="person", entity_id=person.id, artwork_type=ArtworkType.PROFILE, width=art.width, height=art.height, aspect_ratio=art.aspect_ratio, provider_id=tmdb_provider.id, provider_asset_id=art.provider_asset_id, original_url=art.url, vote_average=art.vote_average, ) ) await refs.link_external_id( session, entity_type="person", entity_id=person.id, provider_id=tmdb_provider.id, external_id=external_id, ) for slug, ext_id in normalized.external_ids.items(): ext_provider = await get_or_create_provider(session, slug, slug.upper()) await refs.link_external_id( session, entity_type="person", entity_id=person.id, provider_id=ext_provider.id, external_id=ext_id, ) await upsert_provider_record( session, provider_id=tmdb_provider.id, entity_type="person", external_id=external_id, raw_data=raw, aurora_entity_id=person.id, match_confidence=confidence if aurora_id_existing else MatchConfidence.NEW, ingestion_job_id=ingestion_job_id, ) logger.info("person_ingested", person_id=str(person.id), external_id=external_id) return person.id