diff --git a/SCRATCHPAD.md b/SCRATCHPAD.md index c11bd74..39564dd 100644 --- a/SCRATCHPAD.md +++ b/SCRATCHPAD.md @@ -77,6 +77,52 @@ 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. +## 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 this scratchpad once the optimization backlog is either converted into issues/work items or distilled into durable project guidance. diff --git a/requirements/shifted_seasons_handling.md b/requirements/shifted_seasons_handling.md new file mode 100644 index 0000000..e018413 --- /dev/null +++ b/requirements/shifted_seasons_handling.md @@ -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.