Skip to content

Vocabulary

The PyCharter Vocabulary pillar attaches business definitions to contract fields so two teams' notional mean the same thing: bind a field to a governed concept once, and every contract, validator, lineage query, and AI agent that touches that field sees the same definition. Concretely it adds field bindings, governed business concepts, lineage tracking, and collaborative governance workflows on top of data contracts — and serves the result to agents over MCP.

What the vocabulary provides

Sub-module Purpose
Concepts Concept schemes, governed concepts, concept types, tiers, field bindings, relationship types, lineage
Concept schemes Group concepts into named schemes (e.g. trading, CRM) with versioning and base URI
RDF / JSON-LD Export vocabulary artifacts as RDF for interoperability
Knowledge graph Graph-based discovery and lineage traversal across contracts
Collaboration Proposals, reviews, conflict resolution, and permissions for governed changes
Versioning Diff, history, and merge of vocabulary artifacts over time
Ingestion Import external concept schemes or vocabulary files
REST API HTTP routes for schemes, concepts, relationships, and semantic health at /api/v1/semantic/
Web UI Vocabulary section: concepts browser, diagrammatic workspace (ReactFlow), scheme selector, property drawer

Core concepts

Concept tiers

Governance is structured around three tiers:

Tier Enum value Description
Core ConceptTier.CORE Governed by the central data team; changes require approval
Domain ConceptTier.DOMAIN Governed by individual domain teams
Free ConceptTier.FREE No governance; used for experimental or team-local concepts

Concept types (node kinds)

Concepts in the knowledge graph are classified by concept type (also referred to as node kind in some APIs):

Type Example
entity Customer, Product, Order
process Checkout flow, Onboarding
event Payment received, Item shipped
attribute Price, Weight, Status
rule Discount eligibility, Age restriction
system Payment gateway, CRM
concept Revenue, Churn
metric Monthly active users, NPS

Relationship types

Type Meaning
references Foreign key or pointer to another field
derived_from Computed from another field
same_as Two fields represent the same concept
related_to General association
is_a Taxonomic parent (X is a kind of Y)
has Composition (X contains Y)
depends_on X requires Y
precedes X comes before Y (temporal)
causes X triggers Y
implements X is a concrete form of Y
governs X constrains Y
describes X is a property of Y
equivalent_to Semantic equivalence

Field bindings in contracts

Bind contract fields to vocabulary concepts with a top-level field_bindings block (not inline ontology: keys under JSON Schema — those are removed):

# orders.contract.yaml
id: trading/orders
version: "1.0.0"
json_schema:
  type: object
  properties:
    order_id:
      type: string
    customer_id:
      type: string
    total_amount:
      type: number
field_bindings:
  order_id:
    primary:
      concept: order_identifier
      concept_scheme: trading:1.0.0
      tags: [core, immutable]
  customer_id:
    primary:
      concept: customer_identifier
      concept_scheme: trading:1.0.0
  total_amount:
    primary:
      concept: monetary_value
      concept_scheme: trading:1.0.0

When a contract's fields map cleanly onto a LinkML class/slot vocabulary, prefer the LinkML-first authoring pattern: declare the contract's primary vocabulary once via domain_schema, then bind each field to a concrete LinkML class:slot pair. Optional concept annotations layer SKOS-style cross-references on top.

# orders.contract.yaml
id: trading/orders
version: "1.0.0"
json_schema:
  type: object
  properties:
    order_id:
      type: string
    customer_id:
      type: string
metadata:
  title: Order events
  version: "1.0.0"

domain_schema: trading:1.0.0      # primary LinkML vocabulary for this contract
field_bindings:
  order_id:
    primary:
      linkml_slot: { class: Order, slot: id }
      concept_scheme: trading:1.0.0
      tags: ["identifier"]
    annotations:
      - concept: TradeEntity
        concept_scheme: financial-core:2.1.0
        relationship: exact_match
  customer_id:
    primary:
      linkml_slot: { class: Order, slot: customer_id }
      concept_scheme: trading:1.0.0

domain_schema names the contract's primary vocabulary so the editor and downstream tooling can auto-load it. Each primary.linkml_slot binding then targets a specific class+slot pair within a declared concept scheme (usually equal to domain_schema).

Shape vs. meaning

Two distinct layers live side-by-side here:

  • Shape — what fields exist, their types, structural constraints — is the LinkML schema's job (trading:1.0.0 in the example).
  • Meaning — what concept a field refers to in the real world — is declared on the binding's annotations or on concept-only bindings.

Don't try to encode meaning inside LinkML classes or shape inside the concept registry. The bridge between the two is the binding's concept_scheme and slot.meaning: URIs on the LinkML schema itself.

Enforcing LinkML bindings at load time

Field bindings that target a LinkML class+slot can be verified against the actual LinkML schema at contract load time. Pass an :class:~pycharter.semantic.linkml.LinkMLSchemaRegistry to load_contract, load_contract_from_string, or load_contract_from_dict:

from pycharter.semantic.contracts import load_contract
from pycharter.semantic.linkml import LinkMLSchemaRegistry

registry = LinkMLSchemaRegistry()  # default: data/vocabulary/versions/
contract = load_contract(
    "orders.contract.yaml",
    linkml_schema_registry=registry,
)

For each binding whose primary.linkml_slot is set, the loader will:

  1. Resolve the binding's concept scheme (e.g. trading:1.0.0) via the registry.
  2. Confirm the named class exists in that schema.
  3. Confirm the slot is reachable on the class — directly, via slot_usage, or via the is_a / mixins inheritance chain.

Typos and stale references fail the load with an actionable message:

ContractLoadError: LinkML field-binding validation failed: 1 issue.
  - field 'order_id' → class 'Custmer', slot 'id' in schema
    'trading:1.0.0': class 'Custmer' does not exist in schema
    'trading:1.0.0'

The check is opt-in. Omit the LinkML registry argument and contract loading preserves structural validation only. Contracts without domain_schema, or with concept-only bindings (primary.concept), are unaffected.


Parsing field bindings

from pycharter.semantic import parse_field_bindings, parse_field_bindings_file

mapping = parse_field_bindings(
    {
        "domain_schema": "trading:1.0.0",
        "field_bindings": {
            "order_id": {
                "primary": {
                    "linkml_slot": {"class": "Order", "slot": "id"},
                    "concept_scheme": "trading:1.0.0",
                }
            }
        },
    }
)

mapping = parse_field_bindings_file("demo_field_bindings.yaml")
print(mapping.bound_field_count)

Key data models

FieldBinding

Per-field link from a contract field to a concept or LinkML slot:

Attribute Description
primary Primary binding target (concept and/or LinkML slot)
annotations Optional additional concept cross-references

FieldConceptBinding

Concept-only binding shape (no LinkML class/slot target).

Concept / ConceptScheme

Governed vocabulary entries and their versioned collections. See Semantic Store.


Seed vocabulary

Load example concept schemes and demo bindings with:

pycharter db seed

Example manifests reference concept_scheme.yaml files under src/pycharter/data/seed/examples/. Import additional schemes via ConceptSchemeImporter or the ingestion API routes under /api/v1/semantic/ingestion/.


RDF / JSON-LD export

Export vocabulary artifacts as RDF for interoperability with SPARQL endpoints and JSON-LD tooling:

from pycharter.semantic.rdf.emitter import emit_concepts_jsonld

# Requires: pip install pycharter[semantic]
doc = emit_concepts_jsonld(concepts, scheme_key="trading", version="1.0.0")

Wiring automatic structural extraction into a pipeline run

When a pipeline step's effective extraction_policy.trigger is automatic, the records passing through the step can be tee'd into the structural extractor (HyperLogLog / Count-Min Sketch / reservoir sampling) and materialised as suggestions in the governed queue. The plumbing is opt-in at the caller — run_dag accepts a extraction_sink_factory that constructs an :class:AutoExtractionSink per load step.

A minimal CLI / API / worker wiring looks like:

from pycharter.pipeline_generator import run_dag
from pycharter.pipeline_generator.steps._base import LoadStepConfig
from pycharter.semantic.extraction.auto_trigger import AutoExtractionSink
from pycharter.semantic.extraction_policy import resolve_effective_policy


def make_extraction_sink_factory(session, run_id, contracts_by_ref, scheme_id):
    """Build a per-step sink factory bound to one run's session."""

    def factory(step: LoadStepConfig) -> AutoExtractionSink | None:
        block = step.contract
        if block is None:
            return None
        contract = contracts_by_ref.get(_ref_key(block))
        effective = resolve_effective_policy(
            contract.extraction_policy if contract else None,
            block.extraction_policy,
        )
        if effective.trigger != "automatic":
            return None
        return AutoExtractionSink(
            session=session,
            scheme_id=scheme_id,
            source_uri=f"pipeline_run://{run_id}#{step.id}",
            policy=effective,
            concept_key_prefix=(contract.id if contract else "records"),
        )

    return factory


result = await run_dag(
    steps,
    pipeline_name="orders",
    run_id="abc123",
    contract_store=store,
    extraction_sink_factory=make_extraction_sink_factory(
        session=session,
        run_id="abc123",
        contracts_by_ref=contracts_by_ref,
        scheme_id=scheme_id,
    ),
)

The factory has three responsibilities the runner deliberately delegates: (a) resolving each step's effective extraction_policy (the canonical resolver is resolve_effective_policy from pycharter.semantic.extraction_policy); (b) returning None for steps that should not fire (off / manual policies, or steps with no contract); and © owning the database session lifecycle. Factory exceptions are logged and swallowed by the runner — auto-extraction is opportunistic and must never cause a real load to fail.

When extraction_sink_factory is omitted, no extraction occurs and run_dag behaves exactly as before. The default-off posture means every existing CLI / API / worker caller of the runner stays unchanged until it explicitly opts in.


Collaborative governance

The vocabulary layer includes a collaboration layer for teams that need structured change management on concept schemes and bindings:

  • Proposals — submit a proposed change to a concept definition
  • Reviews — accept, reject, or request changes on a proposal
  • Conflict resolution — detect and resolve competing proposals
  • Permissions — control who can propose, review, and approve changes per concept tier

These are primarily used through the REST API (see the semantic API routes at /api/v1/semantic/).


Install

Vocabulary support is included in the core package. RDF features require an extra:

pip install pycharter[semantic]

See also