Skip to content

Bulk matching: refresh matched books, and overwrite replaces series - #121

Merged
KodeStar merged 6 commits into
mainfrom
claude/trusting-rubin-qccgpl
Oct 11, 2026
Merged

KodeStar merged 6 commits into
mainfrom
claude/trusting-rubin-qccgpl

Conversation

@KodeStar

@KodeStar KodeStar commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #122 (series rules). The base is that branch so this diff shows only this PR's changes. Merge #122 first; GitHub then retargets this to main. Docs: KodeStar/audiosilo-docs#121.

Summary

A new refresh match-run mode looks up books that already have an ASIN or ISBN, by that identifier alone, to fill in or overwrite their details. Revised here so it also cleans up books an earlier match stamped with wrong series: every edition, variant and translation of a series, written as community overrides.

Refresh mode (the original PR, kept)

  • Mode: mode: "refresh", over catalog.MatchedBooks (every book with an ASIN or ISBN).
  • Lookup: each book is looked up by its own identifier; the ISBN is tried when the ASIN finds nothing.
  • Outcomes: the record found is auto. An identifier the community doesn't know is none / identifier_unknown, never a fuzzy match by title.
  • Identifiers are never written: the proposal carries none, so only fill and overwrite apply (ScopesFor; ErrScope gives 400 for ids).
  • Audit and console: audited as book.match_refresh; the button is Fill in matched books in Health > Not matched.

Revisions in this round

Overwrite replaces series, never unions them.

  • Plan returns Writes (set, revert, cover).
  • Under overwrite, a book's series become the community's for its recording, under Series robustness: series rules for matches and rails, Series page rail picking #122's series rules.
  • A series, series_index or more_series value the community wrote earlier that it no longer offers is reverted to what the files say. This covers a series the community deleted or merged, and an edition or translation the rules leave out.
  • fill and ids never revert.
  • A position comes back from the files only beside the files' own series.

Series the user typed survive.

  • Overrides record their origin (source: edited vs community). plan never touches an edited field, and only overwrite reverts.
  • editTx now deletes only community rows when the edit's source is community, so an admin edit made after the run looked survives a revert.
  • A set still overwrites an edited field, because the match dialog's ticked edited field must.

Redirects.

  • Books store series names, not slugs. A retired work slug answers with a 301 that the meta client follows, in remote and mirror mode, so overwrite writes the merged record's current series names.
  • Covered by TestCandidatesFollowARetiredSlug.

The review shows removals.

  • Each scope's changes carries series: {from, to} when it changes the book's series.
  • The dialog shows "Series: A Demo mode #2; B Demo mode #2 → A Demo mode #2" for the chosen scope, so a removed series is never silent.

Safety and efficiency.

  • A refresh records only books a scope would change, so an already-clean library leaves no items.
  • Each proposal names the identifier it was found by (found_by). Applying it does nothing on a book matched again since.
  • Repick runs don't read series records.

Docs: CLAUDE.md, and the API reference plus User Guide in the docs PR. That docs PR covers what the original body listed as "Not in this PR": the refresh mode, book.match_refresh, and repick items carrying only changes.ids.

Re-review of the original PR

  • Correctness, tests and i18n were checked in all 6 locales; new strings were added to all 6.
  • Fixed:
    • planFor deleted identifiers that the proposal never carried (dead code);
    • review items were computed for scopes a mode doesn't take;
    • the "Use community values" description now says what it does without implying that the per-book match dialog removes series.

Wire (additive; admin-only, so the frontend repo needs no type change)

  • GET /admin/match-runs/{id}/items:
    • changes.<scope>.series {from, to};
    • proposal.found_by;
    • changes has only the scopes the run's mode takes.
  • POST /admin/match-runs accepts mode: "refresh".
  • PATCH /admin/libraries/{id}/book with source: "community" and revert now removes only a community value. The console never sends that combination.

Verified

  • Gates: npm run check (668 tests), go build ./..., go vet ./..., go test -race ./..., golangci-lint run (0 issues).
  • Tests, each with an allowed and a denied case:
    • overwrite replaces and reverts; fill and ids don't;
    • an edited series stays;
    • a community revert keeps an edit;
    • a refresh cleans a polluted book end to end, and a second refresh records nothing;
    • a book matched again isn't overwritten;
    • a retired slug is followed;
    • the console shows "old → new".
  • Not checked in a real browser: the refresh review dialog. The series line is covered by a vitest/testing-library test only. Series robustness: series rules for matches and rails, Series page rail picking #122's browser check covered the Series page and the match dialog.

Left for a human

  • Field labels for reverts: the review's "Sets {{fields}}" line lists reverted fields as set. The series line shows the removal, but telling set and revert apart needs a wire field.
  • Scopes in the console: the console hard-codes refresh's scopes. Sending scopes on the run would remove that copy.
  • Match dialog: accepting a match by hand doesn't revert stale community series; only bulk overwrite does.
  • Load on the shared service: a refresh still makes a lookup and a work fetch per matched book on remote metaserve (about 2 requests per book; cheap in mirror mode).
  • Apply memory: MatchItemsToApply loads every pending item at once, which matters for very large refresh runs.

claude and others added 6 commits October 10, 2026 22:36
Bulk matching only ran over books with no ASIN or ISBN, so books matched by
an identifier alone (the manager's enrichment, an ids-only apply) never got
their title, series, position, narrator, description or cover filled in.

A new refresh run mode takes every book with an identifier, looks it up by
that identifier alone (the ISBN when the ASIN finds nothing) and treats the
record as confident. Applying fills or overwrites the details under the usual
scopes but never writes an identifier, so matchrun.ScopesFor allows only fill
and overwrite for it. Audited as book.match_refresh. The console adds a
'Fill in matched books' button beside Find matches.
A match wrote every series of a work, and overwrite only ever added:
series the community has since deleted, merged or now leaves out
(editions, translations) stayed. Plan now returns Writes: under
overwrite, a series value the community wrote that it no longer offers
is reverted (back to what the files say). An admin's own series stay;
a community edit's revert never removes an edited override. The review
shows a scope's series before and after (changes.series).
…t0y7m' into claude/trusting-rubin-qccgpl

# Conflicts:
#	internal/matchrun/plan_test.go
#	internal/matchrun/runner.go
… cleanup

- the review's change line for a scope shows the book's series before
  and after (changes.series), so a removal is visible
- overwrite / refresh hints say series an earlier match added are
  cleaned (all six locales)
- tests: overwrite replaces and reverts, an admin's series survive, a
  community revert keeps an edit, a refresh cleans a polluted book, a
  retired work slug is followed
- Writes.after is the one rule for a field's value once written; the
  review's series change builds on catalog.SeriesList (now exported)
- a refresh's proposal carries no identifier, so planFor needs no case
- a refresh records only books a scope would change
- the community-revert guard is decided in Go
- console: SeriesRef, one series formatter, one series line; the
  overwrite description no longer says the match dialog removes series
- overwrite puts the files' series position back only beside the files'
  series, and reverts other series only when the community lists none
  (not when its list is too long to store)
- a refresh logs a book it couldn't read instead of passing it over
- a refresh proposal names the identifier it was found by (found_by);
  applying it skips a book matched again since
@KodeStar KodeStar changed the title Add refresh match run mode to fill in details for books with identifiers Bulk matching: refresh matched books, and overwrite replaces series Oct 11, 2026
@KodeStar
KodeStar changed the base branch from main to claude/admin-series-robustness-1t0y7m October 11, 2026 12:31
Base automatically changed from claude/admin-series-robustness-1t0y7m to main October 11, 2026 13:00
@KodeStar
KodeStar merged commit 791d67e into main Oct 11, 2026
3 checks passed
@KodeStar
KodeStar deleted the claude/trusting-rubin-qccgpl branch October 11, 2026 13:00
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