Repository navigation
feat(metadata): Nasjonalbiblioteket as an opt-in primary provider (#2979) - #2995
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
vavallee
left a comment
There was a problem hiding this comment.
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:
- 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.Completeis true and leave it indeterminate otherwise? Adding that case to your test would be great. - 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. - 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!
|
Thanks @vavallee, all three are in, plus the nit:
Nit: the
|
…#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>
…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>
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>
cd8d1e4 to
02cd223
Compare
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>
|
@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:
All of it stays inside |
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>
…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>
|
Two more fixes from testing against a wider set of Norwegian authors:
Validated against 28 Norwegian authors of crime, literary and children's fiction (not committed; it calls the live API):
New tests cover the merge, the probe order and the fallback ordering. |
|
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
left a comment
There was a problem hiding this comment.
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.
…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>
|
Thanks! Both done:
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. |
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.
internal/metadata/nbpackage implementingmetadata.Provider, plusGetAuthorWorks/GetAuthorWorksSnapshot.nbaccepted formetadata.primary_provider;nb:foreign IDs route to it.nb:books.Review follow-ups (from #2979)
changelog.d/2979-…now uses "Title (#2979). Text."getwrapsprovidererr.ErrRateLimitedfor 429 andprovidererr.ErrUnavailablefor 5xx; a 4xx stays a plain error.GetEditionsfornbdocs/third-party-data.mdsectionThe 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:
nrt) on audiobook editionsBook.Narrator1 lydfil (11 t, 16 min),21:34:00,(3 t, 7 min))Edition/Book.DurationSecondsBook.GenresAuthor.AlternateNames, kept as aliases only whenLatinAliasBindsaccepts themWhat the NB API supports (investigated, not assumed)
GET https://api.nb.no/catalog/v1/items; OpenAPI at/catalog/v1/items/api-docs. The deprecatedservices/search/v2is unused.q=isbn:<n>(any media type, so audiobook ISBNs resolve); author search via theapi_nameauthorindex; an author's works viafilter=namecreators:"Last, First"(the author index misses many records, andcrecredits count as authorship); title via freeqwithsearchType=FIELD_RESTRICTED_SEARCH(the default also searches OCR'd full text).expand=metadata:people[]with MARC relator roles and a Norwegian authority-file ID.authority.bibsys.no), and catalogue records are kept only when they credit that ID as author, which drops namesakes and translator/narrator credits.sesamIdon every record (nb:<sesamId>); URNs exist only for digitised items. Authors:nb:author:<authority id>.<series> : <volume>) takes its own title from the part name.CoverProvider.Behaviour notes
ErrProviderNotConfigured: NB needs no credentials, so a client fromNew()is always usable; the sentinel is returned for a zero-value client only.How it was verified
Checklist
Signed-off-bythat matches its author (git commit -s)changelog.d/2979-metadata-nasjonalbiblioteket.mddocs/DEPLOYMENT.mdupdated if env vars, config, or upgrade path changed (no change needed: no new env var or config)docs/updated (User Guide, architecture, third-party data)Test plan
make checksteps: gofmt, Go version check, build, vet, golangci-lint v2.11.4, govulncheck,make test, web typecheck/lint/build/test, licence driftinternal/metadata/...,internal/models,cmd/..., reconciliation tests ininternal/api); the fullinternal/apiandinternal/dbrace shards run in CI🤖 Generated with Claude Code