Skip to content

Examples cannot be expanded: adopt prefixed terms and add value coercion to dprod-context.jsonld #246

Description

@jgeluk

Summary

Every JSON-LD example in examples/ references the remote context
https://www.omg.org/spec/DPROD/dprod-context.jsonld, and no example expands
into the RDF graph it is meant to denote.
Expanding any of them today yields
essentially the empty graph.

The examples are written in bare terms ("type": "DataProduct", "outputPort",
"title"), while the generated context is a bare prefix map that defines none of
them and declares no value coercion. Neither half is usable as it stands.

The fix adopted below — following @VladimirAlexiev's recommendation in #93 — is
to move the examples to prefixed terms and add the missing value coercion to
the context, rather than growing the context to define every bare term. The
adoption material presents the standalone context as what gives DPROD JSON its
semantics; this issue is about making that true.

What the generator produces

spec-generator/main.py:107-119 builds the context as a bare prefix map:

jsonld_context_ontology = {
        "@version": 1.1,
        "dprod": ontology_namespace_iri,
        "xsd": str(XSD),
        "owl": str(OWL),
        "dcat": str(DCAT),
        "dct": str(DCTERMS),
        "prov": str(PROV),
        "rdfs": str(RDFS),
        "rdf": str(RDF),
        "sh": str(SH),
        "linkedin": str(LINKEDIN)
}

and writes exactly that at spec-generator/main.py:147-150:

with open('dist/dprod-context.jsonld', mode='x', encoding='utf-8') as f:
    json.dump({"@context": jsonld_context_ontology}, f, indent=4)

That same dict is also passed as the compaction context for dist/dprod.jsonld
(main.py:143), where a prefix-only context is entirely appropriate. The defect
is reusing it as the application-facing context that the examples consume;
those are two different artifacts with two different jobs.

Why the examples break

examples/dprod-example.json is representative:

{
  "@context": "https://www.omg.org/spec/DPROD/dprod-context.jsonld",
  "id": "https://www.ekgf.org/data/data-product/permid-data-product",
  "type": "DataProduct",
  "title": "PermId Data Product",
  "dataProductOwner": "https://www.linkedin.com/in/olibage/",
  "outputPort": { "type": "DataService", "conformsTo": "https://swagger.io/specification/" }
}

Against the generated context, three independent things go wrong:

  1. id and type are undefined, so they are dropped. Every node in every
    example becomes a blank node and no rdf:type is ever emitted. (The fix is
    for the examples to use @id / @type, not for the context to alias them —
    see the Decision section.)

  2. The bare terms are undefined. DataProduct, outputPort, inputPort,
    isAccessServiceOf, isDistributionOf, endpointURL, securitySchemaType,
    dataProductLifecycleStatus, title, description, format, conformsTo
    are all dropped. (The fix is to prefix them in the examples.)

  3. No value coercion. This one is a genuine context defect and survives the
    move to prefixed terms: prefix expansion gives you the predicate IRI but
    never makes the value an IRI, so IRI-valued properties produce literals
    rather than resources:

    # actual
    [] dprod:dataProductOwner "https://www.linkedin.com/in/olibage/" .
    # intended
    :permid-data-product dprod:dataProductOwner <https://www.linkedin.com/in/olibage/> .

Net effect: expanding any DPROD example against the published context yields
essentially the empty graph.

The full set of bare terms used across examples/ is: action, assignee,
assigner, computedOn, conformsTo, dataProductLifecycleStatus,
dataProductOwner, datasetOwner, description, endPointDescription,
endpointURL, format, geographicalCoverage, id, inputPort,
isAccessServiceOf, isDistributionOf, isMeasurementOf, label,
leftOperand, operator, outputPort, permission, refinement,
rightOperand, securitySchemaType, title, type, uid, value — plus the
types DataProduct, DataService, Dataset, Distribution. None are defined.

A second, unused context

spec-generator/globals.py:40-52 defines json_ld_context, which does alias
id/type:

json_ld_context = {
    "@vocab": str(RDF),
    "dprod": ontology_namespace_iri,
    ...
    "id": "@id",
    "type": "@type"
}

It is dead code — nothing in main.py references it. It would also be wrong if
wired up, because "@vocab": rdf: expands DataProduct to
rdf:DataProduct rather than dprod:DataProduct. It should be either fixed and
used, or deleted, so there is one obvious context definition rather than two.

Decision: prefixed terms, per @VladimirAlexiev's recommendation in #93

Two approaches were considered:

(A) Define every bare term in the DPROD context — keep the examples as they
are ("type": "DataProduct", "outputPort": ...) and make the context define
every one of them. Rejected.

(B) Use prefixed terms in the examples"@type": "dprod:DataProduct",
"dprod:outputPort": ... — and keep the context small. Adopted.

@VladimirAlexiev argued for (B) in #93 from direct experience with GS1 EPCIS:

I strongly recommend to use prefixed terms like dprod:DataProduct, dprod:DataProductAgreement and NOT bare terms like DataProduct, DataProductAgreement

  • We did that in EPCIS, and as a result it's harder to use EPCIS together with
    other JSONLD contexts.
  • The EPCIS context is over-optimized, but that was necessary since EPCIS JSON
    doesn't have prefixes.
  • EPCIS+CBV are used pretty much alone (not mixed with other data), but I think
    DPROD will be mixed with other data on a regular basis

That last point is decisive for DPROD specifically. A data product description is
almost never a standalone document — it sits alongside DCAT catalogues, ODRL
policies, DQV measurements and organisation-specific vocabularies, several of
which already appear in our own examples. A bare-term context claims generic
names like title, format, action, target, source and value for
DPROD's own reading of them, and every consumer who merges DPROD JSON with
another context then has to fight those claims. EPCIS could afford this because
EPCIS JSON has no prefixes and is consumed alone; DPROD has neither excuse.

Approach (B) also degrades honestly. Under (A) a term nobody remembered to define
silently vanishes; under (B) an unprefixed or misprefixed term is visibly wrong.

What the context must contain

Under (B) the context stays close to what is generated today, plus two additions:

  1. Value coercion. Prefix expansion alone does not make a value an IRI.
    "dprod:dataProductOwner": "https://..." still yields a literal unless the
    context declares the coercion:

    "dprod:dataProductOwner": { "@id": "dprod:dataProductOwner", "@type": "@id" }

    This must be generated, not hand-maintained. dprod-ontology.ttl already
    declares 13 owl:ObjectProperty terms — dataProductOwner,
    lifecycleStatus, dataProductLifecycleStatus,
    informationSensitivityClassification, domain, inputPort, outputPort,
    inputDataset, outputDataset, isDistributionOf, isAccessServiceOf,
    protocol, securitySchemaType — so the coercion set derives mechanically
    from the graph and cannot drift. owl:DatatypeProperty terms such as
    purpose get no coercion, or @type from their declared range.

    Coercions are also needed for the reused IRI-valued terms the examples use:
    dct:conformsTo, dct:format, dcat:endpointURL,
    dcat:endpointDescription. Defining these is safe — they are namespaced
    names, not generic ones, so they cannot collide with another context's terms.

  2. Prefixes for every vocabulary the examples draw on. Currently missing
    odrl and dqv, both of which appear in examples today.

Deliberately not included: id/type aliases. Examples should use the
JSON-LD keywords @id and @type directly, which is the same argument one step
further — an alias for a keyword is a bare-term claim on the two most generic
names in the vocabulary.

What the examples must change

This is the larger half of the work and touches every file in examples/:
prefix every term, and replace id/type with @id/@type.

Doing so exposes terms that resolve to nothing in any vocabulary DPROD
references. Ignoring the ODRL terms (which need an odrl: prefix) these are:

term file(s) note
lifecycle sba-pool-rates, equity-trade DPROD defines dataProductLifecycleStatus / lifecycleStatus, not lifecycle
endPointDescription dprod-example.json capital P; presumably dcat:endpointDescription
schemaCompatiblity sba-pool-rates misspelt, and undefined even spelt correctly
environment, sql sba-pool-rates no corresponding DPROD term
datasetOwner equity-trade no corresponding DPROD term
geographicalCoverage sba-pool-rates presumably dct:spatial
computedOn, isMeasurementOf data-quality DQV terms, never prefixed
label data-quality presumably rdfs:label
value data-quality presumably dqv:value

Each needs a decision: map to an existing term, add it to the DPROD ontology, or
remove it from the example. Under approach (A) these would have been quietly
absorbed into the DPROD namespace and never questioned.

No @vocab

Unchanged by the decision above, and reinforced by it. @vocab expands any
undefined term by concatenation onto a fallback namespace, so outputProt
becomes dprod:outputProt — a well-formed IRI, a real triple, no error. With
prefixed terms an undefined term is instead plainly visible as such, which is
what surfaced the table above. @vocab would have hidden every row of it.

@vocab also fixes none of the defects in this issue: it cannot express
"@type": "@id", so IRI-valued properties would still yield literals. It only
makes omissions harder to detect, and the build-time check below depends on
omissions being detectable.

Acceptance criteria

  • Examples use prefixed terms throughout, and @id / @type rather than
    id / type.
  • Every term in every example resolves to a defined term in DPROD, DCAT,
    DCTERMS, ODRL, DQV or an explicitly declared example namespace. The table
    above is resolved rather than papered over.
  • dist/dprod-context.jsonld declares prefixes for every vocabulary the
    examples use, and "@type": "@id" on every IRI-valued property, generated
    from the ontology's owl:ObjectProperty declarations rather than
    hand-listed.
  • Expanding each file in examples/ against the generated context produces
    the intended triples — typed nodes with stable IRIs, and object properties
    pointing at resources rather than literals.
  • A build-time check expands the examples and fails on any undefined term,
    so context and examples cannot drift apart again (see validate all examples by converting them to turtle/trig #92).
  • globals.json_ld_context is either wired up correctly or removed.

Prior reports — this is not a new finding

This defect was reported by @VladimirAlexiev on 2024-09-25 in #93 and has been
open in substance ever since.
That issue states the problem exactly:

Your examples use classes like DataProduct, DataProductAgreement. But these
terms are not defined in the context, so no triples come out.

#93 was closed as completed on 2026-02-25 on the grounds that the generator now
emits a dedicated dist/dprod-context.jsonld. That closure was premature.
Splitting the context out of the ontology dump fixed the nesting problem
(#90, LOADING_REMOTE_CONTEXT_FAILED) but did not define a single term, which
was #93's actual complaint. The present issue is that unresolved remainder, and
credit for the diagnosis belongs to #93.

@VladimirAlexiev also demonstrated the consequence empirically in #92 (still
open, tracked as OMG DPROD-44):

  • no DPROD terms are present whatsoever
  • The second example produces no triples.

and again in #91 (still open), where the data-quality example reduces to no
NQuads at all in the JSON-LD Playground.

Related open reports of the same underlying cause:

On the design alternative

@VladimirAlexiev's prefixed-terms recommendation in #93 is no longer an open
question — it has been adopted as the approach for this issue. See the Decision
section above.

Related

#247 — two example files do not currently parse as JSON at all. That blocks the
round-trip expansion check in the acceptance criteria above, so it should land
first.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions