iteration1

This commit is contained in:
Javanaut
2026-04-12 17:12:32 +02:00
parent 0e4fae538b
commit 559869ca68
15 changed files with 1074 additions and 250 deletions

View File

@@ -16,13 +16,15 @@ Secondary source:
## Scope
- Persisting shifted-season rules in SQLite.
- Treating shifted-season rules as show-level data rather than pattern-level
data.
- Matching source season and episode numbers against one stored rule.
- 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 shifted-season rules from the Textual show-editing workflow.
- Managing show-level default mappings and pattern-level override mappings from
the Textual editing workflows.
## Out Of Scope
@@ -33,11 +35,15 @@ Secondary source:
## Terms
- `shifted-season rule`: one persisted row that belongs to one show and defines
how one source-numbering range maps into target numbering.
- `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 matching
- `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.
@@ -45,16 +51,19 @@ Secondary source:
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.
- `sibling shifted-season rules`: all shifted-season rules stored for the same
show.
- `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 treat shifted-season
rules as children of a show. Shifted-season rules shall not belong to
patterns.
- `SHIFTED_SEASONS_HANDLING-0002`: Each persisted shifted-season rule shall
belong to exactly one show.
- `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`.
@@ -63,11 +72,11 @@ Secondary source:
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 season equals `original_season`
- the source episode is greater than or equal to `first_episode` when the
lower bound is closed,
lower bound is closed
- the source episode is less than or equal to `last_episode` when the upper
bound is closed.
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
@@ -79,88 +88,90 @@ Secondary source:
- `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 may identify the owning
show, but shifted-season rule selection shall depend on the show and source
numbering, not on which pattern matched.
- `SHIFTED_SEASONS_HANDLING-0011`: For one show and one `original_season`,
shifted-season rules shall not overlap in their effective episode coverage. At
most one rule may apply to any one source season and episode tuple.
- `SHIFTED_SEASONS_HANDLING-0012`: If a shifted-season rule uses two closed
- `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-0013`: Shifted-season rule evaluation shall be
- `SHIFTED_SEASONS_HANDLING-0014`: Shifted-season rule evaluation shall be
deterministic. Released behavior shall not depend on arbitrary database row
order when more than one stored rule could match.
- `SHIFTED_SEASONS_HANDLING-0014`: During `convert`, when show, season, and
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-0015`: When conversion is supplied explicit
- `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-0016`: Operator-facing show editing shall expose
list, add, edit, and delete flows for shifted-season rules as part of the
show-management workflow.
- `SHIFTED_SEASONS_HANDLING-0017`: User-facing shifted-season editing should
- `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 shifted-season rules.
- A shifted-season rule is stored against one show, not against one pattern.
- A source tuple matching one stored rule yields exactly one shifted target
season and episode tuple derived by additive offsets.
- A source tuple matching no stored rule retains its original season and
episode values.
- Two shifted-season rules for the same show and original season cannot both be
valid if they cover overlapping episode ranges.
- A rule with closed bounds such as `first_episode=1` and `last_episode=10`
rejects an inverted interval such as `20..10`.
- A show with several patterns still uses one shared shifted-season rule set,
because shifted-season ownership is show-scoped.
- 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 show-management flow can display and maintain shifted-season rules for
the current show.
- The TUI can display and maintain shifted-season rules from both the show and
pattern editing flows.
## Current Code Fit
- `src/ffx/model/shifted_season.py` defines the persisted
`ShiftedSeason` entity with `show_id`, `original_season`, episode bounds, and
additive offsets.
- `src/ffx/model/show.py` implements the one-to-many
`Show -> ShiftedSeason` relationship, which already aligns with show-level
ownership.
- `src/ffx/shifted_season_controller.py` implements create, update, lookup,
delete, sibling retrieval, and the runtime `shiftSeason(...)` mapping step.
- `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 the current Textual CRUD
flow for managing show-scoped shifted-season rules.
- `src/ffx/cli.py` applies `shiftSeason(...)` during `convert` before TMDB
episode lookup and before output season and episode suffix generation.
- The current `convert` implementation disables stored shifting whenever its
TMDB override bucket is present, including cases such as `--show` without an
explicit target season or episode override, so current behavior is broader
than the minimum bypass contract stated above.
- The current code does not fully satisfy the intended validation contract yet:
overlap rejection and update-time range validation are not hardened
sufficiently, and deterministic selection depends too much on invalid overlap
state not being present.
`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. The exact source-domain versus
target-domain semantics of each override should stay documented clearly so
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 validation and convert-time numbering behavior are not yet strongly locked
down by focused tests.
- Existing databases created before stricter validation may already contain
invalid overlapping or inverted shifted-season rules, so migration and repair
paths should continue to treat explicit validation failures as recoverable
operator signals.
so precedence, migration, and convert-time numbering behavior need focused
tests.