Documentation
¶
Overview ¶
Package copycat is Koha's Z39.50/SRU copy cataloging and staged-import workflow over the shared ingest pipeline: external targets are searched through the libcodex protocol clients, results and .mrc uploads stage into datastore batches (nothing touches the grain tree), every staged record carries its identity-resolver match ("would merge with Work w…"), and commit runs the batch through the same clustering pipeline every feed uses -- store-backed, CAS-guarded, editorial always preserved.
Original cataloging: blank-record MARC skeletons and the validation gate for records that enter the catalog from the editor rather than an external target. A draft stages into a normal batch with source "original", so review, identity matching, commit, and revert are the same machinery every import uses.
Index ¶
- Constants
- Variables
- func Incomplete(err error) bool
- type Batch
- type FieldError
- type FieldTerm
- type Match
- type PartialError
- type Profile
- type RevertResult
- type RevertSkip
- type SearchFunc
- type SearchResult
- type Service
- func (s *Service) Batches(ctx context.Context) ([]Batch, error)
- func (s *Service) Commit(ctx context.Context, id, actor string) (Batch, error)
- func (s *Service) DeleteBatch(ctx context.Context, id string) error
- func (s *Service) DeleteProfile(ctx context.Context, name string) error
- func (s *Service) DeleteTarget(ctx context.Context, name string) error
- func (s *Service) GetBatch(ctx context.Context, id string) (Batch, []StagedRecord, error)
- func (s *Service) Profiles(ctx context.Context) ([]Profile, error)
- func (s *Service) PutProfile(ctx context.Context, p Profile) error
- func (s *Service) PutTarget(ctx context.Context, t Target) error
- func (s *Service) Revert(ctx context.Context, id, actor string) (RevertResult, error)
- func (s *Service) Review(ctx context.Context, id, policy string, decisions map[int]string) (Batch, error)
- func (s *Service) SearchAll(ctx context.Context, query string, fields []FieldTerm, names []string) ([]SearchResult, map[string]string, map[string]string, error)
- func (s *Service) SeedDefaultTargets(ctx context.Context) error
- func (s *Service) Stage(ctx context.Context, label, source string, docs []marcview.RecordDoc, ...) (Batch, []StagedRecord, error)
- func (s *Service) StageMARC(ctx context.Context, label string, mrc []byte, owner string) (Batch, []StagedRecord, error)
- func (s *Service) StageOriginal(ctx context.Context, label string, doc marcview.RecordDoc, owner string) (Batch, []StagedRecord, []FieldError, error)
- func (s *Service) Targets(ctx context.Context) ([]Target, error)
- type StagedRecord
- type Target
- type Template
Constants ¶
const ( ProtocolSRU = "sru" ProtocolZ3950 = "z3950" )
Protocols a target can speak.
const ( // PolicyReplaceFeed commits everything; a matched Instance's feed // statements are replaced by the incoming record's (the pipeline's // re-ingest semantics). PolicyReplaceFeed = "replace-feed" // PolicyFillHoles commits only records whose Instance is new -- a // matched Work gains the new edition, but an existing Instance is never // overwritten. PolicyFillHoles = "fill-holes-only" // PolicyNever commits only records with no match at all. PolicyNever = "never" )
Overlay policies: what a commit does with records that match the existing corpus. Editorial statements are preserved by the pipeline in every case.
const ( DecisionImport = "import" DecisionSkip = "skip" )
Decisions a reviewer takes per staged record.
const ( StatusStaged = "STAGED" StatusCommitted = "COMMITTED" )
Batch statuses.
const StatusReverted = "REVERTED"
StatusReverted marks a committed batch whose grains were rolled back.
Variables ¶
var DefaultTargets = []Target{ {Name: "dnb-sru", URL: "https://services.dnb.de/sru/dnb", Protocol: ProtocolSRU, Version: "1.1", Schema: "MARC21-xml", Indexes: map[string]string{"isbn": "dnb.num", "issn": "dnb.num"}}, {Name: "k10plus-sru", URL: "https://sru.k10plus.de/opac-de-627", Protocol: ProtocolSRU, Indexes: map[string]string{"isbn": "pica.isb", "issn": "pica.iss"}}, {Name: "loc-sru", URL: "http://lx2.loc.gov:210/LCDB", Protocol: ProtocolSRU}, }
DefaultTargets are the search sources seeded on a store that has never had targets: open, anonymous SRU endpoints serving MARC21, each verified live against the exact queries the fielded search emits. LOC speaks the Bath-profile identifier indexes as-is; DNB only answers SRU 1.1 and names its schema MARC21-xml, with dnb.num covering both standard numbers; K10plus wants its PICA identifier indexes.
var ErrCapped = errors.New("result set truncated at the search limit")
ErrCapped reports that the search limit, not the result set, ended the stream: the target may hold more records than were returned.
var ErrConflict = errors.New("copycat: conflicts with batch state")
ErrConflict reports a request that conflicts with the batch's current state -- e.g. deleting a COMMITTED batch whose revert-set is still the only undo.
var ErrNotFound = errors.New("copycat: not found")
ErrNotFound reports a missing target or batch.
var ErrValidation = errors.New("copycat: invalid request")
ErrValidation reports a request the service refuses.
var SuggestedTargets = []Target{ {Name: "loc", URL: "lx2.loc.gov:210/LCDB", Protocol: ProtocolZ3950}, {Name: "loc-sru", URL: "http://lx2.loc.gov:210/LCDB", Protocol: ProtocolSRU}, {Name: "k10plus", URL: "https://sru.k10plus.de/opac-de-627", Protocol: ProtocolSRU, Indexes: map[string]string{"isbn": "pica.isb", "issn": "pica.iss"}}, {Name: "indexdata-test", URL: "z3950.indexdata.com:210/marc", Protocol: ProtocolZ3950}, }
SuggestedTargets are the open, no-credential sources the copycat UI offers as one-click presets. It is the single source for the preset row: the UI fetches it (GET /v1/copycat/targets/suggested) rather than maintaining its own copy, which is how the k10plus preset came to lack the PICA indexes its seeded twin carries. A preset sharing a URL with a DefaultTargets entry must carry the same SRU knobs, or the one-click target speaks different CQL than the seeded one -- TestSuggestedTargetsAgreeWithDefaults pins that. Blurbs are the UI's to add; the wire config lives here.
Functions ¶
func Incomplete ¶ added in v0.109.0
Incomplete reports whether err means "these records, but not all of them" -- a partial stream or the search cap -- as opposed to an outright failure. A search returning records with an Incomplete error is a warning, never a failure: suppressing the hits would throw away the useful half.
Types ¶
type Batch ¶
type Batch struct {
ID string `json:"id"`
Label string `json:"label"`
Source string `json:"source"` // "upload" or a target name
Policy string `json:"policy"`
Status string `json:"status"`
Records int `json:"records"`
Owner string `json:"owner"`
CreatedAt time.Time `json:"createdAt"`
// Commit outcome.
Committed int `json:"committed,omitempty"`
Skipped int `json:"skipped,omitempty"`
CommitAt time.Time `json:"commitAt,omitzero"`
// Revert outcome.
Reverted int `json:"reverted,omitempty"`
RevertAt time.Time `json:"revertAt,omitzero"`
}
Batch is one staged import.
type FieldError ¶
FieldError anchors a validation failure to a MARC field ("LDR" for the leader).
func ValidateOriginal ¶
func ValidateOriginal(doc marcview.RecordDoc) []FieldError
ValidateOriginal is the minimum-viability gate for editor-born records: a 24-character leader, one 245 with a non-empty $a, 40-character 008s, and well-formed fields. It judges the pruned record -- untouched skeleton rows are not errors.
type FieldTerm ¶
FieldTerm is one (access point, term) pair of a fielded search; terms AND together. Indexes are the ones libcodex maps on both protocols.
type Match ¶
type Match struct {
WorkID string `json:"workId,omitempty"`
InstanceID string `json:"instanceId,omitempty"`
MatchedWork bool `json:"matchedWork"`
MatchedInstance bool `json:"matchedInstance"`
}
Match is a staged record's dry-run identity resolution against the current corpus.
type PartialError ¶ added in v0.109.0
PartialError reports a stream that broke after delivering records. It carries both, because a caller needs the hits and the reason the set is short.
Total is what the target said its result set holds, or -1 when it never said (unknownTotal). "1 of 9 records arrived" is a different sentence from "1 record arrived", and only the first tells a cataloger how much they are missing.
The zero value is inert rather than wrong: a PartialError exists only when at least one record arrived, so Total == 0 can never exceed Got and reads as "no total", the same as -1.
func (*PartialError) Error ¶ added in v0.109.0
func (e *PartialError) Error() string
func (*PartialError) Unwrap ¶ added in v0.109.0
func (e *PartialError) Unwrap() error
type Profile ¶
type Profile struct {
Name string `json:"name"`
Targets []string `json:"targets,omitempty"`
Policy string `json:"policy,omitempty"`
}
Profile is a saved staging configuration: which targets a search fans out to and the overlay policy a staged batch starts with -- recurring imports stop re-entering the same choices per batch.
type RevertResult ¶
type RevertResult struct {
Batch Batch `json:"batch"`
Reverted int `json:"reverted"`
Skipped []RevertSkip `json:"skipped,omitempty"`
}
RevertResult summarizes a batch revert.
type RevertSkip ¶
RevertSkip reports one grain the revert left alone.
type SearchFunc ¶
type SearchFunc func(ctx context.Context, t Target, terms []FieldTerm, limit int) ([]*codex.Record, error)
SearchFunc is the protocol seam: it fetches up to limit records from one target. A non-nil error alongside records means the set is incomplete; see Incomplete. Tests inject fakes; production uses protocolSearch.
type SearchResult ¶
type SearchResult struct {
Target string `json:"target"`
Title string `json:"title,omitempty"`
Author string `json:"author,omitempty"`
Date string `json:"date,omitempty"`
ISBN string `json:"isbn,omitempty"`
Edition string `json:"edition,omitempty"`
LCCN string `json:"lccn,omitempty"`
Record marcview.RecordDoc `json:"record"`
}
SearchResult is one external hit, ready to stage.
type Service ¶
type Service struct {
Blob blob.Store
DB store.Store
// Queue receives audit entries (optional).
Queue *suggest.Service
// Trigger gets one grains-changed event per commit (optional).
Trigger trigger.Notifier
// Prefix roots the grain tree ("" = repo layout).
Prefix string
// Feed names the provenance graph committed batches write (default
// "copycat").
Feed string
// Search overrides the protocol clients (tests); nil = protocolSearch.
Search SearchFunc
// Index, when set (and Prefix is ""), is the shared work index match
// passes seed from instead of loading the whole prior grain store per
// Stage/Commit; nil falls back to LoadPriorStore.
Index *workindex.Index
}
Service is the copy-cataloging surface.
func (*Service) Commit ¶
Commit runs a batch's importable records through the shared store-backed ingest pipeline: matches are re-resolved against the current corpus, the overlay policy filters, and grains land via CAS with editorial preserved. A COMMITTED batch refuses to commit again: the grain pipeline is byte-stable across a re-commit, but the undo bookkeeping is not -- a double commit re-derived Created from the now-existing grains, poisoning the revert set so revert reported success while leaving the created works live. Committing a REVERTED batch stays allowed: it is the redo of an undone import, and at that point the store again holds the true prior state, so the undo set re-derives correctly.
func (*Service) DeleteBatch ¶
DeleteBatch removes a batch, its records, and its revert set. A COMMITTED batch is refused: its revert-set is the only pre-commit snapshot Revert can roll the commit back from, and the works it created persist, so deleting it would strand them non-revertable. This is symmetric to the commit-side guard, which refuses re-committing a COMMITTED batch. Revert the batch first (which sets it REVERTED), then it deletes freely.
func (*Service) DeleteProfile ¶
DeleteProfile removes a staging profile.
func (*Service) DeleteTarget ¶
DeleteTarget removes a target.
func (*Service) PutProfile ¶
PutProfile creates or replaces a staging profile.
func (*Service) Revert ¶
Revert rolls a committed batch back grain by grain: overlaid grains get their pre-commit bytes restored exactly (byte-stable), created grains are tombstoned (URL stability over deletion), and any grain whose post-commit state carries later edits is skipped and reported -- editorial work is never destroyed by an undo.
func (*Service) Review ¶
func (s *Service) Review(ctx context.Context, id, policy string, decisions map[int]string) (Batch, error)
Review updates a batch's overlay policy and per-record decisions.
func (*Service) SearchAll ¶
func (s *Service) SearchAll(ctx context.Context, query string, fields []FieldTerm, names []string) ([]SearchResult, map[string]string, map[string]string, error)
SearchAll fans the query out to every configured target (or the named subset) concurrently and returns the normalized hits; per-target failures come back as errors keyed by target name rather than failing the fan-out. A bare query searches the server-choice "any" index; fields AND onto it.
warnings names the targets whose answer is incomplete but usable: a stream that broke after some records, or one the search limit cut short. Those targets' hits are in results -- a partial success is not a failure, and hiding the hits would throw away the useful half -- but a cataloger deciding "my book is not in this catalog" must be told the set is short.
func (*Service) SeedDefaultTargets ¶
SeedDefaultTargets installs DefaultTargets so a fresh deployment's subject lookup and copy cataloging work without configuration. It runs once ever per store (a marker record remembers the seeding), so an admin who deletes every target stays at zero across restarts.
func (*Service) Stage ¶
func (s *Service) Stage(ctx context.Context, label, source string, docs []marcview.RecordDoc, owner string) (Batch, []StagedRecord, error)
Stage creates a batch from record docs (search imports or a parsed .mrc upload), each carrying its match banner.
func (*Service) StageMARC ¶
func (s *Service) StageMARC(ctx context.Context, label string, mrc []byte, owner string) (Batch, []StagedRecord, error)
StageMARC parses raw ISO 2709 bytes and stages them.
func (*Service) StageOriginal ¶
func (s *Service) StageOriginal(ctx context.Context, label string, doc marcview.RecordDoc, owner string) (Batch, []StagedRecord, []FieldError, error)
StageOriginal prunes, validates, and stages one editor-born record as a source "original" batch. Field-anchored errors come back instead of a batch when the record fails the gate.
type StagedRecord ¶
type StagedRecord struct {
Index int `json:"index"`
Record marcview.RecordDoc `json:"record"`
Title string `json:"title,omitempty"`
Match Match `json:"match"`
Decision string `json:"decision"`
}
StagedRecord is one record of a batch, reviewable before commit.
type Target ¶
type Target struct {
Name string `json:"name"`
// URL: an SRU base URL, or a Z39.50 "host:port/database" target.
URL string `json:"url"`
Protocol string `json:"protocol"`
// SRU dialect knobs, all optional (Z39.50 targets ignore them).
// Version is the SRU protocol version ("" = the client default, 1.2);
// DNB, for one, only answers 1.1.
Version string `json:"version,omitempty"`
// Schema is the recordSchema requested ("" = marcxml); servers name
// their MARC21 XML schema differently (DNB: "MARC21-xml").
Schema string `json:"schema,omitempty"`
// Indexes overrides the CQL index an access point maps to, for servers
// off the Dublin Core / Bath mapping (K10plus: {"isbn": "pica.isb"}).
Indexes map[string]string `json:"indexes,omitempty"`
}
Target is one external search source.
type Template ¶
type Template struct {
ID string `json:"id"`
Label string `json:"label"`
Record marcview.RecordDoc `json:"record"`
}
Template is a blank-record skeleton for one material type: LDR and fixed fields prefilled, the common data fields present but empty (empty rows prune away at staging).
func LoadTemplates ¶
LoadTemplates returns the shipped skeletons, sorted by id.