Compare commits
1 Commits
v0.2.4
...
0e4fae538b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e4fae538b |
@@ -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.
|
||||||
|
|||||||
166
requirements/shifted_seasons_handling.md
Normal file
166
requirements/shifted_seasons_handling.md
Normal 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.
|
||||||
Reference in New Issue
Block a user