diversity

package
v0.229.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package diversity maps a work's controlled subjects to diversity-audit categories. It is the "content demographics" half of the diversity-audit feature: what works are *about*, derived from their subject headings. Creator demographics are a separate, opt-in axis.

The crosswalk is data-driven from an embedded TOML seed, which an operator may override with their own file. Matching a subject to a category is a statement about the work's topic, never about the identity of its creators.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EncodeCategories added in v0.191.0

func EncodeCategories(cats []Category) ([]byte, error)

EncodeCategories renders categories as a crosswalk TOML document -- the same format Load reads and `lcat diversity-audit --crosswalk` takes, so a server-persisted override stays portable to the CLI.

Types

type Auditor

type Auditor struct {
	// contains filtered or unexported fields
}

Auditor streams works into a coverage-first content-diversity tally. Build it once from a crosswalk, Add each work's subjects, then read Report. It is not safe for concurrent Add.

func NewAuditor

func NewAuditor(cw *Crosswalk) *Auditor

NewAuditor returns an Auditor over the given crosswalk.

func (*Auditor) Add

func (a *Auditor) Add(subjects []SubjectRef)

Add folds one work's subjects into the tally. A work counts toward CoveredWorks when it carries at least one subject with a URI or a non-empty label, and toward a category once when any of its subjects maps there. A work with no usable subjects contributes only to TotalWorks -- it dilutes coverage, which is the point of reporting coverage.

func (*Auditor) Report

func (a *Auditor) Report() Report

Report snapshots the tally as a coverage-first Report, with categories in the crosswalk's stable reporting order. Shares are 0 when their denominator is 0.

type Category

type Category struct {
	ID       string   `toml:"id" json:"id"`
	Label    string   `toml:"label" json:"label,omitempty"`
	Keywords []string `toml:"keywords" json:"keywords,omitempty"`
	URIs     []string `toml:"uris" json:"uris,omitempty"`
	Schemes  []string `toml:"schemes" json:"schemes,omitempty"`
	// Roots are authority URIs whose transitive skos:narrower closure joins
	// the category -- "these URIs plus everything beneath them", expanded at
	// audit time from the loaded scheme (WithNarrower), so the facet
	// self-maintains as the vocabulary gains terms. A small CURATED set, not
	// one URI: closure only descends, so siblings and high parents each need
	// their own root; and concept/-ism terms with no broader edge stay the
	// keywords' job. Roots union with uris and keywords.
	Roots []string `toml:"roots" json:"roots,omitempty"`
	// Benchmark is an operator-supplied comparison share in [0,1] (service-area
	// demographics, publishing output, or the collection's own baseline), with
	// BenchmarkSource naming where it came from ("ACS 2024 service area",
	// "CCBC 2025"). The seed ships none: there is no standard target percentage,
	// and a benchmark without a named source is a number pretending to be a goal.
	Benchmark       *float64 `toml:"benchmark,omitempty" json:"benchmark,omitempty"`
	BenchmarkSource string   `toml:"benchmarkSource,omitempty" json:"benchmarkSource,omitempty"`
}

Category is one diversity-audit category and the controlled subjects that map to it: an exact authority-URI match, a whole-word/phrase match on a subject's heading label (tolerating plural inflections of each keyword), or a whole-vocabulary match by scheme code (e.g. every homosaurus-scheme subject is LGBTQIA+-relevant). The taxonomy is an editorial choice; this is its data shape.

func ParseCategories added in v0.191.0

func ParseCategories(data []byte) ([]Category, error)

ParseCategories parses one crosswalk TOML document into its categories, validating that every category names an id. It does not merge with the seed; use FromBytes to validate the full merged build.

func Seed added in v0.191.0

func Seed() []Category

Seed returns the embedded seed's categories with full matching detail, for surfaces that present the built-in taxonomy alongside an operator's override.

type CategoryTally

type CategoryTally struct {
	ID           string  `json:"id"`
	Label        string  `json:"label"`
	Works        int     `json:"works"`
	ShareCovered float64 `json:"shareCovered"`
	ShareTotal   float64 `json:"shareTotal"`
	// Benchmark/BenchmarkSource pass the operator's comparison share through
	// from the crosswalk, when one was configured. The tool never grades the
	// delta: a share against a benchmark is only as good as the coverage
	// above it, and interpreting the gap is the librarian's call.
	Benchmark       *float64 `json:"benchmark,omitempty"`
	BenchmarkSource string   `json:"benchmarkSource,omitempty"`
}

CategoryTally is one category's representation. Works counts each work once, however many of its subjects matched. ShareCovered is Works/CoveredWorks (the share among works that could be categorized at all); ShareTotal is Works/TotalWorks (the share of the whole corpus).

type Crosswalk

type Crosswalk struct {
	// contains filtered or unexported fields
}

Crosswalk categorizes subjects. It is built once (from the seed plus any operator overrides) and is read-only thereafter, so it is safe for concurrent use.

