Semantic Web and Web 3.0 · class 12 · demo

The competency queries

Every queries/*.rq of the project, run as queries.py runs them: the {{name}} placeholders filled with the example: of the header, over the graph plus the chain and the domain in their own named graphs. A saved query that comes back empty is the commonest way for an ontology to become decorative.

2 rows manufacturers-by-country.rq

How many manufacturers there are per country.

# How many manufacturers there are per country.
PREFIX onto: <https://data.csoares.me/onto#>
SELECT ?country (COUNT(DISTINCT ?f) AS ?manufacturers) WHERE {
  ?f a onto:Manufacturer ; onto:country ?country .
}
GROUP BY ?country
ORDER BY DESC(?manufacturers)
?country?manufacturers
country:PT5
country:ES1

Take away graph:suppliers-csv and the result changes.

3 rows manufacturers-in-two-sources.rq

Manufacturers that identity resolution linked across the two sources.

# Manufacturers that identity resolution linked across the two sources.
PREFIX schema: <https://schema.org/>
PREFIX owl:    <http://www.w3.org/2002/07/owl#>
SELECT ?catalogue ?csv ?name WHERE {
  ?catalogue owl:sameAs ?csv ; schema:name ?name .
}
ORDER BY ?name
?catalogue?csv?name
manufacturer:1manufacturer:501234567Acme Lda
manufacturer:2manufacturer:502345678Barros & Filhos, S.A.
manufacturer:3manufacturer:503456789Cerdeira Industrial

Take away graph:catalogue or graph:identity and the result changes.

3 rows products-by-manufacturer.rq country=PT

Products per manufacturer, filtered by country. The catalogue has the products but no country; the country comes from the suppliers' CSV and from the aligned external source. It only returns anything because the graph crossed the sources. Grouping is by catalogue entity and not by name: once identity is resolved, the same company has several forms of the name (one per source). The country placeholder (in double braces) is replaced before the query runs: by the service, after checking the value against the pattern above; by the verifier, with the example value.

# parameter: country ^[A-Z]{2}$
# example: country=PT
# Products per manufacturer, filtered by country. The catalogue has the products but no
# country; the country comes from the suppliers' CSV and from the aligned external source.
# It only returns anything because the graph crossed the sources.
# Grouping is by catalogue entity and not by name: once identity is resolved,
# the same company has several forms of the name (one per source).
PREFIX onto:   <https://data.csoares.me/onto#>
PREFIX schema: <https://schema.org/>
PREFIX owl:    <http://www.w3.org/2002/07/owl#>
# The country placeholder (in double braces) is replaced before the query
# runs: by the service, after checking the value against the pattern above;
# by the verifier, with the example value.
SELECT ?manufacturer (SAMPLE(?n) AS ?name) (COUNT(DISTINCT ?p) AS ?products) WHERE {
  ?p a onto:Product ; onto:manufacturedBy ?manufacturer .
  ?manufacturer (owl:sameAs*) ?source .
  ?source schema:name ?n ;
         onto:country <https://data.csoares.me/country/PT> .
}
GROUP BY ?manufacturer
ORDER BY DESC(?products)
?manufacturer?name?products
manufacturer:1Acme Lda2
manufacturer:2Barros & Filhos, S.A.2
manufacturer:3Cerdeira Industrial Unipessoal1

Take away graph:catalogue or graph:identity or graph:suppliers-csv and the result changes.

2 rows products-of-manufacturer.rq taxId=501234567

Products of a manufacturer identified by its TAXID. owl:sameAs* finds the TAXID on any of the URIs the company has (catalogue or CSV). In this data the catalogue carries the TAXID too, so the identity graph is not needed here: take it away and the answer is the same.

# parameter: taxId ^[0-9]{9}$
# example: taxId=501234567
# Products of a manufacturer identified by its TAXID. owl:sameAs* finds the TAXID on any of
# the URIs the company has (catalogue or CSV). In this data the catalogue carries the TAXID
# too, so the identity graph is not needed here: take it away and the answer is the same.
PREFIX onto:   <https://data.csoares.me/onto#>
PREFIX schema: <https://schema.org/>
PREFIX owl:    <http://www.w3.org/2002/07/owl#>
SELECT DISTINCT ?product ?name WHERE {
  ?f onto:taxId "501234567" .
  # owl:sameAs* is a property path: zero or more sameAs hops, so ?same is ?f
  # itself or any entity identity resolution linked to it.
  ?same (owl:sameAs*) ?f .
  ?product a onto:Product ; onto:manufacturedBy ?same ; schema:name ?name .
}
ORDER BY ?name
?product?name
product:100Bearing 6204
product:101Bearing 6205

Take away graph:catalogue and the result changes.

2 rows provenance-of-manufacturer.rq taxId=501234567

Which file or database this manufacturer description came from, and when.

# parameter: taxId ^[0-9]{9}$
# example: taxId=501234567
# Which file or database this manufacturer description came from, and when.
PREFIX onto: <https://data.csoares.me/onto#>
PREFIX prov: <http://www.w3.org/ns/prov#>
SELECT ?graph ?source ?at WHERE {
  GRAPH ?graph { ?f onto:taxId "501234567" }
  ?graph prov:wasDerivedFrom ?source ;
         prov:generatedAtTime ?at .
}
?graph?source?at
graph:suppliers-csvfile://sources/suppliers.csv2026-11-01T13:21:28+00:00
graph:cataloguesqlite://sources/catalogue.db2026-11-01T13:21:28+00:00

Take away default graph (provenance) or graph:catalogue or graph:suppliers-csv and the result changes.

3 rows suppliers-certificates.rq

The query that crosses the chain with the domain: for each manufacturer, the state of its supply certificate. Neither half answers this alone: the manufacturer name and the declared validity are in the domain graph; the block, the transaction and the revocation are in the chain graph. This crossing is what the defence wants. Syntax note: the slash is not allowed in the local part of a prefixed name, so the graphs appear with the IRI written out in full.

# The query that crosses the chain with the domain: for each manufacturer, the
# state of its supply certificate.
#
# Neither half answers this alone: the manufacturer name and the declared
# validity are in the domain graph; the block, the transaction and the
# revocation are in the chain graph. This crossing is what the defence wants.
#
# Syntax note: the slash is not allowed in the local part of a prefixed
# name, so the graphs appear with the IRI written out in full.

PREFIX onto:   <https://data.csoares.me/onto#>
PREFIX schema: <https://schema.org/>
PREFIX xsd:    <http://www.w3.org/2001/XMLSchema#>

SELECT ?manufacturer ?certificate ?status ?block WHERE {
  GRAPH <https://data.csoares.me/graph/domain> {
    ?f a onto:Manufacturer ;
       schema:name ?manufacturer ;
       onto:hasCertificate ?certificate .
  }
  # OPTIONAL: a certificate that was never anchored still appears, with
  # ?block and ?revoked unbound.
  OPTIONAL {
    GRAPH <https://data.csoares.me/graph/chain> {
      ?r a onto:OnChainRecord ;
         onto:document ?certificate ;
         onto:block ?block ;
         onto:revoked ?revoked .
    }
  }
  # BOUND tells "no match in the OPTIONAL" apart from a value.
  BIND (IF(!BOUND(?block), "not anchored",
        IF(?revoked, "revoked", "anchored")) AS ?status)
}
ORDER BY ?manufacturer
?manufacturer?certificate?status?block
Acme Ldacertificate:2026-001anchored8412991
Barros & Filhos, S.A.certificate:2026-002revoked8412991
Cerdeira Industrialcertificate:2026-003not anchoredunbound

Take away graph:chain or graph:domain and the result changes.