Skip to content

feat(metadata): Nasjonalbiblioteket as an opt-in primary provider (#2979) - #2995

Merged
vavallee merged 24 commits into
vavallee:mainfrom
francisrath:feat/metadata-nasjonalbiblioteket
Oct 7, 2026
Merged

vavallee merged 24 commits into
vavallee:mainfrom
francisrath:feat/metadata-nasjonalbiblioteket

Conversation

@francisrath

@francisrath francisrath commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds Nasjonalbiblioteket (the National Library of Norway) as an opt-in primary metadata provider. NB holds Norwegian publications by legal deposit under their original Norwegian titles, where other providers mostly hold the English translations, so a Norwegian library's files match the author's catalogue instead of falling back to weak fuzzy matches.

Closes #2979.

  • New internal/metadata/nb package implementing metadata.Provider, plus GetAuthorWorks / GetAuthorWorksSnapshot.
  • nb accepted for metadata.primary_provider; nb: foreign IDs route to it.
  • Opt-in only: unlike every other provider, NB is not registered as an enricher when something else is primary, so installs that don't select it send NB no traffic.
  • Settings → Metadata Profiles selector entry with help text; provider labels and a link to the record's nb.no page for nb: books.

Review follow-ups (from #2979)

# Request Change
1 Fragment style changelog.d/2979-… now uses "Title (#2979). Text."
2 429 / 5xx as provider errors get wraps providererr.ErrRateLimited for 429 and providererr.ErrUnavailable for 5xx; a 4xx stays a plain error.
3 Skip per-work GetEditions for nb Skipped, and the work's own editions are used as the evidence instead of leaving it indeterminate as for DNB, since NB works arrive with every edition attached. A work with no editions attached stays indeterminate. If you'd rather match DNB exactly, it's a one-line change.
4 docs/third-party-data.md section The delivered records are CC0 while the search API Bindery reads states no licence of its own; publisher-written summaries; covers excluded; Sikt authority file under NLOD 2.0, with its attribution line for redistribution.
5 Authority found, search empty → incomplete Reported as a partial catalogue, never complete and empty.
6 Docs: leaving NB as primary User guide and architecture notes say NB-linked authors stop syncing until relinked.

The optional gated live test is not included. Its assertions would have to name real books, and I'd rather keep real titles out of the repo; happy to add one asserting structure only (works found, series numbered) if you'd like it.

More from the same requests

Added after review, with no extra requests to NB or Sikt:

Data Source Goes into
Narrator narrator credits (nrt) on audiobook editions Book.Narrator
Running time the audiobook record's extent (1 lydfil (11 t, 16 min), 21:34:00, (3 t, 7 min)) Edition/Book.DurationSeconds
Genres subject genres, minus format terms and Nynorsk twins Book.Genres
Name variants the authority record's other name forms (MARC 400) Author.AlternateNames, kept as aliases only when LatinAliasBinds accepts them

What the NB API supports (investigated, not assumed)

Question Finding
Endpoint GET https://api.nb.no/catalog/v1/items; OpenAPI at /catalog/v1/items/api-docs. The deprecated services/search/v2 is unused.
Auth None for search. The OAuth flow on digitalpreservation.no is for the DPS submission API only.
Search ISBN via q=isbn:<n> (any media type, so audiobook ISBNs resolve); author search via the api_nameauthor index; an author's works via filter=namecreators:"Last, First" (the author index misses many records, and cre credits count as authorship); title via free q with searchType=FIELD_RESTRICTED_SEARCH (the default also searches OCR'd full text).
Author credits Only with expand=metadata: people[] with MARC relator roles and a Norwegian authority-file ID.
Author by ID Not supported by NB search, so the name heading comes from the authority file's public read API (authority.bibsys.no), and catalogue records are kept only when they credit that ID as author, which drops namesakes and translator/narrator credits.
IDs sesamId on every record (nb:<sesamId>); URNs exist only for digitised items. Authors: nb:author:<authority id>.
Works NB has no work record. Editions are folded into works by author and title; translations join their original through the cataloguer's original/uniform title, and a volume catalogued under its series' name (<series> : <volume>) takes its own title from the part name.
Series The name is in the search JSON (mixed with publisher imprint series); the number is in each record's MODS. Only the author's own series is taken: the MODS entry linked to their authority record.
Name index gaps NB leaves some records out of every name index although they credit the author correctly. A search on each of the author's series names recovers them (one extra request per series).
Covers NB's image service refuses in-copyright books; the images some records link to are licensed to library catalogues only. No CoverProvider.
Paging Max page size 100; author catalogues are capped at 20 pages (the creator index counts every translation) and reported partial beyond that.
Rate limits None published, none in response headers.

Behaviour notes

How it was verified

  • Unit tests use fixtures in the exact shape of NB, authority-file and MODS responses, with an invented author, titles and ISBNs. They cover: author search; ISBN lookup (audiobook); a translation folded into its original; a volume titled by its series; series and number from MODS (imprint series excluded, an unlinked entry of a known series accepted, MODS failure non-fatal); recovery of volumes missing from the name index; a catalogue split by a spacing slip; capped, empty and failed-recall catalogues reported partial; 429/5xx mapping; reconciliation using a work's own editions; not-configured path; malformed IDs rejected before any request.
  • Live against the real API, through the aggregator with NB as primary: Norwegian author search resolves to the NB record; catalogues list books under their original titles; an audiobook ISBN resolves to its work with the audiobook edition attached; two long series resolve to one series row each with every position in order, including volumes NB's name index misses; an English-language author absent from NB falls back to OpenLibrary.

Checklist

  • Every commit carries a Signed-off-by that matches its author (git commit -s)
  • Changelog fragment added as changelog.d/2979-metadata-nasjonalbiblioteket.md
  • Tests added or updated
  • docs/DEPLOYMENT.md updated if env vars, config, or upgrade path changed (no change needed: no new env var or config)
  • Wiki pages under docs/ updated (User Guide, architecture, third-party data)
  • No new dependency

Test plan

  • make check steps: gofmt, Go version check, build, vet, golangci-lint v2.11.4, govulncheck, make test, web typecheck/lint/build/test, licence drift
  • Race detector on the changed packages (internal/metadata/..., internal/models, cmd/..., reconciliation tests in internal/api); the full internal/api and internal/db race shards run in CI

🤖 Generated with Claude Code

@github-actions github-actions Bot added the bindery-notified Discord notification already sent for this PR label Oct 4, 2026
@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

@vavallee vavallee left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @francisrath, this came back really clean. All six asks are in, the fragment reads right, and the 429/5xx mapping plugs straight into discovery's backoff. Fixtures with invented data are a nice touch too.

Three small things and it's good to go:

  1. Edition evidence on a partial catalogue. I like using the work's own editions instead of going indeterminate like DNB. But when the snapshot is partial (over the page cap, or a series recall failed) a work can be missing editions, and right now a short edition still makes it a page count candidate. Could you only use them when snapshot.Complete is true and leave it indeterminate otherwise? Adding that case to your test would be great.
  2. Authority record gone. In GetAuthorWorksSnapshot, a 404 or deleted authority record returns an empty catalogue marked complete. Sikt merges duplicate records now and then, and reconciliation would then offer to remove all of that author's wanted books. Please report it as partial, same as your empty search fix.
  3. Docs. Please add the CC0 source to the Nasjonalbiblioteket section in third-party-data.md: https://doc.aja.bs.no/hente/bibliografiske-data/mlnb.html

Tiny nit: the GetEditions comment says one record, but it returns the whole work.

No need for the live test, the fixtures cover it. Once these land I'll merge it for v1.41.0, right after the v1.40.1 patch ships. Really nice work!

@francisrath

Copy link
Copy Markdown
Contributor Author

Thanks @vavallee, all three are in, plus the nit:

  1. Edition evidence on a partial catalogue (fa89d8bd): a work's own editions are used only when snapshot.Complete; on a partial catalogue NB works stay indeterminate. The reconciliation test now runs both a complete and a partial snapshot, and the partial one makes no candidates.
  2. Authority record gone (67a7d432): a 404 or a deleted authority record now returns a partial catalogue instead of a complete empty one, covered by a test for each case.
  3. Docs (cd8d1e44): the CC0 source is linked in the Nasjonalbiblioteket section of third-party-data.md.

Nit: the GetEditions comment now describes the whole work it returns.

make test, lint, govulncheck and the race detector on the changed packages pass locally. Marking this ready for review.

@francisrath
francisrath marked this pull request as ready for review October 4, 2026 21:43
vavallee added a commit that referenced this pull request Oct 4, 2026
…#2998)

Review of #3000 found two holes in the import language check.

Only the first dc:language of the first EPUB counted, so an EPUB tagged
fr then en was blocked under an English profile while the same file
tagged en then fr imported, and a release with A.epub (sv) and B.epub
(en) was rejected while the reversed names imported. EpubMetadata now
also carries Languages (every dc:language, normalised, deduped; Language
is unchanged for existing callers). The gate classifies each candidate
EPUB as allowed when any declared language is allowed, and rejects the
release only when some EPUB is disallowed and none is allowed, the
shape of the format gate. A mixed release imports the allowed EPUBs and
skips the rest in the ebook loop, as the format check skips a format.
Relabelling under a restricted profile picks the first allowed declared
language, so an "und, en-GB" EPUB no longer relabels the book "und".

The two letter table held 25 codes, so uk, he, sk, fa, is and the rest
normalised to nothing known and passed as "no language". It now covers
all 183 ISO 639-1 codes, with English names for every code it yields,
and cmn/yue fold onto chi.

The Norwegian fold is renamed iso639NorwegianToB and matches
francisrath's #2995 hunk line for line so his rebase drops it cleanly.

Docs note that External import mode is not checked.

Co-authored-by: francisrath <fr@ncis.no>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9
Signed-off-by: vavallee <vavallee@protonmail.com>
vavallee added a commit that referenced this pull request Oct 4, 2026
…ile does not allow (#2998) (#3000)

* fix(importer): reject a download whose file is in a language the profile does not allow (#2998)

The language filter at grab time reads only the release name, so a
release that does not say its language passes it. At import #1933 then
read the EPUB's dc:language and relabelled the book to match, so an
English only library quietly filled with Swedish and Dutch editions that
showed as imported and were never searched again.

When the author's metadata profile lists its allowed languages (or, with
an any language profile, search.preferredLanguage is "en", mirroring the
grab time filter) and the downloaded EPUB declares a specific language
outside that list, the release is now handled like a disallowed format:
import path recorded for Match to book, release blocklisted, download
blocked with a message naming both languages, book left Wanted, nothing
placed or relabelled. A user locked book language counts as allowed for
that book.

Unchanged: undeclared, und, mul and unmapped codes; any language
profiles (relabelling stays); manual import and Match to book (warn
only); library scans and adoption; books already imported.

Also folds nb/nob/nn/nno onto nor in NormalizeLanguageCode so Bokmal
and Nynorsk files pass a Norwegian profile, and adds LanguageName for
readable messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9
Signed-off-by: vavallee <vavallee@protonmail.com>

* fix(importer): count every declared language and every ISO 639-1 code (#2998)

Review of #3000 found two holes in the import language check.

Only the first dc:language of the first EPUB counted, so an EPUB tagged
fr then en was blocked under an English profile while the same file
tagged en then fr imported, and a release with A.epub (sv) and B.epub
(en) was rejected while the reversed names imported. EpubMetadata now
also carries Languages (every dc:language, normalised, deduped; Language
is unchanged for existing callers). The gate classifies each candidate
EPUB as allowed when any declared language is allowed, and rejects the
release only when some EPUB is disallowed and none is allowed, the
shape of the format gate. A mixed release imports the allowed EPUBs and
skips the rest in the ebook loop, as the format check skips a format.
Relabelling under a restricted profile picks the first allowed declared
language, so an "und, en-GB" EPUB no longer relabels the book "und".

The two letter table held 25 codes, so uk, he, sk, fa, is and the rest
normalised to nothing known and passed as "no language". It now covers
all 183 ISO 639-1 codes, with English names for every code it yields,
and cmn/yue fold onto chi.

The Norwegian fold is renamed iso639NorwegianToB and matches
francisrath's #2995 hunk line for line so his rebase drops it cleanly.

Docs note that External import mode is not checked.

Co-authored-by: francisrath <fr@ncis.no>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9
Signed-off-by: vavallee <vavallee@protonmail.com>

---------

Signed-off-by: vavallee <vavallee@protonmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: francisrath <fr@ncis.no>
francisrath and others added 18 commits October 5, 2026 08:17
Read-only client for the National Library of Norway's catalogue search
API (api.nb.no/catalog/v1/items). NB holds Norwegian publications by
legal deposit under their original titles, where OpenLibrary often
catalogues them under the English translation.

NB records are editions, not works, so author catalogues are folded into
works by title, with translations joined to the original through the
original or uniform title the cataloguer recorded. Authors are keyed by
their Norwegian authority file ID; NB search cannot look an author up by
that ID, so the name is resolved through authority.bibsys.no.

No cover or series-catalogue capability: NB's image service refuses
in-copyright items, and series positions are only in the per-record MODS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
"nb" is accepted for metadata.primary_provider and nb: foreign IDs route
to it. Unlike the other providers it is not registered as an enricher
when another provider is primary, so installs that did not choose it send
the National Library no traffic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
Adds the provider to the Metadata Profiles selector with help text, names
nb: records in the provider labels, and links NB book records to their
nb.no page. Docs and changelog fragment updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
The aggregator resolves an ISBN to the primary's work and refreshes it
through GetBook, which returned only the one edition record, so a book
added by ISBN lost its other editions (an audiobook's ISBN among them).
GetBook now finds the record's siblings by authority-file author and
title, falling back to the record alone if that search fails.

Book search also accepts the "isbn:<n>" form the canonical lookup sends,
and includes audiobooks like the author catalogue does, so both paths
return the same editions for a work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
NB's search records name a series but not the number; the number is only
in each record's MODS. Works whose records name a series get one MODS
request (bounded, at most three editions per work, four in flight).

Only the author's own series is taken: the MODS series entry linked to
their authority record. Publisher imprint series are recorded as series
too but never carry that link. An entry recorded without the link is
accepted only when the same series is linked elsewhere in the catalogue.
Series are filled in the author catalogue and in GetBook, because a
rebind replaces a book's series with what GetBook returns.

Language qualifiers on catalogue titles are now matched against a list of
language names instead of a suffix pattern, which also matched names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
NB catalogues some volumes under their series' name: the title proper is
the series, the volume's own title is the part name, and the number is
the part number, sometimes only on the uniform title. Taking the title
proper turned each such volume into a book named after its series, with
no number, since its search record names no series to look up.

The part name is now the record's title, and the part number is a series
candidate, taken on the same condition as an unlinked series entry: the
author's catalogue links that series elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
NB leaves some records out of every name index although their author
credit is correct, so no name search returns them and a series came back
with volumes missing. A search on each series the author's catalogue
links them to finds them; only records crediting the author's authority
ID are kept. One extra request per series, and MODS lookups are cached
for the call so regrouping fetches nothing twice. A failed recall search
marks the catalogue partial, so reconciliation never reads a volume
found this way as removed upstream.

The single-book sibling search drops the name-index filter for the same
reason; it already keeps only the author's records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
A record whose title a cataloguing slip had split with a space came back
as a second book beside the same work. Work keys now ignore spaces
(within one author, so two of their titles differing only in spacing are
taken as one work), and a work takes the title most of its editions
carry, so one record's slip cannot name the book.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
HTTP 429 now wraps providererr.ErrRateLimited and 5xx wraps
providererr.ErrUnavailable, so scheduled discovery backs off instead of
walking on through its queue. NB publishes no limits, so a refusal is
taken at its word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
Reconciliation looked up each work's editions with a per-work call, which
for NB is a full GetBook plus a sibling search. NB works come from the
author catalogue with every edition already attached, so those are the
evidence now and the call is skipped, as it already is for DNB. Unlike
DNB, the works are still judged: a work with no editions attached stays
indeterminate rather than counting as having no ISBN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…tial

When the authority record exists but the name search returns nothing, the
author exists and NB's name index simply misses their records, so the
catalogue is reported incomplete instead of complete and empty, which
reconciliation could otherwise read as every book removed upstream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…primary means

Adds a Nasjonalbiblioteket section to third-party-data.md: CC0 records via
NB's metadata delivery, publisher-written summaries, no covers, and the
Sikt authority file under NLOD 2.0 with its attribution line. The user
guide and architecture notes now say that switching the primary away from
NB leaves NB-linked authors unsynced until they are relinked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…logue

A 404 or deleted authority record returned an empty catalogue marked
complete. Sikt merges duplicate authority records now and then, and
reconciliation would then offer to remove every wanted book of that
author. It is now reported as partial, like an empty name search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…alogue

On a partial catalogue (over the page cap, or a failed series recall) a
work can be missing editions, so a lone short edition made it a page
count candidate. Its own editions are now the evidence only when the
snapshot is complete; otherwise the work stays indeterminate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…now ships

The nb/nob/nn/nno fold landed on main with vavallee#3000, which carries its own
changelog entry for it, so the release notes would list it twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
@francisrath
francisrath force-pushed the feat/metadata-nasjonalbiblioteket branch from cd8d1e4 to 02cd223 Compare October 5, 2026 06:23
All of it comes from requests NB already serves: the catalogue records a
work is built from, and the authority record GetAuthor reads.

- Narrator: the narrator credits on the work's audiobook editions.
- Running time: parsed from the audiobook record's extent ("1 lydfil
  (11 t, 16 min)", "21:34:00", a CD set's "(3 t, 7 min)"), on the
  audiobook edition and the book. Records that state none get none.
- Genres: the cataloguer's subject genres, without format terms and
  without the Nynorsk twin of a Bokmål term NB lists beside it.
- Name variants: the authority record's other name forms become the
  author's AlternateNames, which the add-author flow keeps as aliases
  when they are another spelling of the same name (LatinAliasBinds), so
  a release name written without Norwegian letters still matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
@francisrath

Copy link
Copy Markdown
Contributor Author

@vavallee one more addition, sorry for growing the PR at this stage. While checking the provider against real catalogues we found more useful data in the responses it already fetches, so this needs no extra requests to NB or Sikt:

  • Narrator on audiobooks, from the narrator credit on the audiobook editions.
  • Running time on audiobooks, parsed from the record's extent (1 lydfil (11 t, 16 min), 21:34:00, or a CD set's (3 t, 7 min)). Records that state none get none.
  • Genres, from the cataloguer's subject genres, minus format terms (Lydbøker) and the Nynorsk twin NB lists beside each Bokmål term.
  • Author name variants from the authority record, as AlternateNames. The add-author flow keeps only the ones LatinAliasBinds accepts as another spelling of the same name; for one author that was exactly the form without ø, out of 19 variants, so release names without Norwegian letters still match.

All of it stays inside internal/metadata/nb (one commit, a4ddca73), with tests on invented fixtures. make test, lint, govulncheck and the race detector pass locally. If you'd rather review it as a follow-up PR, say so and I'll move it out so this one can merge as it was.

Not-found lookups answer nothing rather than an error, unusable input
makes no request, a transport failure is an error and never an empty
catalogue, and malformed authority links, single names, unusable years
and sparse records are handled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
francisrath and others added 2 commits October 5, 2026 14:17
…r credits

NB leaves many records that credit an author out of its nameauthor
index, and catalogues authorship as the generic "cre" about as often as
"aut". For some authors the provider saw a small fraction of their
catalogue. Fetch from namecreators, accept "cre" and "creator" as author
credits, and raise the page cap to 20, since the creator index counts
every translation. The authority-ID check still drops translator and
narrator credits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…mes out

NB catalogues one series under more than one name ("<A> og <B>" and
"<full name A> og <full name B>"), linking both to the author, so a
series came out split in two. Names that one work's records link with
the same number are now one series, under the name most books carry.

Since works include their translations, the series probe could land on
a translation's record and name the series in that language. Series now
come from the book's Norwegian records first, then Danish and Swedish
ones, which name series alike. A record that numbers the volume is
preferred over one that does not.

Some series are linked on a translation's record alone, with no
Norwegian record naming them. Those are used as a last resort, after
every Norwegian source. This sometimes has strange effects: a Norwegian
book can end up in a series with a Finnish, English or Dutch name, and
one series can appear under two names when neither record numbers the
volume. The books are still grouped and mostly numbered correctly, which
is better than no series at all. On 28 Norwegian authors, the fallback
recovered 17 books into a correctly named series and gave 9 a series
with a foreign name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
@francisrath

Copy link
Copy Markdown
Contributor Author

Two more fixes from testing against a wider set of Norwegian authors:

0d280415: works from the creator index. NB leaves many records that credit an author out of its nameauthor index, and catalogues authorship as the generic cre about as often as aut. For some authors the provider saw only a small fraction of their catalogue. Works now come from namecreators, and cre/creator count as author credits. The authority-ID check still drops translator and narrator credits. The page cap goes from 5 to 20, because the creator index counts every translation.

1321c9b5: series.

  • Merging two names: NB sometimes catalogues one series under two names, both linked to the author, which split it in two. Names that one work's records link with the same number are now merged under the name most books carry.
  • Language order: since works now include their translations, the series probe could land on a translation's record and name the series in that language. Series now come from Norwegian records first, then Danish and Swedish ones.
  • Numbers: a numbered entry is preferred over an unnumbered one.
  • Fallback: some series are linked only on a translation's record, so those are used as a last resort. That sometimes gives a Norwegian book a series with a foreign name, but the books are still grouped and numbered, which beats no series. The commit message spells this out.

Validated against 28 Norwegian authors of crime, literary and children's fiction (not committed; it calls the live API):

Before After
Errors / partial catalogues 0 / 0 0 / 0
Books with a series 286 310
Books that lost a series compared with a strict own-language-only variant n/a 0
Books given a foreign-named series by the fallback n/a 9

New tests cover the merge, the probe order and the fallback ordering.

@francisrath

Copy link
Copy Markdown
Contributor Author

Re-requesting review: the earlier requested changes are addressed in the commits since (summaries in the comments above). The non-API race shard on the last run was cancelled at the 30-minute job limit, not by a test failure; could you re-run it? I can't trigger it from the fork.

@vavallee vavallee left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @francisrath, all three asks are in and I checked each one by reverting the fix: both new tests go red without it, so they're doing their job. The language hunk is gone, and it merges cleanly with current main, including the settings and tenancy changes that landed since. Build, vet, the Go tests and the frontend tests are all green on the merged tree.

I also poked at the cre change against the live API, since that's where I'd expect editors or translators to slip in. They don't. Editors come through as edt, red or utg and translators as trl, so cre really is the author. Nice catch on the index gap.

One small docs thing before merge: the CC0 source you linked is Biblioteksentralen's page for the metadata delivery over OAI-PMH and SRU, but Bindery reads the catalog search API, which doesn't publish any terms. Could you narrow that bullet so it says the delivery records are CC0 and the search API states no licence of its own? Same for the "(CC0 metadata)" note at the top of client.go. A line saying the NLOD attribution matters when someone redistributes the data, not for a self hosted install, would help too.

On the race shard: your #2983 is on main now, so merging main into the branch picks up the faster test DB and that shard should finish well inside the limit.

Not blocking: a heavily translated author can now cost a few hundred requests per refresh, so I'll keep an eye on that once it's out. Great work on this, happy to merge it for v1.41.0 once the docs tweak is in.

francisrath and others added 2 commits October 6, 2026 23:32
…ivery's

The CC0 statement we cited covers NB's metadata delivery over OAI-PMH
and SRU, which Bindery does not use. The catalogue search API it reads
publishes no licence of its own. Say so in the third-party data page and
the package comment, and note that the NLOD attribution for the
authority file applies when the data is redistributed, not to a
self-hosted install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Francis Rath <fr@ncis.no>
…nalbiblioteket

Signed-off-by: Francis Rath <fr@ncis.no>
@francisrath

Copy link
Copy Markdown
Contributor Author

Thanks! Both done:

  • Docs (3c3e4b18):
    • docs/third-party-data.md now says the CC0 statement covers NB's metadata delivery over OAI-PMH and SRU, and that the search API Bindery reads states no licence of its own.
    • The client.go package comment says the same.
    • The NLOD attribution line is scoped to redistribution, with a note that a self-hosted install showing the names to its own users doesn't redistribute them.
  • Main merged in (89c66616), which picks up test(db): migrate OpenMemory's schema once per process and copy it #2983's test DB for the race shard.

I also updated the PR description where it had gone stale: works now come from the creator index, the page cap is 20, and the licence wording matches.

On request volume, agreed. Most of it on a heavily translated author is the creator-index pages plus the per-work MODS probes for series. If it shows up in practice, the probes are the first thing I'd trim.

@vavallee
vavallee merged commit c03fdb8 into vavallee:main Oct 7, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bindery-notified Discord notification already sent for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nasjonalbiblioteket (National Library of Norway) as an opt-in primary metadata provider

2 participants