func Default

func Default() *Crosswalk

Default returns the crosswalk built from the embedded seed alone. A malformed seed is a programming error (the embedded file is guarded by a golden test), so it panics rather than returning a silently empty categorizer.

func FromBytes added in v0.191.0

func FromBytes(overrides ...[]byte) (*Crosswalk, error)

FromBytes returns the crosswalk built from the embedded seed with each override document (raw TOML bytes) merged over it in order -- Load for callers whose override lives somewhere other than a file, e.g. a persisted server-side override.

func Load

func Load(overridePaths ...string) (*Crosswalk, error)

Load returns the crosswalk built from the embedded seed with each override file merged over it in order. Merge is by category id: an override category unions its keywords and uris onto the matching seed category and, when it sets a non-empty label, replaces the label; an override category with a new id is appended. A missing or malformed override file is an error -- an operator who points libcat at a crosswalk expects it to be honored, not silently ignored.

func (*Crosswalk) Categories

func (c *Crosswalk) Categories() []Category

Categories returns the categories in stable reporting order (seed order, then any operator-added categories). Keyword/URI detail is omitted; this is for labeling aggregated counts.

func (*Crosswalk) Categorize

func (c *Crosswalk) Categorize(uri, label, scheme string) []string

Categorize returns the ids of every category a single subject maps to, in stable reporting order. A subject matches a category by exact authority URI, by its vocabulary scheme code (e.g. every homosaurus-scheme subject), or by a whole-word/phrase match of any of the category's keywords against the heading label, tolerating plural inflections. Passing "" for an argument skips that dimension.

func (*Crosswalk) CategorizeSubjects

func (c *Crosswalk) CategorizeSubjects(subjects []SubjectRef) []string

CategorizeSubjects returns the ids of every category any of the given subjects maps to, deduplicated and in stable reporting order -- the work-level roll-up a caller uses to count a work once per matched category.

func (*Crosswalk) Definitions added in v0.191.0

func (c *Crosswalk) Definitions() []Category

Definitions returns the categories in stable reporting order WITH their merged matching detail (keywords, uris, schemes) -- the shape a crosswalk editor presents. The copy is deep, so callers cannot mutate the crosswalk.

func (*Crosswalk) Label

func (c *Crosswalk) Label(id string) string

Label returns a category's display label, or the id itself if unknown.

func (*Crosswalk) WithNarrower added in v0.217.0

func (c *Crosswalk) WithNarrower(narrower func(uri string) []string) *Crosswalk

WithNarrower returns a crosswalk whose categories' root sets are expanded through the transitive skos:narrower closure the resolver describes: for each category root, every descendant URI matches the category exactly as an explicit uris entry would. The receiver is untouched (it stays safe for concurrent use); a nil resolver, or a crosswalk with no roots, returns the receiver itself. Polyhierarchy and cycles are fine -- closure membership is a set.

type MultiplicityTally added in v0.186.0

type MultiplicityTally struct {
	Uncategorized int `json:"uncategorized"`
	MatchedOne    int `json:"matchedOne"`
	MatchedMulti  int `json:"matchedMulti"`
}

MultiplicityTally is the exclusive covered-works decomposition.

type Report

type Report struct {
	// TotalWorks is every work the audit saw.
	TotalWorks int `json:"totalWorks"`
	// CoveredWorks is the works carrying at least one subject (URI or label) --
	// the only works that can be categorized, and the honest denominator.
	CoveredWorks int `json:"coveredWorks"`
	// Coverage is CoveredWorks/TotalWorks in [0,1]. A low value means the audit
	// speaks for only part of the collection; read every category share with it.
	Coverage float64 `json:"coverage"`
	// Categories are the diversity categories in the crosswalk's reporting order.
	Categories []CategoryTally `json:"categories"`
	// Multiplicity decomposes CoveredWorks exclusively -- how many covered
	// works matched no category, exactly one, or two or more. Unlike the
	// per-category tallies (which overlap), these three sum with the
	// uncovered remainder to TotalWorks, so they stack honestly in a
	// composition chart; MatchedMulti doubles as the intersectionality
	// signal.
	Multiplicity MultiplicityTally `json:"multiplicity"`
}

Report is a coverage-first content-diversity audit of a corpus. It reports what the works are *about*, never who created them. Every category share is stated against an explicit denominator so undercounting is visible rather than hidden: CoveredWorks (works that carry any subject at all) is the honest base for representation, while ShareTotal shows the dilution across the whole corpus, including works no one has subjected yet.

type SubjectRef

type SubjectRef struct {
	URI    string
	Labels []string
	Scheme string
}

SubjectRef is one of a work's subjects as the audit sees it: an authority URI (may be empty for a bare-string ILS heading), its heading labels (may be empty for a URI-only reference), and the vocabulary scheme code ("homosaurus", "fast", "lcsh"; may be empty). Every dimension feeds the crosswalk.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL