1 Commits

Author SHA1 Message Date
Javanaut
0e4fae538b prep season shift 2026-04-12 16:52:12 +02:00
2 changed files with 212 additions and 0 deletions

View File

@@ -77,6 +77,52 @@
3. Continue replacing oversized legacy test matrices with focused modern integration and unit coverage. 3. Continue replacing oversized legacy test matrices with focused modern integration and unit coverage.
4. Triage the legacy `Scenario 4` pattern/track failure and decide whether to fix the harness, adapt it to the zero-track guard, or retire that path during the ongoing test-suite migration. 4. Triage the legacy `Scenario 4` pattern/track failure and decide whether to fix the harness, adapt it to the zero-track guard, or retire that path during the ongoing test-suite migration.
## Shifted Season Status (2026-04-12)
- Current assessment:
- The shifted-season subsystem is present end to end and looks feature-complete in shape, but it is not yet hardened.
- The storage, TUI CRUD surface, and CLI/TMDB filename application path all exist, so this is no longer a stubbed or half-started area.
- The main gap is correctness and direct verification rather than missing surface area.
- Implemented surface confirmed:
- Requirements still treat shifted seasons as part of the accepted product surface in [`requirements/project.md`](/home/osgw/.local/src/codex/ffx/requirements/project.md) and [`requirements/architecture.md`](/home/osgw/.local/src/codex/ffx/requirements/architecture.md).
- Persistence exists via [`src/ffx/model/shifted_season.py`](/home/osgw/.local/src/codex/ffx/src/ffx/model/shifted_season.py) plus the `Show.shifted_seasons` relationship in [`src/ffx/model/show.py`](/home/osgw/.local/src/codex/ffx/src/ffx/model/show.py).
- CRUD logic exists in [`src/ffx/shifted_season_controller.py`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_controller.py).
- Textual add/edit/delete flows are wired through [`src/ffx/shifted_season_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_details_screen.py), [`src/ffx/shifted_season_delete_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_delete_screen.py), and the show details table in [`src/ffx/show_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/show_details_screen.py).
- CLI conversion applies season shifts before TMDB lookup and output suffix generation in [`src/ffx/cli.py`](/home/osgw/.local/src/codex/ffx/src/ffx/cli.py).
- Verified current behavior:
- `~/.local/share/ffx.venv/bin/python -m unittest discover -s tests/unit -p 'test_*.py'` passed on 2026-04-12: `75` tests in `0.795s`.
- That run emitted `ResourceWarning` messages for unclosed SQLite connections, so the suite is green but not perfectly clean.
- There is almost no direct shifted-season coverage in the modern tests:
- [`tests/unit/test_cli_rename_only.py`](/home/osgw/.local/src/codex/ffx/tests/unit/test_cli_rename_only.py) stubs `ShiftedSeasonController` rather than exercising it.
- [`tests/unit/test_screen_support.py`](/home/osgw/.local/src/codex/ffx/tests/unit/test_screen_support.py) only verifies controller bootstrap wiring.
- Net effect: the subsystem is integrated, but its core rules are effectively untested by the current modern suite.
- Reproduced correctness gaps:
- Overlap validation is broken in [`src/ffx/shifted_season_controller.py:41`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_controller.py:41) because `getOriginalSeason` is compared as a method object instead of being called.
- Reproduction on 2026-04-12 with a temp SQLite DB:
- Added `S1 E1-E10`.
- `checkShiftedSeason(...)` incorrectly returned `True` for overlapping `S1 E5-E15`.
- `addShiftedSeason(...)` then stored the overlapping row successfully.
- `updateShiftedSeason(...)` in [`src/ffx/shifted_season_controller.py:93`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_controller.py:93) does not enforce episode ordering, so an invalid range like `first_episode=20`, `last_episode=10` was accepted in the same reproduction.
- Because [`src/ffx/shifted_season_controller.py:213`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_controller.py:213) returns the first matching sibling and [`src/ffx/shifted_season_controller.py:163`](/home/osgw/.local/src/codex/ffx/src/ffx/shifted_season_controller.py:163) applies no explicit ordering, overlapping rows would also make runtime shifting ambiguous.
- Progress summary:
- Good progress:
- The subsystem exists across requirements, schema, UI, and conversion flow.
- It appears fully integrated into the show-editing workflow rather than parked as dead code.
- Incomplete progress:
- Validation logic is not trustworthy yet.
- Modern tests do not currently protect the subsystem's real behavior.
- User-facing error feedback in the shifted-season screens still has placeholder `#TODO: Meldung` branches.
- Recommended next slice:
1. Add direct controller tests for overlap rejection, episode-order validation, and `shiftSeason(...)` selection behavior.
2. Fix `checkShiftedSeason(...)` and add the same range/order validation to `updateShiftedSeason(...)`.
3. Make sibling selection deterministic or enforce non-overlap strongly enough that ordering no longer matters in practice.
4. Add at least one focused integration test that proves a stored shifted season changes TMDB lookup and/or generated filename numbering during conversion.
## Delete When ## Delete When
- Delete this scratchpad once the optimization backlog is either converted into issues/work items or distilled into durable project guidance. - Delete this scratchpad once the optimization backlog is either converted into issues/work items or distilled into durable project guidance.

View File

@@ -0,0 +1,166 @@
# 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.
- Treating shifted-season rules as show-level data rather than pattern-level
data.
- Matching source season and episode numbers against one stored rule.
- 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.
## 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 that belongs to one show and defines
how one source-numbering range maps into target numbering.
- `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
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.
- `sibling shifted-season rules`: all shifted-season rules stored for the same
show.
## 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-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 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
episode bounds, `last_episode` shall be greater than or equal to
`first_episode`.
- `SHIFTED_SEASONS_HANDLING-0013`: 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
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
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
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.
- 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.
## 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/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.
## 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
stored shifting is neither skipped nor double-applied unexpectedly.
- 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.