178 lines
8.9 KiB
Markdown
178 lines
8.9 KiB
Markdown
# 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.
|