bead.interop

Lossless, law-verified interoperability mappings between bead models and the layers linguistic-annotation schema. The layers subpackage maps bead's corpus, annotation, and resource data to and from the canonical lairs.records models via didactic lenses (dx.Iso / dx.Lens); round-trip fidelity is guaranteed by the GetPut/PutGet lens laws.

See the Layers Interoperability guide for usage.

Bridge lenses (bead-native <-> layers)

graph_lens

Lens between a CorpusGraph and the layers property graph.

The lens projects a :class:~bead.corpus.graph.CorpusGraph to a :class:CorpusGraphLayers view (a bundle of canonical :class:lairs.records.expression.Expression and :class:lairs.records.graph.GraphNode records plus a :class:lairs.records.graph.GraphEdgeSet) and keeps a complement holding the information layers' graph does not express directly (the bead framework identity, the bead node ids, edge directedness, and exact float confidence). Together the view and complement reconstruct the graph exactly.

CorpusGraphLayers

Bases: Model

A layers view of a corpus graph: expressions, graph nodes, and an edge set.

The records are ordered tuples; the bead node ids that key them live in the lens complement (layers records carry no bead node id). An empty graph projects to empty tuples and an empty edge set.

CorpusGraphLayersLens

Bases: Lens[CorpusGraph, CorpusGraphLayers, JsonValue]

Lossless lens CorpusGraph <-> (layers graph view, bead complement).

forward(graph_in: CorpusGraph) -> tuple[CorpusGraphLayers, JsonValue]

Project a graph to its layers view and bead complement.

backward(view: CorpusGraphLayers, complement: JsonValue) -> CorpusGraph

Reconstruct the graph from its layers view and bead complement.

graph_to_layers(graph_in: CorpusGraph) -> CorpusGraphLayers

Return the standalone layers-shaped view of a corpus graph.

bridges

Bridge lens between a bead CorpusRecord and a layers expression.

A :class:~bead.corpus.records.CorpusRecord projects to a canonical :class:lairs.records.expression.Expression; the lens complement holds the bead-only remainder (framework identity, the source name, and the record index, which layers has no slot for), so the round-trip is exact and the GetPut/PutGet laws hold.

RecordExpressionLens

Bases: Lens[CorpusRecord, Expression, JsonValue]

Lossless lens CorpusRecord <-> (layers expression, complement).

forward(record: CorpusRecord) -> tuple[expression.Expression, JsonValue]

Project a record to a layers expression and bead complement.

backward(view: expression.Expression, complement: JsonValue) -> CorpusRecord

Reconstruct a record from its layers expression and complement.

record_to_expression(record: CorpusRecord) -> expression.Expression

Return the standalone layers expression view of a corpus record.

parse_lens

Lossless iso between a dependency parse and layers annotation records.

A :class:~bead.tokenization.parsers.ParsedSentence maps to a :class:ParsedSentenceLayers view: a canonical :class:lairs.records.segmentation.Tokenization plus two :class:lairs.records.annotation.AnnotationLayer records (a part-of-speech token-tag layer and a dependency relation layer). ParsedToken/ParsedSentence carry no framework identity, so the mapping is a true bijection (dx.Iso).

The layers token has no spaceAfter slot, so each token's space-after flag travels in its part-of-speech annotation's features alongside the lemma, xpos, and morphology. The annotation layers require an expression reference and a timestamp that the parse does not carry; these are emitted as fixed constants and ignored on the way back.

ParsedSentenceLayers

Bases: Model

A layers view of a parse: a tokenization plus pos and dependency layers.

ParsedSentenceLayersIso

Bases: Iso[ParsedSentence, ParsedSentenceLayers]

Lossless ParsedSentence <-> layers tokenization + annotation layers.

forward(sentence: ParsedSentence) -> ParsedSentenceLayers

Project a parsed sentence to layers tokenization + annotations.

backward(view: ParsedSentenceLayers) -> ParsedSentence

Reconstruct a parsed sentence from its layers projection.

parse_to_layers(sentence: ParsedSentence) -> ParsedSentenceLayers

Return the layers tokenization + annotation-layer view of a parse.

item_bridge

Lens between a bead Item and canonical layers annotation records.

A bead :class:~bead.items.item.Item carries standoff span and relation annotations over one or more rendered elements. This lens projects an item to a :class:lairs.integrations.codecs.CorpusFragment of canonical lairs records:

  • one :class:lairs.records.expression.Expression per rendered element,
  • one :class:lairs.records.segmentation.Segmentation per tokenized element,
  • one span :class:lairs.records.annotation.AnnotationLayer per element whose spans anchor by tokenRefSequence (head_index maps to anchorTokenIndex; a Wikidata label_id maps to a knowledgeRef),
  • one relation AnnotationLayer whose annotations carry ArgumentRef source/target objectRef arguments.

The layers token has no space-after slot and an Item carries many fields layers cannot express, so the round-trip is a dx.Lens: the view captures the text, tokenization, and a faithful annotation projection; the complement carries the bead-only remainder (framework identity, the item-construction fields, the space-after flags, and the spans and relations verbatim), so the GetPut/PutGet laws hold for every item.

ItemLayersLens

Bases: Lens[Item, CorpusFragment, JsonValue]

Lossless lens Item <-> (layers corpus fragment, bead complement).

forward(item: Item) -> tuple[CorpusFragment, JsonValue]

Project an item to a layers fragment and bead complement.

backward(view: CorpusFragment, complement: JsonValue) -> Item

Reconstruct an item from its layers fragment and bead complement.

item_to_layers(item: Item) -> CorpusFragment

Return the standalone layers fragment view of an item.

Resource lenses

Lenses between bead's resource models and their layers counterparts: lexical items and lexicons to entries and collections, templates to layers templates, and filled templates to layers fillings.

resource_lens

Lenses between bead resource models and layers resource records.

Maps bead's lexical and template resources to their canonical :mod:lairs.records.resource counterparts:

  • LexicalItem <-> a layers entry
  • Lexicon <-> a layers collection with its entry records
  • Template <-> a layers template (with its slots and constraints)
  • FilledTemplate <-> a layers filling (with its per-slot fillings)

Each lens produces a layers-shaped view from the generated models and keeps the fields that have no layers equivalent (the bead framework identity, tags, the LexicalItem original form / free-text source, the bead DSL constraint context, and the filled-template slot requirement map) in the lens complement, so reconstruction is exact.

LexicalItemEntryLens

Bases: Lens[LexicalItem, Entry, JsonValue]

Lossless lens LexicalItem <-> (layers entry, complement).

forward(item: LexicalItem) -> tuple[resource.Entry, JsonValue]

Project a lexical item to a layers entry and complement.

backward(view: resource.Entry, complement: JsonValue) -> LexicalItem

Reconstruct a lexical item from its layers entry and complement.

LexiconLayers

Bases: Model

A layers view of a lexicon: a collection plus its entry records.

LexiconCollectionLens

Bases: Lens[Lexicon, LexiconLayers, JsonValue]

Lossless lens Lexicon <-> (layers collection + entries, complement).

forward(lexicon: Lexicon) -> tuple[LexiconLayers, JsonValue]

Project a lexicon to a layers collection + entry views.

backward(view: LexiconLayers, complement: JsonValue) -> Lexicon

Reconstruct a lexicon from its layers collection + complement.

TemplateLayersLens

Bases: Lens[Template, Template, JsonValue]

Lossless lens Template <-> (layers template, complement).

forward(template: Template) -> tuple[resource.Template, JsonValue]

Project a template to a layers template and complement.

backward(view: resource.Template, complement: JsonValue) -> Template

Reconstruct a template from its layers template and complement.

FilledTemplateFillingLens

Bases: Lens[FilledTemplate, Filling, JsonValue]

Lossless lens FilledTemplate <-> (layers filling, complement).

The layers filling record (resource.Filling) is the canonical representation of a filled template: it carries the template reference, the per-slot fillings, the rendered text, and the filling strategy. The bead-only remainder (identity, the source template name, the slot requirement map, and the exact lexical-item fillers) travels in the lens complement.

forward(filled: FilledTemplate) -> tuple[resource.Filling, JsonValue]

Project a filled template to a layers filling and complement.

backward(view: resource.Filling, complement: JsonValue) -> FilledTemplate

Reconstruct a filled template from its layers filling and complement.

Judgment and list lenses

Lenses between bead's response and list-composition models and their layers counterparts: annotation records to judgments (and judgment sets), bead list constraints to layers list constraints, and experiment lists to layers collections with their memberships and constraints.

judgment_lens

Lenses between bead annotation records and layers judgment records.

Maps bead's reliability inputs to their canonical :mod:lairs.records.judgment counterparts:

  • AnnotationRecord <-> a layers judgment
  • a tuple of AnnotationRecord sharing one annotator <-> a layers judgmentSet

A single layers judgment has no slot for the annotator or the question name; those belong to the parent judgmentSet and experiment. The lens therefore keeps the bead framework identity, the annotator id, the question name, and the raw item id / response label in the lens complement so reconstruction is exact. The aggregate is modelled as two plain functions (a tuple is not a single dx.Model), each one delegating to the per-record lens so every record round-trips exactly.

AnnotationRecordJudgmentLens

Bases: Lens[AnnotationRecord, Judgment, JsonValue]

Lossless lens AnnotationRecord <-> (layers judgment, complement).

The layers judgment record (:class:lairs.records.judgment.Judgment) is the canonical representation of a single annotator response: it carries the item reference and the categorical value. The bead-only remainder (identity, annotator id, question name, and the raw item id / response label) travels in the lens complement, since a single judgment has no slot for an annotator or a question name.

forward(record: AnnotationRecord) -> tuple[judgment.Judgment, JsonValue]

Project an annotation record to a layers judgment and complement.

backward(view: judgment.Judgment, complement: JsonValue) -> AnnotationRecord

Reconstruct an annotation record from its layers judgment + complement.

records_to_judgment_set(records: tuple[AnnotationRecord, ...], participant: Participant | None = None) -> tuple[judgment.JudgmentSet, JsonValue]

Project records sharing one annotator to a layers judgment set.

Groups a homogeneous tuple of annotation records (all from one annotator) into a layers judgmentSet, projecting each record through :data:ANNOTATION_RECORD_JUDGMENT. The set's agent carries the shared annotator id and its createdAt is the earliest record creation time. The complement carries the shared annotator id and the per-record complements, so :func:judgment_set_to_records inverts it exactly.

When the annotator's :class:~bead.participants.models.Participant is supplied, the set's agent is that participant's agentRef and its features carries the participant's study fields (demographics, sessions, consent), which the judgmentSet schema documents as the home for "annotator demographics, session metadata, completion time, payment info". The participant is supplementary provenance on the view: the records reconstruct from the complement alone, so judgment_set_to_records is unaffected.

Parameters:

Name Type Description Default
records tuple[AnnotationRecord, ...]

Annotation records that all share one annotator_id. Must be non-empty.

required
participant Participant | None

The annotator behind records. When given, its identity and study fields enrich the judgment set's agent and features.

None

Returns:

Type Description
tuple[JudgmentSet, JsonValue]

The layers judgment set view and the lens complement.

Raises:

Type Description
ValueError

If records is empty, or if the records do not all share the same annotator_id.

See Also

judgment_set_to_records : Invert this projection.

judgment_set_to_records(view: judgment.JudgmentSet, complement: JsonValue) -> tuple[AnnotationRecord, ...]

Reconstruct annotation records from a layers judgment set + complement.

Inverts :func:records_to_judgment_set by zipping each layers judgment in the set with its per-record complement and delegating to :data:ANNOTATION_RECORD_JUDGMENT, so every record round-trips exactly.

Parameters:

Name Type Description Default
view JudgmentSet

The layers judgment set produced by :func:records_to_judgment_set.

required
complement JsonValue

The matching lens complement (per-record complements plus the shared annotator id).

required

Returns:

Type Description
tuple[AnnotationRecord, ...]

The reconstructed annotation records, in their original order.

See Also

records_to_judgment_set : The forward projection.

list_lens

Lenses between bead list-composition models and layers judgment records.

Maps bead's experimental list models to their canonical :mod:lairs.records counterparts:

  • ListConstraint <-> a layers listConstraint (judgment.ListConstraint)
  • ExperimentList <-> a layers collection with its collectionMembership records and per-list listConstraint records, bundled as the bead-side :class:ExperimentListLayers aggregate view

The bead ListConstraint union is far richer than the four scalar fields of the layers listConstraint record, so the lens carries the entire bead constraint (serialized with model_dump_json) in the lens complement and reconstructs the exact subclass with ListConstraint.model_validate_json on the way back. The ExperimentList lens keeps the bead framework identity, the numeric list id, the ordered item references, the presentation order, the list and balance metadata, the per-constraint satisfaction records, and the per-constraint complements in its own complement, so reconstruction is exact.

ListConstraintLens

Bases: Lens[ListConstraint, ListConstraint, JsonValue]

Lossless lens ListConstraint <-> (layers list constraint, complement).

The layers listConstraint record (judgment.ListConstraint) keeps only a kind slug, an optional targetProperty, an optional formal constraint expression, and an optional parameters map. The bead union carries many more variant-specific fields, so the projection is lossy on its own; the complement stores the entire bead constraint as model_dump_json and reconstruction resolves the exact subclass through the discriminated-union root.

forward(constraint: ListConstraint) -> tuple[judgment.ListConstraint, JsonValue]

Project a list constraint to a layers list constraint and complement.

backward(view: judgment.ListConstraint, complement: JsonValue) -> ListConstraint

Reconstruct a list constraint from its complement.

The layers view is purely a projection; the exact bead constraint comes from the serialized model in the complement, whose discriminated-union root resolves the correct subclass.

ExperimentListLayers

Bases: Model

A layers view of an experiment list.

Attributes:

Name Type Description
collection Collection

The list itself, projected to a layers collection of kind stimulus-pool.

memberships tuple[CollectionMembership, ...]

One membership per item reference, in presentation-independent item order, carrying the item ordinal.

list_constraints tuple[ListConstraint, ...]

The per-list constraints, projected via :data:LIST_CONSTRAINT.

ExperimentListLens

Bases: Lens[ExperimentList, ExperimentListLayers, JsonValue]

Lossless lens ExperimentList <-> (layers collection aggregate, complement).

The layers view bundles a collection (the list), one collectionMembership per item reference (preserving order via the membership ordinal), and the per-list listConstraint records. The bead-only remainder (framework identity, the numeric list id, the ordered item references, the presentation order, the list and balance metadata, the constraint-satisfaction records, and the per-constraint complements) travels in the lens complement, so a round-trip reconstructs the original list exactly.

forward(experiment_list: ExperimentList) -> tuple[ExperimentListLayers, JsonValue]

Project an experiment list to a layers collection aggregate.

backward(view: ExperimentListLayers, complement: JsonValue) -> ExperimentList

Reconstruct an experiment list from its layers aggregate and complement.

Codec and corpus I/O

codec

A lairs codec that round-trips a bead ItemCollection.

:class:BeadCodec binds the :class:lairs.integrations.ports.Codec port so a downstream user can reach it through lairs.codec("bead") once both packages are installed (the codec is registered via the lairs.codecs entry point in bead's project metadata).

The codec's external format is a bead ItemCollection serialized as JSON. decode runs each item through :data:~bead.interop.layers.item_bridge.ITEM_LAYERS and concatenates the resulting canonical layers records, threading each item's lens complement and the collection's own fields as private records under the bead.interop.complement NSID (a layers consumer ignores unknown NSIDs). Because the complement rides along, encode(decode(x)) == x holds losslessly for every collection, and a single canonical mapping (the lens) drives both directions.

BeadCodec

Bidirectional codec ItemCollection JSON <-> layers corpus fragment.

decode(src: str | bytes, *, into: CorpusFragment | None = None) -> CorpusFragment

Decode a bead ItemCollection JSON document into a layers fragment.

encode(records: Iterable[FragmentRecord]) -> str

Encode layers fragment records back into ItemCollection JSON.

corpus_io

Ingest a layers Corpus into bead models and emit bead data as a corpus.

Ingest (layers to bead):

  • :func:expression_to_record / :func:corpus_to_records stream a corpus's expressions as :class:~bead.corpus.records.CorpusRecord instances.
  • :func:corpus_to_graph builds a :class:~bead.corpus.graph.CorpusGraph from a corpus, deriving a parent edge from each expression's parentRef.
  • :func:corpus_to_items reconstructs best-effort :class:~bead.items.item.Item instances from a corpus's span and relation annotation layers.
  • :func:load_layers_corpus is a thin wrapper over :func:lairs.load_corpus.

Egress (bead to layers):

  • :func:items_to_corpus and :func:graph_to_corpus build a :class:lairs.data.Corpus from bead data, reusing the canonical lenses.
  • :func:materialize_corpus, :func:save_corpus_repo, and :func:publish_corpus delegate to the corresponding lairs store and publish entry points; the network-bound publish path is opt-in and defaults to a dry run.

expression_to_record(expr: expression.Expression, *, source_name: str, record_index: int = 0) -> CorpusRecord

Build a :class:CorpusRecord from a layers expression.

corpus_to_records(corpus: Corpus, *, source_name: str | None = None) -> Iterator[CorpusRecord]

Stream a corpus's member expressions as corpus records.

corpus_to_graph(corpus: Corpus, *, source_name: str | None = None) -> CorpusGraph

Build a corpus graph from a corpus, with a parent edge per parentRef.

corpus_to_items(corpus: Corpus, *, item_template_id: UUID) -> Iterator[Item]

Reconstruct best-effort items from a corpus's annotation layers.

Third-party corpora carry no bead complement, so item-construction fields take their defaults; the rendered text, spans, and relations are recovered from the expression and its span and relation annotation layers.

load_layers_corpus(uri: str, *, source: str = 'pds', pds_client: PdsClient | None = None, follow_refs: bool = True) -> Corpus

Load a layers corpus by AT-URI (a thin wrapper over lairs.load_corpus).

items_to_corpus(collection: ItemCollection, *, corpus_name: str, authority: str = 'local') -> Corpus

Build a layers corpus from a bead item collection.

Each item's rendered elements become expressions, its tokenizations become segmentations, and its spans and relations become annotation layers (reusing :data:~bead.interop.layers.item_bridge.ITEM_LAYERS); a membership links each expression to the corpus, with one corpus record describing the dataset.

graph_to_corpus(graph: CorpusGraph, *, corpus_name: str, authority: str = 'local') -> Corpus

Build a layers corpus from a corpus graph's record-bearing nodes.

materialize_corpus(corpus: Corpus, out_dir: Path) -> list[Path]

Materialize a corpus to Arrow/Parquet views (delegates to lairs).

save_corpus_repo(corpus: Corpus, path: Path) -> str

Commit a corpus to a local lairs repository and return the revision.

publish_corpus(repo: Repository, revision: str, *, to: str, endpoint: str | None = None, client: httpx.Client | None = None, dry_run: bool = True) -> object

Publish a committed corpus revision to a PDS (opt-in; default dry run).

Returns the lairs PublishPlan. endpoint is the PDS base URL and client an authorized httpx.Client; both are required for an actual write (dry_run=False). The network-bound publish entry point is imported lazily so importing this module never pulls the publish stack.