← Back to demo

PMDCO Transform — Two INSERTs, One Output Pattern

How two SPARQL INSERTs bridge Catena-X SAMM RDF and Manufacturing-X AAS-ontology RDF into a shared PMDCO graph pattern into the same PMDCO graph pattern — and why that makes a schema-free cross-dataspace query possible.

1. The Problem: Two Incompatible Source Representations

After each dataspace pipeline loads its raw source data, the triplestore holds two structurally alien graphs:

  • urn:graph:catx-samm — SAMM aspect RDF, where properties are identified by Catena-X-specific IRIs such as mat:stressAtBreak and units use the SAMM unit vocabulary (urn:samm:…:megapascal). The staging graph also contains the SAMM aspect model (catx-samm-model.ttl), which declares each samm:Property, its samm:characteristic, and the samm-c:unit the INSERT reads to identify units and labels.
  • urn:graph:mfgx-aas — AAS-ontology RDF produced by py-aas-rdf, where properties are aas:Property individuals identified indirectly through a semanticId → keys → value chain pointing to IDTA IRI strings such as https://admin-shell.io/idta/…/TensileStrengthMean/1/0.

Neither graph knows about the other's vocabulary. A SPARQL query over both would need separate path patterns for every property — or a UNION for every dataspace. The PMDCO Transform eliminates that by writing both into the same structural pattern before any query runs.

2. The Output Pattern: Quality → Datum → QuantityValue

Every property from either source — whether it came in as a SAMM field name or an AAS semanticId string — is written into the target graph as the same four-node chain:

# material entity (BFO material entity)
ex:316-4401_material a pmd:PMD_0000000, obo:BFO_0000040 ;
  rdfs:label "Steel 316/4401" ;
  obo:RO_0000086  ex:316-4401_tensile_strength .   # has quality

# quality individual — class IRI from TTO or PMDCO
ex:316-4401_tensile_strength a tto:TTO_0000053 ;    # tensile strength
  obo:RO_0000052  ex:316-4401_material ;             # characteristic of
  obo:IAO_0000417 ex:316-4401_tensile_strength_svs . # is quality measured as datum

# OBI scalar value specification
ex:316-4401_tensile_strength_svs a obo:OBI_0001931 ;
  obo:OBI_0001938 ex:316-4401_tensile_strength_value . # has value specification

# QUDT QuantityValue — same predicate chain as CatX output
ex:316-4401_tensile_strength_value a qudt:QuantityValue ;
  qudt:unit         unit:MegaPA ;
  qudt:numericValue "632" .

The Catena-X INSERT produces exactly the same four-node chain for PA6GF30 in urn:graph:catx. Both pipelines converge on qudt:numericValue — the single predicate the cross-dataspace SELECT actually reads.

3. How Each INSERT Bridges Its Schema

Each INSERT uses VALUES tables to declare the schema-specific identity of each property and its PMDCO class. CATX_INSERT reads urn:graph:catx-samm (SAMM instance data + aspect model) and writes to urn:graph:catx. AAS2KG_INSERT reads urn:graph:mfgx-aas and writes to urn:graph:mfgx. Both produce the same PMDCO predicate chain.

For the Catena-X path the property is identified directly by its SAMM IRI:

# CatX path: match by SAMM property IRI  (unit: = https://qudt.org/vocab/unit/)
VALUES (?prop ?qualityClass ?qdtUnit) {
  ( mat:stressAtBreak       pmd:PMD_0000952  unit:MegaPA  )  # tensile strength
  ( mat:youngsModulus       pmd:PMD_0000618  unit:MegaPA  )  # elastic modulus
  ( mat:impactStrength      pmd:PMD_0000518  unit:J-PER-M2 ) # impact strength
  ( mat:meltingTemperature  pmd:PMD_0000851  unit:DEG_C   )  # melting point
}

For the Manufacturing-X path the property has no direct IRI — it is encoded as a string value inside an aas:Key node reachable via a three-hop chain (aas:Property → semanticId → keys → value). The INSERT navigates that chain and maps the string to the same TTO/PMDCO quality class:

# MfgX path: navigate semanticId chain, match by IRI string
GRAPH <urn:graph:mfgx-aas> {
  OPTIONAL {
    ?tensileP a aas:Property ;
      aasP:value ?tensileValue ;
      aasSM:semanticId ?tss .
    ?tss aasR:keys ?tk .
    ?tk aasK:value
      "https://admin-shell.io/idta/InspectionDocumentsOfSteelProducts/TensileStrengthMean/1/0" .
  }
}

Both paths write a tto:TTO_0000053 (tensile strength) into the target graph. The query never sees the source vocabulary — it only ever reads the shared PMDCO/TTO class IRI.

4. Why PMDCO and TTO Must Be Loaded

SPARQL alone can match class IRIs in VALUES bindings without the ontologies being present — so the INSERT technically executes without them. But the ontologies earn their keep in three ways:

  • Human-readable labels. tto:TTO_0000053 rdfs:label "tensile strength" exists in the TTO ontology file. If TTO is loaded into the triplestore, any downstream query can fetch labels via ?qualityClass rdfs:label ?label instead of hard-coding string mappings. In this demo the label annotation INSERT in Node C substitutes for that, but in a production endpoint with loaded ontologies, no annotation step is needed.
  • Class hierarchy and inference. PMDCO and TTO define rdfs:subClassOf chains (e.g. tensile strength is a mechanical property is a material quality). A SPARQL endpoint with RDFS inference enabled can then answer queries like "give me all mechanical properties" without listing every leaf class in a VALUES block.
  • Provenance and interoperability. Using the canonical TTO/PMDCO class IRIs (rather than local coin) means any other system that also imports TTO can federate with this triplestore without a mapping step — the shared vocabulary is the integration layer.
In this browser demo the ontologies are not loaded — Oxigraph WASM runs without inference. The VALUES tables in the INSERT and the label annotation INSERT in the PMDCO Transform step replicate what a loaded ontology would provide automatically in a production deployment.

5. The Payoff: A Single Query Path, No UNION

Because the INSERT wrote the same predicate chain into both urn:graph:catx and urn:graph:mfgx, the cross-dataspace SELECT can traverse both graphs with one path expression. SPARQL evaluates the path over all named graphs simultaneously — it finds the same pattern in both and returns results from both without any dataspace-specific branch.

# This one path works for both dataspaces
?component obo:BFO_0000051 ?mat .      # hasPart → material
?mat       obo:RO_0000086  ?quality .  # has quality
?quality   a              ?qualityType .
?quality   obo:IAO_0000417 ?datum .    # is measured as
?datum     obo:OBI_0001938 ?qv .       # has value specification
?qv        qudt:numericValue ?value .  # ← same predicate for both PA6GF30 and Steel 316

If the INSERT had not run — or had produced different predicate chains for the two dataspaces — the query would need a UNION branch for each source vocabulary, and would need to be rewritten every time a new dataspace joins. With the shared PMDCO pattern in place, adding a third dataspace means writing one new INSERT, not touching the query.

See the full cross-dataspace SELECT query →