Repository navigation
Series robustness: series rules for matches and rails, Series page rail picking - #122
Merged
Merged
Conversation
…il picking
The community lists every series a work is in, including variant reading
orders, translations and editions named for one kind of recording
("Harry Potter (Full-Cast Editions)", "Discworld (abridged)"). A match
stamped all of them onto a book, and the rail cap (3) let editions push a
book's real sub-series out of /meta.
- meta: series rules (seriesrules.go). Match candidates' series leave out
variant orders, translations, other-language series and editions no
recording is; an edition carries the recordings it holds, and
SeriesFor picks the book's series for its recording. Rails apply the
rules before the cap.
- matchrun: Propose takes the book's series for the chosen recording.
- meta: the frozen search fallback cuts a numbering prefix ("BV01 - ").
- console: the match dialog applies the same rules (seriesFor); the Series
card picks its rail by name, a reading order's name, a looser name, or
most of its own books, else none (no more falling back to the book's
first rail); an unnumbered book fills the missing entry its title is.
- recordings carry their own series (MatchRecording.series), so the console and matchrun read the rule's result instead of copying it - one edition-kind classifier; NamesMatch for narrators, the meta module's PrimarySubtag for languages - series facts read once per series across a match's candidates, concurrently; a repick reads none; smaller cache quota; facts the cache knows filter rails before the cap - the frozen search fallback is left as it was (works/match cuts the numbering prefix) - console: loose series key as the server's NormalizeSeries; title keys computed once
- a narrator edition after a colon ("Harry Potter: Narrated by ..."),
and a narrator named beside "Unabridged", read as editions
- a translation is foreign only when its original is in the work's
language (a German work keeps Scheibenwelt)
- rails pick editions by the recording the lookup named, never the first
recording guessed; the book's own edition rail goes first, as it is its
main series
- a main view found foreign hands its rail slot on (bounded)
- series facts: at most 16 records, 5 s, none for a work they can't rule on
- console: only a name's head before a colon or dash is a parent series
("Mistborn Trilogy" can take the Mistborn rail)
This was referenced Oct 11, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The community metadata has recording-variant series ("Harry Potter (Full-Cast Editions)", "Discworld (abridged)"), translations and duplicate works, which a parallel audiosilo-meta session is cleaning up. This PR makes the server and the admin console robust to that data instead of turning it into a bad UI.
Cleanup of books that already carry wrong series is in the revised #121, which is stacked on this branch. Docs: KodeStar/audiosilo-docs#121.
1. Series page showed the wrong series' gaps
Root cause, found in the real data:
/metabuilds at most 3 rails (maxSeriesRails), so the "Discworld: Tiffany Aching" rail was never fetched.pickRailthen fell back torails[0](Discworld, 49 books). The names matched fine (seriesKeyagrees on "Discworld: Tiffany Aching").Server fix: rails now follow the series rules below before the cap.
Console fix (
pickRail): the card picks, in order:match.NormalizeSeries);A rail named like the series' head before a colon or dash is never used ("Discworld" for "Discworld: Tiffany Aching"); that is the parent series.
Why that threshold: a book's work sits on every series it is in, so one shared book can't tell series apart (that was the bug). Two books that make up most of the series are this series spelled another way. Ties go to the smaller rail, the more specific series.
2. Bobiverse: unnumbered, untagged books
works/match) already sends titles raw, and metaserve cuts these prefixes itself (leadCodeREand friends in itsmatchscore.go). Match automatically therefore finds "BV01 - We Are Legion (We Are Bob)" as is.works/searchfallback does not strip them. I left it frozen per CLAUDE.md, since it is slated for deletion.3. Match stamped every series of a work onto the book
New
internal/meta/seriesrules.go. A match candidate and the rails never treat these as a series of the book:ordering_of);translation_of) of another of the work's series that is in the work's language;How editions are read: the community API links no series to a recording (series
sourcesaren't served), so they are read from the name. An edition is a series named exactly like another of the work's series plus a bracketed qualifier, or a "narrated by" tail. The qualifier says which recordings hold it:"(Unabridged)", language editions and ordering notes hold none. A name that only extends another ("Discworld: Tiffany Aching") is its own series.
Main series: for the book's recording, an edition named for it is the main series; otherwise the work's primary series is.
Wire changes (additive):
MatchSeries.id.MatchRecording.series: the list for a book that is that recording, main first. The match dialog andmatchrun.Proposeread it, so the console has no copy of the rule.MatchCandidate.seriesis now the list for every recording (no editions).Series facts (
language,translation_of) come fromseries/{id}, which the server already fetched for rails. They are read once per series per request, concurrently and bounded (16 records, 5 s), and cached in a news:key space. A repick reads none.Frontend repo
No change is needed in
KodeStar/audiosilo-frontendsrc/api/types.ts. The player-facing/metaenvelope keeps its shape; only which rails it holds, and their order, change. The new fields are on admin-only match endpoints.Verified
scripts/build-admin.sh(npm ci; check = typecheck + eslint + prettier + 665 vitest tests; build),go build ./...,go vet ./...,go test -race ./...,golangci-lint run(v2.14, 0 issues)./web, not vendored here);/metaagainst the real meta.audiosilo.app (network blocked here; covered by tests and the fake service);Left for a human
edition_oflink), and putlanguage/translation_ofon a work's series refs. Then the name heuristic and the extra series fetches can go./meta. A sturdier fix is an admin endpoint that returns the rail of the series the card draws.