Skip to content

Dev - #331

Merged
jessielw merged 57 commits into
mainfrom
dev
Aug 7, 2026
Merged

Dev#331
jessielw merged 57 commits into
mainfrom
dev

Conversation

@jessielw

@jessielw jessielw commented Aug 7, 2026

Copy link
Copy Markdown
Owner

No description provided.

jessielw and others added 30 commits August 3, 2026 15:24
Records the valid range, integer requirement and provider-scale notes for
every external rating and vote-count field. No behaviour change yet.
Rating fields accepted any number, so a threshold on the wrong scale saved
without complaint and then matched nothing. Reject out-of-range values,
decimals on integer-backed fields, lists, booleans and non-finite values at
save time, naming the field by its display label and explaining the two
fields whose stored scale differs from the provider's published one.
A value can sit inside the valid range and still be unsatisfiable: nothing
exceeds the ceiling, and nothing falls below the floor. Reject those two
combinations and point at the inclusive operator that does what the user
meant. The inclusive variants stay valid, since items do sit on both bounds.
Move the ten range/type validation tests back into NumericBoundsValidationTests
where they belong, leaving NumericBoundaryImpossibilityTests with its six
boundary tests. Both classes define identical _validate helpers, so no
functional change — purely structural reorganization.
The numeric condition input carried no min, max or step, so the editor gave
no hint that Tomatometer runs 0-100 while IMDb rating runs 0-10.
Metacritic user score and Letterboxd score are stored as percentages while
their sites publish 0-10 and 0-5, so a user copying the published number
enters a value that can never match. Say so in the field itself.
The editor keeps its own copy of the numeric bounds, matching how it already
duplicates field labels and operators. This fails if the two drift.
- Document that Metacritic user score and Letterboxd score are stored
  as 0-100 even though the providers publish them on different scales,
  since the docs were repeating the exact trap the branch closes in code
- Point FIELD_NUMERIC_BOUNDS at its frontend mirror and the parity test
  that enforces the two stay in sync
- Rename the local bounds tuple in _validate_numeric_bounds from
  _minimum/_maximum to minimum/maximum; the underscore prefix wrongly
  signalled "unused" for values used throughout the function
- Raise the field's full numeric expectation (range and, where
  applicable, the provider-scale note) instead of a bare "expects a
  number" when a value is a bool or fails numeric coercion
- Give the bounded rating placeholder the same trailing ellipsis as
  its sibling placeholders
No job currently runs pytest on pull requests. This suite includes a test
that pins the frontend rating bounds to the backend table; the test only
protects against drift if it runs automatically on each pull request.
Validate rating field values in the advanced rule engine
Condition values were harvested as literal tag labels whatever the operator,
so regex and substring patterns were treated as tag names. Skip them during
collection and report their presence separately, ready for the refresh to
widen its scope when one is present.
A regex or substring condition names no specific tag, so the per-scan top-up
matched nothing and left tag data as the last media sync wrote it. A tag
removed in Radarr could therefore keep a movie flagged for up to a full sync
cycle. Refresh the whole catalog for that media type instead.

Exact-match rules are unchanged: they still refresh only the labels they name
and leave every other label on the row alone.
…fresh

A strip set built from the arr's current tag catalog can't name a label
whose tag was deleted there, and can't distinguish a catalog that came
back genuinely empty from one that was never fetched. Both left stale
labels on movie rows after a full-refresh pass. When a rule needs the
whole catalog, replace each row with the confirmed labels instead of
stripping and re-adding. Exact-match rules keep the narrower patch
behaviour unchanged.
Mirrors the Radarr change on the Sonarr branch so both media types behave the
same way, and a tag removed in Sonarr takes effect at the next cleanup scan
rather than the next media sync.
Add two CleanupScanIntegrationTests cases for _refresh_arr_tags_for_rules
in full-refresh mode: one confirming label additions union across all
successful arr configs before the write loop runs, and one confirming a
row reachable from any failed config is excluded from the write even
when another config for that row succeeded. Neither property was
previously tested, so a refactor that moved the write inside the
per-config loop, or that used a single config's effective_labels, could
silently wipe a second arr's tags while the suite stayed green.

Also correct the function's docstring, which still described the write
as a strip-then-readd patch in every case; that is only true in
exact-match mode, since full-refresh mode replaces the row outright.
Note at the movie_refreshed_labels/series_refreshed_labels declarations
that they drive the exact-match strip and the debug log only, replace a
debug log line that printed the whole catalog on every scan with a
count in full-refresh mode, and simplify a condition that was already
guaranteed true by its enclosing branch.
…operators

Refresh arr tags for rules that match tags by pattern
fix: treat zero-vote TMDB titles as unrated
code-boar and others added 26 commits August 4, 2026 23:16
Selecting several users reduced their totals with max() before applying
the operator, so `less than 5 minutes by alice or bob` only matched when
both were under the threshold, and `equals` compared against the
highest-watching user alone. Each selected user is now compared on their
own and the condition matches as soon as one of them satisfies it, which
is what the field labels and the docs describe.

Per-user totals also read as a concrete zero for every target, including
targets no imported history covers, so a "watched less than" rule matched
media the playback source cannot see. The resolver now carries the same
targets the aggregate duration fields are available for and reports the
rest as unknown; the preview target count follows the same rule.

Loading the totals is now gated on a rule actually using the fields,
rather than on any playback field, so aggregate-only rules no longer pay
for the per-user query.
The editor's value check stringified the condition value, and a
{usernames, amount} object stringifies to "[object Object]", so a
condition with no user picked and no amount passed as filled in and was
only rejected by the API on save. The check moves to a module beside the
tree helpers, where the compound shape is recognised by structure and
both halves have to be present, and where node --test can cover it.
The minimums are applied while history is imported, so changing them only
affects events imported afterwards; neither the settings panel nor the
rules page said so. Also spells out that percent counts time watched
rather than furthest position, that it is measured against the runtime of
the file being evaluated, and that episode runtimes arrive with a
media-server sync, so percent rules on an existing install stay unknown
until one has run.
dev's c3f7b1d09a24 and this branch's a3f6b8d1c4e2 both chain off
d7f9a2c4e6b8, so combining them leaves alembic with two heads and
`upgrade head` refuses to run. The playback migrations now chain onto
c3f7b1d09a24 instead. That is a straight re-point rather than a merge
revision because neither of this branch's migrations has shipped, so no
database has applied them from the old parent.
pyproject.toml has been at 0.3.4 while uv.lock still recorded the
reclaimerr package as 0.3.3, so every `uv run` or `uv sync` rewrote the
lock and left a stray modification in the working tree.
chore: sync uv.lock with the project version
Harden trusted proxy authentication before merge
feat: updated dependencies
Comment thread .github/workflows/pytest.yml Fixed
@jessielw
jessielw merged commit a1d24a4 into main Aug 7, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants