Documentation
¶
Overview ¶
Package identity classifies and normalises whatever the user typed.
Input arrives as a DOI in five formats, an arXiv ID in two generations, a bare OpenAlex ID, a URL, a PMID, or a title typed from memory. The first five resolve deterministically and silently.
A title does not. Title search returns candidates and requires confirmation — interactively in the CLI, or via an explicit --accept-first in scripts (ADR-005). The asymmetry is deliberate: a wrong seed is not a small error, it poisons everything expanded from it, and the user may not notice for weeks.
A malformed identifier is refused with errs.ErrInvalidInput, never passed on to a title search: that would ask the user to pick a candidate for something that was never a title.
It also owns fil IDs (D17): Filiation's own name for a work, "F" and eight Crockford base32 characters, generated here and parsed here like every other identifier. A fil ID names a work already in the library; OpenAlex has never heard of one.
Pure functions over strings, except NewFilID, which reads crypto/rand. A leaf package: no I/O, and one internal import, errs, so that a refusal reaches the CLI as a sentinel it can branch on. errs imports nothing, so no cycle is possible — the same exception config takes.
Index ¶
- Constants
- func IsFilID(s string) bool
- func NewFilID() string
- func NormaliseDOI(s string) (string, error)
- func NormaliseFilID(s string) (string, error)
- func NormaliseOpenAlexID(s string) (string, error)
- func TitleKey(title string) string
- func TitleSimilarity(a, b string) float64
- func TitlesMatch(a, b string) bool
- type ID
- type Kind
Constants ¶
const FilIDLength = 9
FilIDLength is the length of a fil ID, prefix included.
Variables ¶
This section is empty.
Functions ¶
func NewFilID ¶ added in v0.3.0
func NewFilID() string
NewFilID returns a fresh random fil ID.
Uniqueness within a library is not this function's promise: 40 random bits make a clash rare, not impossible, and the store checks each new ID against the library inside the single writer before using it. This only guarantees the shape.
func NormaliseDOI ¶
NormaliseDOI reduces a DOI to the form the store keys on: no resolver prefix, percent-decoded, stray trailing punctuation removed, and lower-cased.
Lower-casing is safe because DOIs are case-insensitive by specification, and necessary because the same DOI arrives in different cases from the user, from OpenAlex and from publisher pages; the UNIQUE index on work.doi can only catch duplicates it can see.
func NormaliseFilID ¶ added in v0.3.0
NormaliseFilID returns the canonical, upper-case form of a fil ID.
Input is case-insensitive, because people type IDs; output is always upper case, because the store compares exactly. Lookalike substitution — O for 0, I or L for 1, which Crockford allows — is deliberately not done: every substitution makes more ordinary words parse as IDs.
func NormaliseOpenAlexID ¶
NormaliseOpenAlexID reduces any spelling of an OpenAlex work ID to the bare form the store keys on: "W2741809807".
It accepts the URL forms OpenAlex itself returns — https://openalex.org/W... and https://api.openalex.org/works/W... — because sources/openalex runs every ID in every response through here. That is where the URL form is the norm, and where a missed normalisation would create a second node for the same paper.
func TitleKey ¶
TitleKey reduces a title to what survives every way it gets written: case folded, and everything but letters and digits removed. Punctuation, spacing, hyphenation and dash style vary between OpenAlex, publisher pages and what a user types from memory, and none of it distinguishes one paper from another.
It is a comparison key, never a display form and never an identity: two different papers can share a title, which is exactly why a title search needs confirmation (ADR-005).
func TitleSimilarity ¶
TitleSimilarity is 1 minus the edit distance between the two titles' keys, scaled by the longer key: 1 for identical, 0 for nothing in common.
func TitlesMatch ¶
TitlesMatch reports whether two titles plausibly name the same work.
It is for ranking and flagging title-search candidates — putting the one that matches what the user typed first and marking it — and it never licenses accepting one. Per ADR-005 a title search always waits for the user or an explicit --accept-first; a wrong seed poisons every node expanded from it, and a string similarity score is exactly the kind of evidence that is right most of the time and silently wrong the rest.
Deliberately stricter than a substring test. "Attention" is contained in "Attention Is All You Need" and is not the same paper.
Types ¶
type ID ¶
type ID struct {
Kind Kind
// Value is the normalised form, and the only form anything downstream
// should use. Two inputs naming the same paper by the same scheme always
// normalise to the same Value; that is the whole job of this package.
Value string
// Raw is exactly what the user typed, kept for error messages. A user who
// pasted five arguments needs to see which one failed in the form they
// pasted it, not in ours.
Raw string
}
ID is one parsed input.
func Parse ¶
Parse classifies input and normalises it.
The order of the checks is the design. The most specific forms go first, so that nothing reaches the title fallback that could have been read as an identifier: a malformed identifier is refused with ErrInvalidInput rather than searched for as a title. Falling through would show the user a list of candidates for something that was never a title, which is the wrong question to be asking them.
func (ID) ArXivDOI ¶
ArXivDOI is the DOI arXiv has minted for this paper, or the empty string if the ID is not an arXiv ID.
It exists because a lookup needs something OpenAlex indexes, and DOIs are. But OpenAlex does not hold every arXiv paper under this DOI — 1706.03762 is a 404 by it (recorded 24 Sept 2026) — so sources/openalex tries it first and falls back to the arXiv landing page. This package only knows the mapping, which is a fact about arXiv.
func (ID) Deterministic ¶
Deterministic reports whether this ID names exactly one work.
Every kind does except a title. A title search returns candidates, and per ADR-005 the CLI must show them and wait, or be given --accept-first explicitly. This is the flag it checks.