Documentation
¶
Overview ¶
Package vocabsrc manages public authority sources (tasks/067): a registry of live-suggest and downloadable vocabulary sources seeded with built-ins (id.loc.gov datasets, Wikidata, VIAF), snapshot download jobs that convert public SKOS RDF dumps into authority-tree N-Quads the vocab index loads atomically, and the live typeahead proxy the picker and enrichment reconcile through. Custom sources (GND, Getty, MeSH, Homosaurus, ...) are drop-in registry entries, not code -- see docs/authority-sources.md.
Index ¶
- Constants
- Variables
- func Convert(r io.Reader, scheme string) ([]byte, int, error)
- func ConvertTo(w io.Writer, r io.Reader, scheme string, maxBytes int64) (int, error)
- type CrosswalkEnricher
- type Enricher
- type InstallInfo
- type Job
- type Service
- func (s *Service) CacheTerm(ctx context.Context, sugg Suggestion) error
- func (s *Service) CreateDownload(ctx context.Context, requester, sourceName string) (Job, error)
- func (s *Service) DeleteSource(ctx context.Context, name string) error
- func (s *Service) GetJob(ctx context.Context, id string) (Job, error)
- func (s *Service) GetSource(ctx context.Context, name string) (Source, error)
- func (s *Service) InstallUpload(ctx context.Context, sourceName string, r io.Reader) (int, error)
- func (s *Service) Installed(ctx context.Context) ([]InstallInfo, error)
- func (s *Service) Jobs(ctx context.Context) ([]Job, error)
- func (s *Service) PutSource(ctx context.Context, src Source) error
- func (s *Service) Reload(ctx context.Context) error
- func (s *Service) RemoveSnapshot(ctx context.Context, name string) error
- func (s *Service) RunDownload(ctx context.Context, id string) error
- func (s *Service) RunQueued(ctx context.Context) (int, error)
- func (s *Service) Schemes(ctx context.Context) ([]string, error)
- func (s *Service) Sources(ctx context.Context) ([]Source, error)
- func (s *Service) Views(ctx context.Context) ([]SourceView, error)
- type Source
- type SourceView
- type Status
- type SuggestClient
- type Suggestion
Constants ¶
const ( // FlavorSuggest2 is the id.loc.gov suggest2 API (any of its datasets -- // and anything else speaking the same shape). FlavorSuggest2 = "suggest2" // FlavorWikidata is the Wikidata wbsearchentities action API. FlavorWikidata = "wikidata" // FlavorVIAF is the VIAF AutoSuggest API. FlavorVIAF = "viaf" // FlavorSearchFAST is OCLC's searchFAST fastsuggest API (tasks/132) -- a // Solr-shaped response; the full documented parameter set is required // (the bare query/fl form 400s). FlavorSearchFAST = "searchfast" )
Suggest response dialects.
Variables ¶
var ErrConflict = errors.New("vocabsrc: conflict")
ErrConflict reports an operation refused by the state it would leave behind -- deleting a source whose snapshot is still installed (tasks/255). The caller can make it succeed by doing something first, which is what separates it from ErrValidation.
var ErrNotFound = errors.New("vocabsrc: not found")
ErrNotFound reports a missing source, job, or installed snapshot.
var ErrValidation = errors.New("vocabsrc: invalid source")
ErrValidation reports a source description the registry refuses.
Functions ¶
func Convert ¶
Convert buffers ConvertTo -- for callers that want the converted bytes in hand; the install paths stream through ConvertTo into the blob store.
func ConvertTo ¶
ConvertTo streams a SKOS N-Triples/N-Quads dump (gzipped or plain) into authority-tree N-Quads under the authority:<scheme> graph, keeping only the predicates the index reads. It decodes one statement at a time, so peak memory is a statement plus the concept-count set, not the dump (tasks/110). Common wrong-format uploads (zip archives, XML exports) are named outright -- publishers like OCLC FAST distribute both. maxBytes caps the decompressed input (0 = the 4GB default). Returns the distinct prefLabel-bearing concept count.
A malformed line refuses the whole dump, naming the line (tasks/317). It used to be skipped, and that was not a decision -- it was whatever libcodex's parser did. The dumps that trip it are the ones you most want refused: a 5,242,880-byte homosaurus-v4.nt, cut mid-IRI at exactly 5MiB, converts cleanly under a lenient parser and installs a vocabulary silently missing every concept after the cut. The subject pages it labels are then wrong, and nothing anywhere says so. Five real LC/Homosaurus/FAST dumps were parsed strictly to check this; the only one that failed was that truncated download.
The line number in that refusal is the dump's own (tasks/320). This used to feed 1MB chunks to rdf.ParseNQuads and add a running base to each SyntaxError.Line, because a bulk parser numbers from the start of the bytes it was handed; the streaming decoder numbers from the start of the stream, so the base is gone and cannot drift from the parser again.
Types ¶
type CrosswalkEnricher ¶
type CrosswalkEnricher struct {
Index *vocab.Index
// Target is the scheme equivalents resolve into.
Target string
}
CrosswalkEnricher walks every work subject's skos:exactMatch/closeMatch links into one target scheme (tasks/072): a work carrying a homosaurus heading whose term matches an LCSH heading gains the LCSH equivalent as a moderated suggestion. Purely local -- both vocabularies must be loaded in the index; no network.
func NewCrosswalk ¶
func NewCrosswalk(ix *vocab.Index, target string) *CrosswalkEnricher
NewCrosswalk wraps the index as a crosswalk enrichment source for one target scheme.
func (*CrosswalkEnricher) Enrich ¶
func (e *CrosswalkEnricher) Enrich(_ context.Context, works []ingest.WorkSummary) ([]ingest.Enrichment, error)
Enrich implements ingest.Enricher: for each work, every controlled subject's match links resolving in the target scheme (and not already on the work) become subject candidates, and each candidate's skos:broader ancestor chain rides along as standalone term metadata (tasks/178) so hierarchy nodes stay labeled without a work carrying them.
func (*CrosswalkEnricher) Name ¶
func (e *CrosswalkEnricher) Name() string
Name implements ingest.Enricher; the registry key and enrichment graph.
type Enricher ¶
type Enricher struct {
Src Source
Client *SuggestClient
// Index, when set, upgrades each match with the locally-installed term
// description (multilingual labels, broader edges) and rides its
// skos:broader ancestor chain along as Enrichment.Terms (tasks/178);
// a match whose scheme is not installed keeps the suggest-API label.
Index *vocab.Index
// MinConfidence drops weaker matches. Default 0.9 (exact label only).
MinConfidence float64
}
Enricher adapts a suggest-capable source to the ingest.Enricher contract (the generalized locsh shape): a Work's uncontrolled tags reconcile against the source's typeahead API, exact normalized-label matches score 1.0 and prefix matches 0.6, and candidates at or above MinConfidence become controlled-subject enrichments.
func NewEnricher ¶
func NewEnricher(src Source, client *SuggestClient) *Enricher
NewEnricher wraps a registry source as an enrichment provider.
func (*Enricher) Enrich ¶
func (e *Enricher) Enrich(ctx context.Context, works []ingest.WorkSummary) ([]ingest.Enrichment, error)
Enrich implements ingest.Enricher: each distinct tag is looked up once per run, and matches at or above MinConfidence become subject candidates on every Work carrying the tag.
type InstallInfo ¶
type InstallInfo struct {
Source string `json:"source"`
Scheme string `json:"scheme"`
Terms int `json:"terms"`
InstalledAt time.Time `json:"installedAt"`
SnapshotURL string `json:"snapshotUrl"`
}
InstallInfo is an installed snapshot's sidecar metadata, stored beside the .nq in the blob store so install state survives restarts.
type Job ¶
type Job struct {
ID string `json:"id"`
Source string `json:"source"`
Scheme string `json:"scheme"`
Requester string `json:"requester"`
Status Status `json:"status"`
Terms int `json:"terms,omitempty"`
Error string `json:"error,omitempty"`
CreatedAt time.Time `json:"createdAt"`
FinishedAt time.Time `json:"finishedAt,omitzero"`
}
Job is one vocabulary download: fetch the source's SKOS dump, convert it to authority-tree N-Quads, install, and swap the index.
type Service ¶
type Service struct {
DB store.Store
Blob blob.Store
// Index is the shared term index snapshots swap into.
Index *vocab.Index
// AuthoritiesPrefix roots the authority tree the index loads; installed
// snapshots land under it at vocab/<name>.nq. Empty = "data/authorities/".
AuthoritiesPrefix string
// BaseSchemes is the deployment's configured scheme filter (nil = all
// authority graphs load, so installs never need scheme bookkeeping).
BaseSchemes []string
// Suggest overrides the live-typeahead client (tests). nil = defaults.
Suggest *SuggestClient
// HTTPClient fetches snapshot dumps. nil = a 15-minute-timeout client.
HTTPClient *http.Client
// MaxSnapshotMB caps a snapshot dump's decompressed size (0 = the 4GB
// default) -- the tasks/110 defensive ceiling against a hostile or
// misconfigured endpoint.
MaxSnapshotMB int
Logger *slog.Logger
// Now overrides the clock (tests).
Now func() time.Time
}
Service is the authority-source surface: registry CRUD, live suggest, and snapshot download/install/remove over the shared vocab index.
func (*Service) CacheTerm ¶
func (s *Service) CacheTerm(ctx context.Context, sugg Suggestion) error
CacheTerm writes a live pick's minimal term description (prefLabel, definition, exactMatch siblings) into the authorities tree under cache/<scheme>/ and swaps the index (tasks/072). A subject picked from a live source labels forever -- across saves and restarts -- and its exactMatch links join the crosswalk data. Re-caching an already-cached term is a no-op.
func (*Service) CreateDownload ¶
CreateDownload queues a download job for a snapshot-capable source. The worker loop (RunQueued) picks it up; installing the same source again is the refresh path -- the snapshot is overwritten in place.
func (*Service) DeleteSource ¶
DeleteSource removes a stored registry entry. A built-in cannot be deleted (deleting a stored override restores the shipped definition).
It refuses with ErrConflict while a snapshot is installed, which is what the screen's tooltip has always promised (tasks/255): deleting the row out from under an install leaves an orphan whose Upload and Delete actions can only 404, and the admin was told the server would stop them. RemoveSnapshot first, then delete.
Deleting a stored *override* of a built-in is exempt: the shipped definition takes its place, so the install keeps a source and is never orphaned.
The refusal is not a statement about the vocabulary. An install still outlives its source row by other routes -- an offline lcat vocab-install (tasks/163), or a registry that reset because the deployment has no document store -- so Views still synthesizes the orphan, and RemoveSnapshot is still the way out of it.
func (*Service) InstallUpload ¶
InstallUpload installs a hand-supplied dump for a registered source -- the escape hatch when the publisher's download URL is unreachable (or the source has none). Same converter, snapshot layout, and index swap as a download; the sidecar records "upload" as the provenance. Synchronous: the caller holds the bytes, no worker round-trip.
func (*Service) Installed ¶
func (s *Service) Installed(ctx context.Context) ([]InstallInfo, error)
Installed lists the installed snapshots by reading the sidecars under the vocab/ subtree.
func (*Service) PutSource ¶
PutSource creates or replaces a registry entry -- the drop-in-config path for sources beyond the built-ins (and for overriding a built-in's URLs).
func (*Service) Reload ¶
Reload rebuilds the shared index from the authorities tree with the effective schemes -- an atomic snapshot swap under concurrent readers.
func (*Service) RemoveSnapshot ¶
RemoveSnapshot deletes an installed snapshot, its install meta, and the sidecar index artifacts built from it, then reloads the index so the scheme's terms drop out.
The scheme comes from the install meta rather than the source registry, because removing a snapshot whose source row is already gone -- the orphan install Views synthesizes so it stays removable -- is exactly the case that leaves artifacts behind (tasks/252). The sidecar goes first: its manifest is what arms the scheme, so an interrupted removal degrades the scheme to the map loader instead of arming it on an index whose snapshot has been deleted.
func (*Service) RunDownload ¶
RunDownload executes a QUEUED job (claiming it RUNNING first, so concurrent workers cannot double-run): fetch, convert, install, reload. Failures land in the job's Error; RunDownload errors only on store problems.
func (*Service) Schemes ¶
Schemes computes the index's effective scheme filter: the configured base filter plus every installed snapshot's and cached live pick's scheme, or nil (= everything) when no base filter is configured.
type Source ¶
type Source struct {
Name string `json:"name"`
Scheme string `json:"scheme"` // vocab scheme key its terms live under
// License and Homepage surface in the download list so a deployment
// sees what it is installing.
License string `json:"license,omitempty"`
Homepage string `json:"homepage,omitempty"`
// Live typeahead capability. SuggestFlavor selects the response dialect
// (suggest2 | wikidata | viaf); SuggestDataset is the suggest2 dataset
// path (e.g. "authorities/subjects").
SuggestFlavor string `json:"suggestFlavor,omitempty"`
SuggestURL string `json:"suggestUrl,omitempty"`
SuggestDataset string `json:"suggestDataset,omitempty"`
// SnapshotURL points at a downloadable SKOS RDF dump (N-Triples or
// N-Quads, optionally gzipped) convertible into the vocab index.
SnapshotURL string `json:"snapshotUrl,omitempty"`
// Builtin marks a shipped source (not deletable; a stored source of the
// same name overrides it).
Builtin bool `json:"builtin,omitempty"`
}
Source is one public authority source. A source may offer live typeahead (SuggestURL), a downloadable SKOS dump (SnapshotURL), or both.
func Builtins ¶
func Builtins() []Source
Builtins returns the shipped sources: the id.loc.gov datasets (subjects, genre/form, children's subjects downloadable; the name authority file is live-only -- its dump is ~11M concepts), OCLC FAST (live-only, tasks/132), Wikidata, and VIAF.
func (Source) CanSnapshot ¶
CanSnapshot reports whether the source offers a downloadable dump.
func (Source) CanSuggest ¶
CanSuggest reports whether the source offers live typeahead.
type SourceView ¶
type SourceView struct {
Source
Installed *InstallInfo `json:"installed,omitempty"`
Job *Job `json:"job,omitempty"`
// Orphan marks a row synthesized from an install with no source record behind
// it. Such a row can only be removed: everything else the screen offers needs a
// source to act on, and answers 404 without one (tasks/255). An empty
// SnapshotURL is not a proxy for this -- an upload-only source has none either.
Orphan bool `json:"orphan,omitempty"`
}
SourceView is the list surface: a source plus its install state and its most recent download job.
type SuggestClient ¶
type SuggestClient struct {
// Client overrides the HTTP client (tests). Default: 10s timeout.
Client *http.Client
}
SuggestClient queries a source's live typeahead API.
func (*SuggestClient) Suggest ¶
func (c *SuggestClient) Suggest(ctx context.Context, src Source, q string, limit int) ([]Suggestion, error)
Suggest queries one source for up to limit typeahead hits.
type Suggestion ¶
type Suggestion struct {
Source string `json:"source"`
Scheme string `json:"scheme"`
ID string `json:"id"`
Label string `json:"label"`
Description string `json:"description,omitempty"`
// Variants are the heading's variant/"used for" labels when the source
// exposes them (suggest2's more.variantLabels) -- often the only context
// a bare authorized heading carries.
Variants []string `json:"variants,omitempty"`
ExactMatch []string `json:"exactMatch,omitempty"`
}
Suggestion is one live typeahead hit, source-tagged for the picker badge. ExactMatch carries sibling identifiers when the source exposes them (VIAF clusters map to LCNAF/GND/Wikidata) so a term created from the pick can record skos:exactMatch cross-references.