refactor(tv_shows): freeze aggregate, builder-only construction, drop ShowTracker fields
The TVShow aggregate is now fully immutable. TVShow, Season and Episode are @dataclass(frozen=True), children stored as ordered tuples sorted by number. All construction goes through TVShowBuilder / SeasonBuilder (new module), which expose from_existing() to seed from a current frozen aggregate and apply modifications. ShowTracker-territory fields are stripped from the domain: ShowStatus, CollectionStatus, expected_seasons/episodes, aired_episodes, collection_status(), is_complete_series(), missing_episodes(), is_ongoing(), is_ended(), Season.name, the aired<=expected validation, and the TMDB status string mapping. These will reappear in a dedicated ShowTracker layer (to be designed) combining the .alfred sidecar with live TMDB data. New SeasonMode enum (PACK / EPISODIC) computed at read time from the season's structural shape — never stored, the YAML sidecar encodes the mode via presence/absence of the episodes: block. Test suite for the domain entirely rewritten to cover frozen invariants, builder ordering, last-write-wins, from_existing round-trip, and SeasonMode derivation. Full suite still green (1078 passed).
This commit is contained in:
@@ -17,6 +17,64 @@ callers).
|
||||
|
||||
### Added
|
||||
|
||||
- **`TVShowBuilder` / `SeasonBuilder` — sole construction surface for the
|
||||
TVShow aggregate** (`alfred/domain/tv_shows/builders.py`). The aggregate
|
||||
is now fully frozen; building goes through a mutable scratchpad that
|
||||
emits an immutable `TVShow` via `build()`. Both builders offer a
|
||||
`from_existing()` classmethod to seed from a current frozen aggregate
|
||||
and apply modifications. Episodes are emitted sorted by number within a
|
||||
season, seasons sorted by number within the show.
|
||||
- **`SeasonMode` enum** (`PACK` / `EPISODIC`) in
|
||||
`alfred/domain/tv_shows/value_objects.py`. Computed at read time from
|
||||
the season's structural shape (`Season.mode` property): a season with
|
||||
no explicit episodes is `PACK` (a single release covering the whole
|
||||
season), a season with episodes is `EPISODIC` (currently airing, one
|
||||
release per episode). Never stored — the YAML sidecar encodes the
|
||||
mode via the presence/absence of the `episodes:` block.
|
||||
|
||||
### Changed
|
||||
|
||||
- **TVShow aggregate is now frozen all the way down.** `TVShow`,
|
||||
`Season` and `Episode` are all `@dataclass(frozen=True)`. Children
|
||||
are stored as ordered tuples (`tuple[Season, ...]`,
|
||||
`tuple[Episode, ...]`) sorted by their respective numbers, replacing
|
||||
the previous mutable dicts. Lookup helpers `TVShow.get_season(n)` and
|
||||
`Season.get_episode(n)` traverse the tuple lazily via `next()`. The
|
||||
former `add_episode` / `add_season` mutation methods are gone — all
|
||||
construction goes through `TVShowBuilder` / `SeasonBuilder`.
|
||||
|
||||
### Removed
|
||||
|
||||
- **ShowTracker-territory fields stripped from the TVShow aggregate.**
|
||||
The aggregate now models only what the `.alfred` sidecar stores
|
||||
(filesystem-observable facts + immutable identity). Dropped from the
|
||||
domain:
|
||||
- `TVShow.status` (`ShowStatus`) and the `ShowStatus` enum entirely,
|
||||
along with its TMDB string mapping (`from_string`).
|
||||
- `TVShow.expected_seasons`, `Season.expected_episodes`,
|
||||
`Season.aired_episodes`, `Season.name`.
|
||||
- `TVShow.collection_status()`, `is_complete_series()`,
|
||||
`missing_episodes()`, `is_ongoing()`, `is_ended()` and the
|
||||
`CollectionStatus` enum.
|
||||
- `Season.is_complete()`, `is_fully_aired()`, `missing_episodes()`
|
||||
and the `aired ≤ expected` validation.
|
||||
- `TVShow.add_episode()` / `TVShow.add_season()` /
|
||||
`Season.add_episode()` — replaced by the builder API.
|
||||
These concerns will reappear in a dedicated `ShowTracker` layer (to
|
||||
be designed) that combines the `.alfred` sidecar with live TMDB data
|
||||
to answer questions like "is this show complete?" or "are new
|
||||
episodes out?". Keeping volatile/derived state out of the aggregate
|
||||
matches the factuel-only philosophy locked in `specs/dot_alfred.md`.
|
||||
|
||||
### Internal
|
||||
|
||||
- **Test suite rewritten for the new aggregate shape.**
|
||||
`tests/domain/test_tv_shows.py` now covers frozen invariants, builder
|
||||
ordering, last-write-wins on duplicates, `from_existing` round-trip,
|
||||
and `SeasonMode` derivation. `tests/infrastructure/test_filesystem_extras.py`
|
||||
helper simplified (no more `ShowStatus.ENDED` / `expected_seasons` on
|
||||
test shows). 1078 tests still green.
|
||||
|
||||
- **Design doc for `.alfred/` sidecar persistence
|
||||
(`specs/dot_alfred.md`).** First entry in the new `specs/` directory.
|
||||
Specifies a per-show `.alfred/` directory holding a `show.yaml` and
|
||||
|
||||
Reference in New Issue
Block a user