Repository navigation
fix(search): reject automatic-search results whose title is irrelevant to the audiobook - #591
kevinheneveld wants to merge 5 commits into
Conversation
af5168e to
c4792f8
Compare
938f2f0 to
60b14cb
Compare
60b14cb to
5eba4a4
Compare
5eba4a4 to
0a4988d
Compare
|
@T4g1 — I think both your points are addressed:
Mind giving it another look when you have a moment? Happy to rebase onto current canary if it's drifted. |
…ethod Addresses T4g1's review on Listenarrs#591: - ISearchResultFilter now exposes a single ShouldFilter(SearchResult, Audiobook? = null) instead of a context-free method plus a default-interface context-aware overload. Filters that don't need the audiobook simply ignore the parameter; RelevanceFilter keys off it (null => fail open). The doc comment now describes the null/fail-open contract rather than justifying the original migration. - Use a consolidated exception filter (ex is not (OperationCanceledException or OutOfMemoryException or StackOverflowException)) for the context-aware filter pass in LibraryController. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @T4g1 — both addressed in
|
|
lgtm! Let's rebase and check pipeline result before merge though |
…ethod Addresses T4g1's review on Listenarrs#591: - ISearchResultFilter now exposes a single ShouldFilter(SearchResult, Audiobook? = null) instead of a context-free method plus a default-interface context-aware overload. Filters that don't need the audiobook simply ignore the parameter; RelevanceFilter keys off it (null => fail open). The doc comment now describes the null/fail-open contract rather than justifying the original migration. - Use a consolidated exception filter (ex is not (OperationCanceledException or OutOfMemoryException or StackOverflowException)) for the context-aware filter pass in LibraryController. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4f53cab to
3b62983
Compare
|
@T4g1 rebased onto canary (now v1.0.11) as requested — the CHANGELOG conflicts are resolved and both the relevance-filter commit and the dropped-3030-category follow-up replayed cleanly. Pipeline is green locally (Release build + 134 search/filter tests pass). Ready for the re-run whenever you are. |
@kevinheneveld The changelog is no longer needed at all. Please update the PR description to match the template. |
3b62983 to
96a3998
Compare
|
Rebased onto canary v1.2.1. This needed more than a replay — canary refactored the whole search subsystem since approval, so I re-homed the change: |
96a3998 to
f222f75
Compare
|
Rebased onto canary v1.3.17 (clean replay) and marked ready. Nothing changed in the diff since @T4g1's approval beyond the rebase; still applies as-is because the auto-pick paths run no filter pass on current canary (the pipeline only runs inside |
therobbiedavis
left a comment
There was a problem hiding this comment.
Since you folded in additional logic, there were some additional findings.
I found two regressions that prevent valid automatic downloads. The Patterson concert is correctly rejected, but the exact-match cases in the inline comments are rejected too. I’d request changes for those two points.
99cb48b to
f5490b0
Compare
|
Thanks for the live comparison — both findings addressed in f5490b0 (rebased onto v1.3.22, one commit). Pipeline scope (DownloadService:170): the relevance check is now a standalone Author corroboration (RelevanceFilter:66):
|
therobbiedavis
left a comment
There was a problem hiding this comment.
The earlier metadata-filter and structured-artist issues are fixed. I found three remaining regressions in the new gate: valid releases can be rejected because of long subtitles or additional author credits, Unicode text and equivalent Unicode encodings, and apostrophe-omitted release names.
Two mixed-result cases also queue the wrong book after the correct release is filtered out: Oliver Twist for David Copperfield, and Anna Karenina for War and Peace. Both reproduce through the API and the natural scheduled worker. I'd request changes for the three inline points, with regression coverage for the variants described there.
Verified against separate live Linux containers for base d220aa8 and head f5490b0, each with a fresh database: 74 API outcomes and 10 scheduled-worker outcomes checked against submitted torrent payloads and persisted download records. Indexers and qBittorrent were controlled HTTP fixtures; no real media transfers or imports were performed.
f5490b0 to
aac85e4
Compare
|
All three threads addressed in aac85e4 (rebased onto canary v1.3.23, still one commit); details are on each inline comment. In short:
The mixed-result selection is now covered end to end: Gates locally: 60 gate unit cases + 13 end-to-end cases + architecture tests green (118/118 in the slice); the wider |
therobbiedavis
left a comment
There was a problem hiding this comment.
The earlier findings are fixed. I found one remaining gap where adding a subtitle bypasses the author check and allows the wrong book to be queued. I’d request changes for the inline comment.
3746c95 to
a39d805
Compare
|
Round 5 addressed in a39d805 (rebased onto canary v1.5.2 over the merged #714/#605, one commit); details on the inline thread. In short: the author check now keys off how many title words the result shares, not how long the stored title is, so a subtitle cannot bypass it ( Gates locally: gate 77 + |
therobbiedavis
left a comment
There was a problem hiding this comment.
@kevinheneveld the subtitle issue is fixed, but the edition stop words are still removing actual title words. i’d request changes for the inline below. i added a possible fix, but feel free to take another approach.
a39d805 to
4b447b1
Compare
bef48f5 to
9e4c780
Compare
|
sorry for jumping in and adding commits to your branch. you'd given me the okay in discord to experiment and commit fixes if i found a way to resolve the findings. after several review rounds, i wanted to work through a concrete solution rather than keep sending findings back and forth. compared with
the backend run passed 4,042 tests, with 270 skipped. the additional live comparison passed all 90 cases. all 37 intended downloads matched the client receipts and saved rows; the other 53 produced no download. another 47 evidence, boundary and mutation checks passed. some examples from the pr discussion, checked against the live test results:
i also benchmarked both heads in fresh containers with identical limits. across three runs, median processing time increased from 2.6 to 6.6 ms at 100 results, and from 31.2 to 46.2 ms at 1,000, including the new freshness check. allocation at 1,000 results increased from 10.9 to 26.7 MB per search. that's an acceptable cost for the additional checks in this workflow. the version, language-detection and torrent-removal differences in the wider comparison came from the canary rebase. please review what i've added and let me know if you'd prefer a different approach. |
|
@therobbiedavis Thanks for working this through end to end, and no apology needed. The shared identity-plus-quality path, the deferral to manual, and the freshness fingerprint are all better than the gate, and I'm happy to go with your design. One concern from replaying my own download history (712 rows, all Prowlarr Torznab/Usenet, so the author has to come from the release name): the current policy auto-confirms 7 of 269 releases that actually imported, while rejecting 59 of the 60 that were wrong-book grabs. The precision is exactly what we want. The recall problem is "unexplained release text": series tags ( Would you be open to a middle tier where, once the complete title and a complete credited author are both found, release annotations of those shapes are explained rather than blocking? I can send the anonymized release-name set if useful. Also for the record, the round-6 artist-prefix strip in my gate had a bug your replacement fixes: Prowlarr infers Artist as the text before the first dash, so title-first releases with a series tag lost their title entirely. |
|
@kevinheneveld thanks for replaying your history. 7 out of 269 makes it clear we're deferring too many valid releases. our tests covered the supported layouts, but missed a lot of normal release naming. yes, i'm open to that middle tier. once the complete title and credited author are established, we should explain recognized series, group, bitrate and narrator annotations. conflicting book numbers or another work's title should still block the download. please send the anonymized set, including the wrong-book grabs. i'd like to use it as a regression corpus and look at the incorrect download we're still confirming too. that gives us something concrete to test against instead of guessing at more naming rules. |
|
@therobbiedavis Here is the set: https://gist.github.com/kevinheneveld/f158b2ee8a7e7a4def79321a8c7b8c94. 498 distinct release-name cases from Prowlarr-fronted Torznab and Usenet indexers, each with the library book the grab was attributed to, the outcome ( Two notes on labels. Happy to re-run the replay against any revision you push. |
032e9df to
3f31edc
Compare
|
@kevinheneveld thanks for the set and the clarification on the labels. i've pushed a738d6d1. the parser now handles supported bitrate, group, series and narrator annotations, plus dotted author credits and stored subtitle joins. complete title/author evidence is still required, inferred Artist isn't independent corroboration, and unknown or conflicting text stays manual. series positions and unlabeled narrators are checked against library metadata. i replayed all 498 cases. Critical Mass, Food of the Gods and the Patterson/Thriller grab still reject for the wrong-book targets. Fires of December stays confirmed. the Godling Chronicles example also passes identity matching now; automatic download still depends on the normal quality and selection checks. could you review the changes and rerun that revision against your full library metadata? if a valid release still defers unexpectedly, please include the case index and stored series, memberships and narrators. those fields weren't in the export and would help reproduce the decision. |
…t to the audiobook
Automatic search (the background sweep and the search-and-download
endpoint) grabbed whatever the indexers returned for an author/title
query and let the quality scorer rank it. A result that shares a single
token with the query — a Patterson Hood concert recording offered for a
James Patterson audiobook, or a different Dickens novel offered on the
author's name — scored on format and seeders like any other release and
was sent to the download client.
Add an AutomaticSearchRelevanceGate that runs on the two automatic paths
only, before quality scoring. The title is judged on its own: the
audiobook's main title (before a subtitle separator) or its full title
must reach half of its significant tokens in the result's title. The
Artist field never supplies title evidence and a leading artist credit
in the title is stripped first (unless that prefix is the book's title,
as when Torznab infers Artist from a title-first release), so an
author's name never stands in for a title word: a same-author decoy is
rejected and a long stored subtitle cannot dilute a release that names
the main title. A match that rests on a single shared
title word additionally needs one credited author named in the result,
whether the stored title is that one word ("Betrayed") or a subtitle
padded it out ("Dune: A Novel"); a match on several title words ("The
Final Empire" for "Mistborn: The Final Empire") stands on its own. The
gate fails open when it cannot judge: no significant title tokens, or a
generic match with no known author.
SignificantTokens tokenizes like IndexerQuerySanitizer builds the query:
Unicode words in any script, NFC-composed first, apostrophes dropped
without splitting, hyphens splitting. Edition descriptors ("A Novel",
"Unabridged Edition") are not stop words — that would strip real title
words from "A Novel Obsession" or "The Novel". FromTitle omits them at
the segment level instead: a separated segment that is nothing but a
descriptor and a trailing descriptor phrase inside a segment are
dropped, the first segment is always a title, and a stored main title or
credited author that is literally such a word is protected.
The gate is deliberately separate from SearchResultFilterPipeline. Those
filters are tuned for metadata-page results and reject legitimate raw
indexer releases: "The Lord of the Rings" as product-like (ring) and
"Man's Search for Meaning" as promotional (search). The automatic paths
never run that pipeline; manual search is untouched.
SearchAndDownloadAsync moves into a DownloadService partial to keep the
file under the 500-line cap.
Tests: AutomaticSearchRelevanceGateTests (the verdict rules, the
tokenizer, and the review regressions), DownloadService_AutomaticRelevanceTests
(end to end through SearchAndDownloadAsync with a strict pipeline filter
proving the metadata-page filters are never consulted) and
AutomaticSearchProcessorRelevanceTests (end to end through the sweep).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Assess every candidate with shared identity and existing quality scoring across automatic API, worker, and fallback searches. Preserve contributor provenance and complete title evidence, normalize known subtitle relationships, and enforce torrent seeder requirements consistently. Validated with 3,982 passing backend tests (270 skipped), 326 live API cases and 22 scheduled-worker cases in the agreed scope. Punctuation-only author metadata hardening remains deferred.
Bind complete titles, stored subtitles, author credits, series positions, narrators, and bounded release annotations through the shared identity and quality path. Preserve source spans, provenance, manual deferral, parser budgets, and pre-handoff freshness checks. Add the frozen historical corpus and policy regressions, including normalized subtitle joins and dotted author cues. Verified with 4571 passing backend tests, generated adversarial controls, 150 live API cases, 96 worker cases, and five freshness checks.
a738d6d to
d1d1fb9
Compare
|
@therobbiedavis Replayed d1d1fb9 against the live library with series, memberships and narrators supplied (
Weighted by grab count the imported figure is 141 of 254 rows, up from 7 of 269 at 032e9df. Library facts never flipped a confirm into a deferral. Precision holds: no wrong-book release is confirmed in any bucket, and Critical Mass, Food of the Gods and the Patterson/Thriller lure still reject. The five import-blocked cases now confirmed (Dark Tower II and III, Other Worlds Than These x2, Chain Reaction, Odyssey) are all the right book; their jobs failed on Remaining deferrals on valid releases, by pattern, with case indexes and the span left
I added |
Summary
Adds a relevance gate to the two automatic-search paths so they stop queuing downloads that merely share a surname or a common word with the audiobook — e.g. a Patterson Hood concert recording matched to a James Patterson audiobook, or a different Dickens novel matched on the author's name. The gate judges whether the release names the book; it fails open when it cannot judge, and manual search keeps the old behavior so users browsing results still see everything.
Changes
Added
AutomaticSearchRelevanceGate(Search/Filters/), a standalone class — deliberately not anISearchResultFilter. TheSearchResultFilterPipelinefilters are tuned for metadata-page results and reject legitimate raw indexer releases ("The Lord of the Rings" → product-like "ring", "Man's Search for Meaning" → promotional "search"), so the automatic paths run this gate alone and never consult the pipeline.Evaluate, returns aRelevanceVerdictwith the reason and both scores for the log line)::,;,-,(,[) or its full title must reach 50% of its significant tokens in the result's title. TheArtistfield never supplies title evidence, and a leading artist credit in the title ("Frank Herbert - Dune") is stripped before the title is judged (unless that prefix is the book's title, as when Torznab infersArtistfrom a title-first release), so an author's name can never stand in for a missing title word: "Charles Dickens - Oliver Twist" is rejected for David Copperfield, and a long stored subtitle ("Good Omens: The Nice and Accurate Prophecies of Agnes Nutter, Witch") cannot dilute "Good Omens [M4B]".Artistfield counts the same as an author embedded in the title (MyAnonamouse returns Title "Dune" / Artist "Frank Herbert"). A generic match with no known author fails open.SignificantTokens: the stop-word-filtered tokenizer the gate uses. Tokens are runs of Unicode letters/digits in any script (native-script titles such as "Война и мир" keep their words); text is NFC-composed first so precomposed and decomposed spellings produce identical tokens in both the title and theArtistfield; and normalization followsIndexerQuerySanitizer, which builds the query the indexer answered — hyphens split ("Full-Cast" → full, cast) and apostrophes are dropped without splitting ("Ender's Game" → enders, game), so a release named after the sanitized query ("Enders Game") tokenizes like the stored title. Edition descriptors are handled bySignificantTokens.FromTitleat the segment level rather than as stop words: a separated segment that is nothing but a descriptor ("Dune: A Novel (Unabridged Edition)" →dune) and a trailing descriptor phrase inside a segment ("Dune Unabridged Edition" →dune) are omitted, but the first segment is always a title and a descriptor word inside a longer segment is kept, so "A Novel Obsession", "The Novel" and "The Last Edition" keep their real words. A stored main title or a credited author that is literally such a word ("Dune - Novel" by the author "Novel") is protected.Changed
AutomaticSearchService.ProcessAudiobookAsyncandDownloadService.SearchAndDownloadAsynccallrelevanceGate.Apply(results, audiobook)after the raw search and before scoring. Rejections are logged with the reason (title_not_relevant/author_not_corroborated) and both title scores.SearchAndDownloadAsyncmoved into aDownloadService.SearchAndDownload.cspartial to keepDownloadService.csunder the 500-line cap.ListenarrWorkflowRegistration(and in the testServiceCollectionBuilder).Testing
AutomaticSearchRelevanceGateTests(114 cases): the bug-report scenario (Patterson Hood vs Dog Diaries), both naming orders for David Copperfield, the metadata-filter regressions (Lord of the Rings, Man's Search for Meaning), the MyAnonamouse split title/artist shape, long stored subtitles (Good Omens, David Copperfield), same-author decoys rejected (Oliver Twist, Anna Karenina, Neverwhere, Speaker for the Dead), a generic title corroborated by any one of six credited contributors (Stories), apostrophe-omitted release names with straight and curly controls (Ender's Game), native-script titles and authors, NFC/NFD equivalence in both the title and theArtistfield (Les Misérables, Saint-Exupéry), generic subtitles never bypassing the author check ("Dune: A Novel" vs "Dune" by John Doe / "The Shining: A Novel" by Stephen King, with the corroborated and no-known-author controls, and "The Final Empire" still passing for Mistborn), edition words that are real title words ("A Novel Obsession", "The Novel Cure", "The Last Edition" exact title-only releases kept; "The Novel" still rejecting an unrelated release), unseparated and Unicode-separated edition metadata never supplying identity ("Dune Unabridged Edition" vs "The Shining Unabridged Edition"), theArtistfield and an identified author prefix never supplying a title word ("Novel Writer - Obsession" for "A Novel Obsession", with-/−/,separators), title-first releases whose inferredArtistis the title, descriptor-shaped author names and compound titles,MainTitlesplitting, and the tokenizer's Unicode/apostrophe/hyphen/edition-descriptor behavior.DownloadService_AutomaticRelevanceTests(14 cases, end to end throughSearchAndDownloadAsyncwith a strictISearchResultFiltermock registered in the pipeline): exact matches reach the client submission; the off-topic release does not; and with a better-seeded same-author decoy present, the correct release is the one submitted (David Copperfield vs Oliver Twist, Война и мир vs Анна Каренина, NFD "Les Misérables", "Enders Game", "Good Omens [M4B]", and "Stories" with Artist = one of five credited authors); for "Dune: A Novel", the better-seeded "The Shining: A Novel" (Stephen King) and an uncorroborated "Dune" are passed over for "Dune" / Artist "Frank Herbert", and nothing is submitted when only those two are returned. The strict mock proves the metadata-page filters are never consulted on this path.AutomaticSearchProcessorRelevanceTests(2 cases, end to end through the sweep'sRunCycleAsyncto the download-service handoff, modeled onAutomaticSearchProcessorFallbackTests): the same two wrong-author releases never reachStartDownloadAsync(strict mock), and the corroborated release is the one handed over.dotnet format --verify-no-changesand the architecture tests are green. The round-6 edition-descriptor/artist-evidence design and its regression tests are @therobbiedavis's proposal from review, adopted as-is.Notes
SearchResultScoreralready penalizes language mismatches).🤖 Generated with Claude Code