Add per-episode breakdown to guest-star filmography credits
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
FilmographyCreditOut already aggregated a guest star's episode appearances into one row per series with an episode_count, but never exposed which episodes those were even though the individual episode Credit rows exist in the database. Adds an `episodes` list (season/ episode number, title, air date, sorted chronologically) alongside the existing count -- closes a known limitation tracked in docs/api.md. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
20f5e82d1b
commit
606d2532c4
@@ -185,9 +185,9 @@ transaction-mode pooling), horizontally-scaled API and worker replicas. See
|
||||
See [`docs/database.md#known-limitations`](docs/database.md#known-limitations)
|
||||
and [`docs/api.md#known-limitations`](docs/api.md#known-limitations) for the
|
||||
current, tracked list (no curated language/country seed data, no fuzzy
|
||||
entity matching, no field-level provenance, no pagination beyond search,
|
||||
person credits' episode-level guest appearances rolled up to a per-series
|
||||
count rather than broken out individually). None of these block the Core
|
||||
0.1 milestone (section 34): search → movie/series/person detail works
|
||||
end-to-end against real TMDb data, verified on a real PostgreSQL + Redis +
|
||||
entity matching, no field-level provenance, list-shaped responses beyond
|
||||
search and browse/discover return everything with no pagination). None of
|
||||
these block the Core 0.1 milestone (section 34): search → movie/series/person
|
||||
detail works end-to-end against real TMDb data, verified on a real
|
||||
PostgreSQL + Redis +
|
||||
Docker deployment (not just this repo's own dev machine).
|
||||
|
||||
@@ -18,6 +18,14 @@ class PersonSummaryOut(BaseModel):
|
||||
popularity: float | None = None
|
||||
|
||||
|
||||
class GuestEpisodeOut(BaseModel):
|
||||
id: str
|
||||
season_number: int
|
||||
episode_number: int
|
||||
title: str | None = None
|
||||
air_date: date | None = None
|
||||
|
||||
|
||||
class FilmographyCreditOut(BaseModel):
|
||||
id: str
|
||||
media_id: str
|
||||
@@ -33,6 +41,10 @@ class FilmographyCreditOut(BaseModel):
|
||||
# those are aggregated to one row per series rather than one per
|
||||
# episode. None for movie credits and regular (non-guest) series roles.
|
||||
episode_count: int | None = None
|
||||
# The individual episodes behind episode_count, sorted by (season,
|
||||
# episode) -- empty for movie credits and regular (non-guest) series
|
||||
# roles, same as episode_count.
|
||||
episodes: list[GuestEpisodeOut] = []
|
||||
|
||||
|
||||
class PersonDetailOut(BaseModel):
|
||||
|
||||
@@ -14,6 +14,7 @@ from app.models.tv import Series
|
||||
from app.repositories import movie_repository, person_repository, polymorphic, tv_repository
|
||||
from app.schemas.person import (
|
||||
FilmographyCreditOut,
|
||||
GuestEpisodeOut,
|
||||
PersonCreditsOut,
|
||||
PersonDetailOut,
|
||||
PersonSummaryOut,
|
||||
@@ -156,6 +157,10 @@ async def get_person_credits(session: AsyncSession, person_aurora_id: str) -> Pe
|
||||
if series is None:
|
||||
continue
|
||||
representative = group[0]
|
||||
guest_episodes = sorted(
|
||||
(episodes[c.entity_id] for c in group if c.entity_id in episodes),
|
||||
key=lambda ep: (ep.season_number, ep.episode_number),
|
||||
)
|
||||
_bucket(department).append(
|
||||
FilmographyCreditOut(
|
||||
id=aurora_id("credit", str(representative.id)),
|
||||
@@ -168,6 +173,16 @@ async def get_person_credits(session: AsyncSession, person_aurora_id: str) -> Pe
|
||||
release_date=series.first_air_date,
|
||||
poster_path=await _series_poster_path(session, series.id),
|
||||
episode_count=len(group),
|
||||
episodes=[
|
||||
GuestEpisodeOut(
|
||||
id=aurora_id("episode", str(ep.id)),
|
||||
season_number=ep.season_number,
|
||||
episode_number=ep.episode_number,
|
||||
title=ep.title,
|
||||
air_date=ep.air_date,
|
||||
)
|
||||
for ep in guest_episodes
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+1
-6
@@ -145,7 +145,7 @@ network and a production company are both `Company` rows distinguished by
|
||||
| Method | Path |
|
||||
|---|---|
|
||||
| GET | `/api/v1/people/{aurora_id}` |
|
||||
| GET | `/api/v1/people/{aurora_id}/credits` (movie and series filmography; guest-star episode credits are aggregated to one row per series with `episode_count`, not one row per episode) |
|
||||
| GET | `/api/v1/people/{aurora_id}/credits` (movie and series filmography; guest-star episode credits are aggregated to one row per series rather than one per episode, but carry both `episode_count` and the individual `episodes` breakdown, sorted by season/episode number) |
|
||||
|
||||
## Collections / Companies
|
||||
|
||||
@@ -195,8 +195,3 @@ you want network-level isolation too.
|
||||
person's `credits`) still return everything with no pagination at all —
|
||||
fine at today's scale, but the natural next thing to revisit if any of
|
||||
them grow large.
|
||||
- `/people/{id}/credits` covers movies and series (episode guest-star
|
||||
credits rolled up per series). It does not yet expose a per-episode
|
||||
breakdown for those guest appearances (just the aggregate
|
||||
`episode_count`) — the individual episode `Credit` rows exist in the
|
||||
database if that's ever needed.
|
||||
|
||||
@@ -57,11 +57,24 @@ async def test_person_credits_include_movies_series_and_aggregated_guest_spots(
|
||||
db_session.add(season)
|
||||
await db_session.flush()
|
||||
|
||||
# Inserted out of episode-number order deliberately, to prove the
|
||||
# aggregated breakdown sorts by (season, episode) rather than insertion
|
||||
# or credit-creation order.
|
||||
episode1 = Episode(
|
||||
series_id=series.id, season_id=season.id, season_number=1, episode_number=1
|
||||
series_id=series.id,
|
||||
season_id=season.id,
|
||||
season_number=1,
|
||||
episode_number=2,
|
||||
title="Second Appearance",
|
||||
air_date=date(2019, 5, 15),
|
||||
)
|
||||
episode2 = Episode(
|
||||
series_id=series.id, season_id=season.id, season_number=1, episode_number=2
|
||||
series_id=series.id,
|
||||
season_id=season.id,
|
||||
season_number=1,
|
||||
episode_number=1,
|
||||
title="First Appearance",
|
||||
air_date=date(2019, 5, 8),
|
||||
)
|
||||
db_session.add_all([episode1, episode2])
|
||||
await db_session.flush()
|
||||
@@ -99,3 +112,10 @@ async def test_person_credits_include_movies_series_and_aggregated_guest_spots(
|
||||
|
||||
guest = next(c for c in series_entries if c.character == "Guest Character")
|
||||
assert guest.episode_count == 2
|
||||
assert len(guest.episodes) == 2
|
||||
assert [e.episode_number for e in guest.episodes] == [1, 2]
|
||||
assert [e.title for e in guest.episodes] == ["First Appearance", "Second Appearance"]
|
||||
assert guest.episodes[0].season_number == 1
|
||||
assert guest.episodes[0].air_date == date(2019, 5, 8)
|
||||
assert guest.episodes[0].id == aurora_id("episode", str(episode2.id))
|
||||
assert guest.episodes[1].id == aurora_id("episode", str(episode1.id))
|
||||
|
||||
Reference in New Issue
Block a user