# Shifted Seasons Handling This file defines the behavioral contract for mapping source season and episode numbering to target season and episode numbering through stored shifted-season rules. Primary sources: - `requirements/project.md` - `requirements/architecture.md` - actual tool code in `src/ffx/` Secondary source: - `SCRATCHPAD.md`, used only to clarify current hardening gaps and not as the primary contract source. ## Scope - Persisting shifted-season rules in SQLite. - Allowing shifted-season rules to be attached either to a show or to a specific pattern. - Selecting at most one active shifted-season rule for one concrete source season and episode tuple. - Applying additive season and episode offsets to produce target numbering. - Using shifted target numbering during `convert` for TMDB episode lookup and generated season and episode filename tokens. - Managing show-level default mappings and pattern-level override mappings from the Textual editing workflows. ## Out Of Scope - General filename parsing rules for detecting season and episode values. - Standalone `rename` command behavior, which currently uses explicit rename inputs rather than stored shifted-season rules. - Stream or track mapping behavior unrelated to season and episode numbering. ## Terms - `shifted-season rule`: one persisted row describing how one source-numbering range maps to target numbering through additive offsets. - `show-level shifted-season rule`: a rule attached directly to a show and used as the fallback mapping layer for that show. - `pattern-level shifted-season rule`: a rule attached directly to a pattern and used as the override mapping layer for that pattern. - `source numbering`: the season and episode values detected from the current source file or supplied as source-side conversion inputs before shifting. - `target numbering`: the season and episode values after one active shifted-season rule has been applied. - `original season`: the source-domain season number a shifted-season rule is eligible to match. - `episode range`: the optional source-domain episode interval covered by one shifted-season rule. - `open bound`: an unbounded start or end of the episode range. Current storage uses `-1` as the internal sentinel for an open bound. - `active shifted-season rule`: the single rule selected for one concrete input after precedence resolution. - `identity mapping`: the default `1:1` outcome where source numbering is used unchanged. ## Rules - `SHIFTED_SEASONS_HANDLING-0001`: The domain model shall allow a shifted-season rule to be owned by exactly one of: - one show - one pattern - `SHIFTED_SEASONS_HANDLING-0002`: A single shifted-season rule shall not belong to both a show and a pattern at the same time. - `SHIFTED_SEASONS_HANDLING-0003`: A shifted-season rule shall carry these fields: `original_season`, `first_episode`, `last_episode`, `season_offset`, and `episode_offset`. - `SHIFTED_SEASONS_HANDLING-0004`: `season_offset` and `episode_offset` shall be additive signed integers applied to matched source numbering to produce target numbering. - `SHIFTED_SEASONS_HANDLING-0005`: A shifted-season rule shall match a source tuple only when: - the source season equals `original_season` - the source episode is greater than or equal to `first_episode` when the lower bound is closed - the source episode is less than or equal to `last_episode` when the upper bound is closed - `SHIFTED_SEASONS_HANDLING-0006`: An open lower or upper episode bound shall represent an unbounded side of the covered source episode range. - `SHIFTED_SEASONS_HANDLING-0007`: If one shifted-season rule matches, target numbering shall be: - `target season = source season + season_offset` - `target episode = source episode + episode_offset` - `SHIFTED_SEASONS_HANDLING-0008`: If no shifted-season rule matches, source numbering shall pass through unchanged. - `SHIFTED_SEASONS_HANDLING-0009`: Shifted-season handling shall operate in a source-to-target numbering model. Stored rules map detected source numbering to the target numbering used by conversion-facing metadata and output naming. - `SHIFTED_SEASONS_HANDLING-0010`: Pattern matching identifies the owning show and optionally a more specific owning pattern. Resolution of the active shifted-season rule shall use this precedence order: - matching pattern-level rule - matching show-level rule - identity mapping - `SHIFTED_SEASONS_HANDLING-0011`: At most one shifted-season rule may be active for one concrete source season and episode tuple. Shifted-season rules shall never stack or compose. - `SHIFTED_SEASONS_HANDLING-0012`: Within one owner scope, shifted-season rules shall not overlap in their effective episode coverage for the same `original_season`. - `SHIFTED_SEASONS_HANDLING-0013`: If a shifted-season rule uses two closed episode bounds, `last_episode` shall be greater than or equal to `first_episode`. - `SHIFTED_SEASONS_HANDLING-0014`: Shifted-season rule evaluation shall be deterministic. Released behavior shall not depend on arbitrary database row order when invalid overlapping rules exist. - `SHIFTED_SEASONS_HANDLING-0015`: A pattern-level rule is permitted to map to zero offsets. Such a rule is a valid explicit override that beats show-level fallback and produces identity mapping for its covered source range. - `SHIFTED_SEASONS_HANDLING-0016`: During `convert`, when show, season, and episode values are available and stored shifting is active, the shifted target numbering shall drive: - TMDB episode lookup - season and episode filename tokens such as `S01E02` - generated episode basenames that include season and episode numbering - `SHIFTED_SEASONS_HANDLING-0017`: When conversion is supplied explicit target-domain season or episode values for TMDB naming, the system shall not apply stored shifting on top of those already-targeted values. - `SHIFTED_SEASONS_HANDLING-0018`: Operator-facing editing shall expose shifted-season rule management in both of these places: - show editing for show-level default mappings - pattern editing for pattern-level override mappings - `SHIFTED_SEASONS_HANDLING-0019`: User-facing shifted-season editing should present open episode bounds as a natural empty-state input rather than forcing operators to type the internal sentinel directly. ## Acceptance - A show can exist with zero or more show-level shifted-season rules. - A pattern can exist with zero or more pattern-level shifted-season rules. - A shifted-season rule is stored against exactly one owner scope. - A source tuple matching a pattern-level rule yields target numbering from that rule even when a matching show-level rule also exists. - A source tuple matching no pattern-level rule but matching a show-level rule yields target numbering from the show-level rule. - A source tuple matching neither scope yields identity mapping. - A pattern-level zero-offset rule can explicitly override a nonzero show-level rule for the same covered source range. - Two shifted-season rules for the same owner scope and original season cannot both be valid if they cover overlapping episode ranges. - During `convert`, shifted numbering is what TMDB episode lookup and generated season and episode tokens see when stored shifting is active. - The TUI can display and maintain shifted-season rules from both the show and pattern editing flows. ## Current Code Fit - `src/ffx/model/show.py` and `src/ffx/model/pattern.py` now both expose shifted-season relationships, and `src/ffx/model/shifted_season.py` stores each rule against exactly one owner scope through `show_id` or `pattern_id`. - `src/ffx/shifted_season_controller.py` now resolves mappings with pattern-over-show precedence and applies at most one active rule for a source tuple. - `src/ffx/show_details_screen.py`, `src/ffx/shifted_season_details_screen.py`, and `src/ffx/shifted_season_delete_screen.py` provide reusable shifted-season editing dialogs, and `src/ffx/pattern_details_screen.py` now exposes the pattern-level override flow. - `src/ffx/cli.py` now resolves shifted numbering during `convert` from: pattern-level match, then show-level match, then identity mapping. - `src/ffx/database.py` now migrates version-2 databases to version 3 by preserving existing show-level rows and extending the schema for pattern-level ownership. ## Risks - The current CLI groups `--show`, `--season`, and `--episode` under one override bucket used for TMDB-related behavior. Source-domain versus target-domain semantics of each override must stay documented clearly so stored shifting is neither skipped nor double-applied unexpectedly. - Existing version-2 databases only contain show-owned shifted-season rows, so a version-3 migration must preserve those rows as the show-level fallback layer. - Current modern automated test coverage for shifted-season behavior is light, so precedence, migration, and convert-time numbering behavior need focused tests.