Skip to content

Keep the judge's verdicts, a protection's past, and the plays of removed media - #240

Merged
Randomname653 merged 1 commit into
mainfrom
claude/bold-johnson-w1warc
Oct 25, 2026
Merged

Randomname653 merged 1 commit into
mainfrom
claude/bold-johnson-w1warc

Conversation

@Randomname653

Copy link
Copy Markdown
Owner

What & why

The year's Wrap-Up is written in December, but three kinds of data it needs were being thrown away before then. This PR keeps all three. It covers the three open items from docs/WRAPUP_V2_BLUEPRINT.md that need no Plex server.

1. Judge verdicts (judge_verdicts, services/judge_record.py)

  • Problem: verdicts lived only in the 30-day verdict: cache, so by December the spring's verdicts were gone.
  • Change: every verdict of a deletion scan is now written to judge_verdicts. A row holds:
    • the verdict after Python's guards, and what the model said;
    • the guard, the deciding pillar and the four findings;
    • the deletion score, the size, the model and the verdict cache key.
  • One row per change: a row is written when a title's verdict first comes or changes. The same verdict at the next scan only moves last_at and counts scans, so nightly scans that reuse cached verdicts add no rows.
  • Where: it is written right after the household floor in the judge loop. A failure is logged and never costs the scan its proposals.

2. Protection history (protection_history)

  • Problem: lifting a protection hard-deleted its row, and re-judging overwrote its verdict, so "you deleted what the judge kept" had nothing to stand on.
  • Change: the old state is copied to protection_history first, with a kind:
    • lifted, with who lifted it;
    • changed by the judge;
    • downscaled.
  • Why a history table: I chose it over a lifted_at column on protected_media because that table has about fifteen readers, and each would need a new filter. One missed filter would mean a lifted protection still shields a title.

3. Plays of removed media (watch_history.orphaned_at, database/orphaned.py)

  • Problem: "Delete history for removed media" deleted every play of a title that had left Plex. A title deleted in March lost its plays before the Wrap-Up.
  • Change: the action is now "Hide history" and sets orphaned_at.
  • Hidden: a do_orm_execute hook adds orphaned_at IS NULL to every ORM SELECT on watch_history. History, taste and recommendations therefore look as they did after a delete, without touching their roughly 50 queries.
  • Still counted: the Wrap-Up, the taste snapshots and the Plex sync's duplicate check ask for the marked rows (orphaned.include(db)). Without that, the sync would import a hidden play a second time.
  • Unhidden: a title back in Plex under its old ratingKey gets its plays back on the next run.
  • Not filtered: bulk UPDATE and DELETE, so re-attribution and deletes still reach the marked rows.
  • Unchanged: the share guard and the refusal on unreadable sections. The API now returns archived / would_archive / returned, and the settings UI text says what happens.

FACTS_VERSION is unchanged: a Wrap-Up reads no other rows than before, it just stops losing them.

Checks

  • python tests/run_all.py is green (254/254 suites)

  • Behaviour changes come with a test that would have caught the old behaviour. tests/test_judge_record.py has 21 checks:

    • verdict rows: new, same, guard change and new inputs each behave as described; failed verdicts are not kept; a title without a TMDB id is keyed by its title;
    • archiving when a protection is re-judged, lifted (via the endpoint) or marked downscaled;
    • the wiring in the judge loop;
    • the hide filter on entity, column, count, select, aliased and get reads, plus both opt-ins;
    • which readers opt in.

    tests/test_plex_history_safety.py now checks that the hidden plays are still in the table, that a second run hides nothing more, and that a title back in Plex gets its plays back.

  • No personal data, tokens or hardcoded local paths in the diff

  • Based on current main


Generated by Claude Code

…ved media

Verdicts lived only in the 30-day verdict cache, so the Wrap-Up in
December could not say what the judge thought in spring. Every verdict
of a deletion scan now lands in judge_verdicts, one row per title and
change of verdict; the same verdict again only counts.

Lifting a protection deleted its row and re-judging overwrote it.
protection_history keeps what it was (lifted, changed, downscaled); the
live protected_media table is unchanged for its readers.

The orphan cleanup deleted every play of a title that left Plex. It now
sets orphaned_at. Every ORM read hides marked rows (database/orphaned);
the Wrap-Up, the taste snapshots and the sync's duplicate check read
them, and a title back in Plex gets its plays back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KECJpoVqo39hQKxkrxUjrc

Copy link
Copy Markdown
Owner Author

security-scan failed, but not because of this PR. The OpenAI API rejected every request with 429 insufficient_quota / credit_balance_exhausted, so the scanner analysed nothing: all 15 files errored, and 0 vulnerabilities were reported. This is the same failure as on #229–#232. It will pass once the account has credits again; there is nothing to fix from this branch. I have no way to re-run the job from here.


Generated by Claude Code

@Randomname653
Randomname653 merged commit b16b0b5 into main Oct 25, 2026
9 of 10 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.

2 participants