External Concept Mappings¶
An OBML model already is an ontology of sorts: every loaded model is exported as an OBSL RDF graph that says which measures exist, which columns they read and how the data objects join. What that graph cannot say on its own is what a measure means to the business. Is Revenue the same thing as the NetRevenue your finance glossary defines? A narrower notion of it? Merely related?
External concept mappings answer that. They are descriptive links from a model artefact to a concept in an ontology you do not own: a corporate business glossary, FIBO, schema.org, GoodRelations. They never change what OBSL compiles or executes.
Two ontologies, one source of truth¶
| OBML model ontology | External business ontology | |
|---|---|---|
| Owned by | your OBML file | a governance team, a standards body |
| Describes | tables, columns, joins, dimensions, measures, metrics and how they compile | business concepts and how they relate to each other |
| Generated by OBSL | yes, as the OBSL RDF graph, from OBML | never; OBSL only references it by IRI |
| Changes when | you edit the model | the ontology owners publish a new version |
OBML stays the single source of truth. The RDF graph is a projection of it, and a mapping is one more fact in that projection: "this artefact relates to that external IRI in this way". OBSL never fetches, imports or restates the external ontology, not at load time and not in the graph. A mapping to an IRI nobody has published yet is allowed; it is a statement about your model, not about the network.
Why mappings never affect SQL¶
A mapping is metadata in the same sense description or synonyms are. It is not read by the query resolver, the planner or the code generator: compiled SQL is byte-identical with and without mappings, and so are the join path, the metric expansion and the planner's warnings. This is deliberate, and it is tested.
Two things this does not mean. It does not make a mapping edit free for the result cache: a shared model id is a hash of the whole OBML document, and the cache key includes it, so changing a mapping is a model edit like any other and cached results are scoped to the new model version. And it does not make mappings a place for logic: if a business rule should change what a query returns, it belongs in a measure filter, a static filter or a metric expression.
Syntax¶
Prefixes¶
Declare the namespaces you map into once, at the top level:
version: 1.0
ontology:
prefixes:
corp: "https://ontology.example.com/business/"
fibo: "https://spec.edmcouncil.org/fibo/ontology/"
gr: "http://purl.org/goodrelations/v1#"
A prefix name is a conservative identifier (letters, digits, _, -, ., starting with a letter or _). A namespace is an absolute IRI. Five prefixes are built in and need no declaration: rdf, rdfs, owl, skos, xsd. You may repeat one of them verbatim, but binding a built-in name to a different namespace is rejected with INVALID_ONTOLOGY_PREFIX.
Mappings¶
The model itself, a data object, a dimension, a measure or a metric may carry an externalConceptMappings list:
measures:
Revenue:
aggregation: sum
expression: "{[Orders].[Amount]}"
externalConceptMappings:
- concept: "corp:NetRevenue" # compact IRI, expanded with ontology.prefixes
relation: exact # required
justification: curated # optional
source: "enterprise-finance-ontology"
ontologyVersion: "2026.1"
confidence: 1.0
comment: "Approved by Finance Data Governance"
- concept: "https://schema.org/MonetaryAmount" # a full IRI works too
relation: broader
| Property | Type | Required | Description |
|---|---|---|---|
concept |
string | Yes | The external concept, as a compact IRI (prefix:LocalName) or a full IRI (https://..., urn:...). See Compact versus full IRIs |
relation |
enum | Yes | exact, close, broader, narrower, related. There is no implicit exact. See Choosing the relation |
justification |
enum | No | How the mapping came to exist: curated (a human decision), imported (from another catalog or format), generated or inferred (by tooling), lexical (a name match only) |
source |
string | No | Where the mapping comes from: a catalog, ontology or process name |
ontologyVersion |
string | No | Version of the external ontology the concept was taken from |
confidence |
number | No | 0 to 1 |
comment |
string | No | Free text: approval, caveat, rationale |
The resolver expands every concept to an absolute IRI. The API returns it as expanded_iri; in the RDF graph it is the object of the direct skos:*Match triple on every mapping, and additionally the obsl:targetConcept of the provenance resource that only mappings with provenance get (see OBSL Graph & SPARQL). Query the SKOS predicates, not obsl:targetConcept, to see every mapping. The expanded form is derived, so authoring expandedIri in YAML is an UNKNOWN_PROPERTY error.
Compact versus full IRIs¶
A value is a full IRI when its scheme is followed by // (https://...) or when it is a URN (urn:...). Anything else of the form prefix:local is a compact IRI, and its prefix must be declared or built in. Treating every scheme: as absolute would let an undeclared prefix through silently, which is exactly the typo this rule exists to catch.
| Value | Reads as | Result |
|---|---|---|
corp:NetRevenue |
compact | expanded with corp |
https://schema.org/Product |
full | kept as written |
urn:isbn:0451450523 |
full | kept as written |
acme:Thing with no acme prefix |
compact | UNKNOWN_ONTOLOGY_PREFIX |
NetRevenue |
relative reference | INVALID_CONCEPT_IRI |
_:b0 |
blank node | INVALID_CONCEPT_IRI |
<https://x.example/A> |
angle-bracketed | INVALID_CONCEPT_IRI, write it without the brackets |
corp:Net Revenue |
contains whitespace | INVALID_CONCEPT_IRI |
Where mappings are allowed¶
Model, data object, dimension, measure, metric. Not columns or joins: a customer_id column identifies a Customer rather than being one, and a join relates two concepts rather than matching either. Both need a more precise relation vocabulary than SKOS matching offers, so they are deliberately out of scope until one exists. Putting externalConceptMappings on a column is an UNKNOWN_PROPERTY error.
Synthesized row-count measures (Sales Count and friends) cannot carry mappings either; they are never persisted. Map a declared measure instead.
Choosing the relation¶
The relation reads artefact first, in the SKOS direction: it describes the external concept as seen from your OBML artefact.
relation |
SKOS predicate | Means | Example |
|---|---|---|---|
exact |
skos:exactMatch |
the artefact and the concept mean the same thing | measure Net Revenue → corp:NetRevenue |
close |
skos:closeMatch |
similar enough to be used interchangeably in many applications | measure Revenue → schema:MonetaryAmount |
broader |
skos:broadMatch |
the external concept is the broader one; the artefact is a narrower notion of it | measure Net Revenue → corp:Revenue |
narrower |
skos:narrowMatch |
the external concept is the narrower one; the artefact covers more than it | data object Customers (retail and corporate) → corp:RetailCustomer |
related |
skos:relatedMatch |
associated, neither equivalent nor hierarchical | metric Return Rate → corp:CustomerSatisfaction |
The two hierarchical relations are the ones people get backwards, so once more with the same example:
measures:
Net Revenue:
externalConceptMappings:
- concept: corp:Revenue # Revenue in general
relation: broader # corp:Revenue is broader than Net Revenue
dataObjects:
Customers: # all customers, retail and corporate
externalConceptMappings:
- concept: corp:RetailCustomer
relation: narrower # corp:RetailCustomer is narrower than Customers
Pick broader when the glossary term is the umbrella, narrower when your model object is the umbrella.
Rules the resolver enforces¶
One IRI carries one relation per artefact. Two entries on the same artefact that expand to the same IRI are DUPLICATE_CONCEPT_MAPPING when they agree on the relation and CONFLICTING_CONCEPT_MAPPING when they do not; the later entry is dropped and the artefact still loads, so every problem is reported at once. The same IRI on different artefacts is fine, and often exactly the point: Revenue maps exact and Revenue Doubled maps narrower to the same corp:NetRevenue.
| Error code | Cause |
|---|---|
ONTOLOGY_PARSE_ERROR |
ontology or ontology.prefixes is not a mapping |
INVALID_ONTOLOGY_PREFIX |
prefix name is not an identifier, namespace is not an absolute IRI, or a built-in prefix is rebound |
UNKNOWN_ONTOLOGY_PREFIX |
a compact IRI uses a prefix that is neither declared nor built in (the message lists the known ones) |
INVALID_CONCEPT_IRI |
blank node, relative reference, angle brackets, forbidden characters, empty local name |
INVALID_CONCEPT_MAPPING |
an entry is not a mapping, relation is missing or unknown, justification unknown, confidence outside 0..1, or the list is not a list |
DUPLICATE_CONCEPT_MAPPING |
same expanded IRI and relation twice on one artefact |
CONFLICTING_CONCEPT_MAPPING |
same expanded IRI with two different relations on one artefact |
ONTOLOGY_PREFIX_CONFLICT |
an extends fragment or an inheriting child binds an already-bound prefix to a different namespace |
All of them carry the YAML source span. extends fragments and inheriting children may declare their own prefixes and model-level mappings; they are unioned into the merged model, and a fragment is held to the same strictness as a top-level document.
Reading mappings back¶
REST. The describe responses (schema, dimensions/{name}, measures/{name}, metrics/{name}) carry each artefact's mappings under external_concept_mappings, and schema adds ontology_prefixes. Three endpoints answer the cross-cutting questions, each with a top-level shortcut:
| Endpoint | Question |
|---|---|
GET .../concept-mappings?concept=corp:NetRevenue |
which artefacts map to this concept? (also namespace=, relation=, types=) |
GET .../concept-mappings/namespaces |
which external namespaces does the model link into, how much? |
GET .../concept-mappings/unmapped |
which artefacts still have no mapping? |
See the endpoint reference.
RDF and SPARQL. Every mapping is a direct skos:*Match triple from the artefact to the expanded IRI; a mapping with provenance also has an obsl:ExternalConceptMapping resource carrying it. The model's prefixes are bound on the graph. See OBSL Graph & SPARQL for a query.
OSI / Apache Ossie. ontology and every artefact's mappings survive the OBML → OSI → OBML round trip inside the ORIONBELT vendor extension of the corresponding OSI entity, since OSI has no native slot for them. See OSI Interoperability.
Guidance¶
Map what carries business identity. A measure that a KPI report is built on, a data object a glossary term names, a dimension that is the official classification. Mapping every artefact for completeness produces links nobody curated, which is worse than no link: prefer an unmapped report you can act on over lexical mappings you cannot trust.
Say how you know. justification, source and ontologyVersion are what let a reader, or an agent, decide how much weight a mapping deserves. A curated mapping with an approval comment is governance; a generated mapping at confidence: 0.6 is a suggestion.
Do not encode rules as mappings. "Orders over 10k are Strategic" is not a narrower mapping to corp:StrategicOrder; it is a rule with a threshold, and it will get its own OBML syntax. A mapping says what an artefact is, not when a row qualifies.
Keep one namespace per authority. If two teams publish overlapping glossaries, map to the one that owns the definition and record the other under source or comment rather than mapping both exact.
Correct:
measures:
Gross Margin:
externalConceptMappings:
- concept: corp:GrossMargin
relation: exact
justification: curated
source: finance-glossary
ontologyVersion: "2026.1"
Not what you want:
measures:
Gross Margin:
externalConceptMappings:
- concept: corp:GrossMargin
relation: exact
- concept: corp:Margin # broader, not a second exact
relation: exact
- concept: corp:HighMarginOrder # a rule, not a meaning
relation: narrower
dataObjects:
Orders:
columns:
Customer ID:
externalConceptMappings: # UNKNOWN_PROPERTY: columns identify, they do not mean
- concept: corp:Customer
relation: exact