Article Meshup & Live Technical Demonstration

AI Ontology Bottom-Up: A Live Virtuoso Meshup

Emily Winks' Atlan "Context & Chaos" article argues that bottom-up ontology discovery answered where definitions come from, but was wrongly treated as an answer to how they should be represented. This collection rebuilds her own win-rate contradiction as SQL tables on a live Virtuoso instance, maps them through RDF Views onto an ontology reusing PROV-O and SKOS, adds her recommended temporal-validity fix and a SHACL constraint, and queries the identical claims three ways — all verified against a real deployment.

KG curated by the linked-data-skills RDBMS pathway and rdf-infographic-skill on behalf of Kingsley Uyi Idehen.

Synopsis

Discover Locally, Represent With What Already Exists

MeshupSource: Winks, 2026-09-04

“Bottom-up answered the question of where definitions should come from. We treated it as an answer to how they should be represented too.”

That is the article's central move, and it is correct as a diagnosis. A team mined 32,237 claims across four real systems — a cloud warehouse, a CRM, a BI tool, a metadata catalog — and reconciled them into 14 entities, 79 metrics, 61 properties, 35 relationships. The exercise surfaced a genuine contradiction: three systems compute “win rate” three different ways. But when it came time to formalize what had been discovered, the project reinvented representation from scratch instead of reaching for standards that already solve exactly this: no validity period on any claim, no axioms or constraints enforcing a single rule about anything, and reification thin enough that reviewer feedback can retroactively contaminate historical judgments. One ontologist told the author flatly that the output should not be called an ontology — she did not disagree.

This collection takes that same worked example and rebuilds a small, honest version of it — four sample claims, not 32,237 — as ordinary SQL tables on a live Virtuoso instance (demo.openlinksw.com), maps those tables through Virtuoso RDF Views onto an ontology whose vocabulary reuses PROV-O and SKOS instead of inventing new terms, adds exactly the “two dates and a rule for assignment” the article recommends via an ALTER TABLE plus a SHACL shape, and queries the identical claims three ways — plain SQL, SPARQL, and SPASQL — every step verified against the real deployment. It does not claim the article's scale. It claims mechanism parity: the gaps she names are closable today, with standards that already exist, on infrastructure already running.

The Article's Own Experiment

Three Gaps, Named By the Author Herself

The article grounds its argument in a real claims-mining exercise. This demo does not reproduce that scale — it reproduces the same representational shape, at illustrative size, so each gap below is closed against real, verifiable data rather than described only in prose.

32,237
across four systems, by the article's own team
4
Source systems
warehouse, CRM, BI tool, metadata catalog
14 / 79
Entities / Metrics
reconciled from the mined claims
61 / 35
Properties / Relationships
completing the reconciled shape
4
Sample claims (this demo)
deliberately small — mechanism parity, not scale parity

Gap 1 — No temporal validity

✅ Closed in this demo

An ALTER TABLE adds valid_from/valid_to to the already-loaded claim table, applied AFTER initial load to demonstrate it as an addition, not a redesign. The CRM win-rate claim is now marked superseded (2026-06-01 to 2026-06-30); the others are current.

Gap 2 — No axioms or constraints

✅ Closed in this demo

A SHACL NodeShape bounds confidence to [0,1] and requires validFrom to precede validTo — a real, checkable rule, not just documentation.

Gap 3 — Insufficient reification

⚠️ The article's claim

Claims carry source and confidence, but reviewer feedback can retroactively alter scoring weights, contaminating historical judgments about past business states.

✅ Made concrete in this demo

A reviewer_feedback row revises the CRM claim's confidence from 0.82 to 0.55 on 2026-07-01 — itself a claim about a claim, with the same missing-validity-period problem one level up. This demo names the gap honestly rather than silently fixing it: a full RDF-star-style reification of the review event is left as the next step, not claimed as already done.

Live Virtuoso Demonstration

SQL Schema, Ontology, and the RDF View That Connects Them

Everything below was executed against OpenLink Virtuoso 08.03.3335 at demo.openlinksw.com (SQL account kidehen, schema DB.kidehen; HTTP is unencrypted on this host). The same relational claims-mining shape the article describes is exposed as RDF through Virtuoso RDF Views — a zero-copy virtual graph, no ETL, no duplicate store. The underlying mapping can be deployed two ways, both shown below: as R2RML (a W3C standard, itself real RDF, and this demo's primary path) auto-compiled by Virtuoso into the Quad Map, or hand-authored directly as ALTER QUAD STORAGE, shown here as a valid alternative.

SQL schema and sample data

SQL

aio_source_system table (DDL)

CREATE TABLE aio_source_system (
  system_id   INTEGER NOT NULL PRIMARY KEY,
  system_name VARCHAR(64),
  system_type VARCHAR(32)
);
SQL

aio_business_entity table (DDL)

CREATE TABLE aio_business_entity (
  entity_id   INTEGER NOT NULL PRIMARY KEY,
  entity_name VARCHAR(64),
  entity_type VARCHAR(32)
);
SQL

aio_business_metric table (DDL)

CREATE TABLE aio_business_metric (
  metric_id   INTEGER NOT NULL PRIMARY KEY,
  metric_name VARCHAR(64)
);
SQL

aio_mined_claim table (DDL) — the reified claim

As first created: exactly the shape the article describes, and exactly missing the validity period it flags as absent.

CREATE TABLE aio_mined_claim (
  claim_id           INTEGER NOT NULL PRIMARY KEY,
  entity_id          INTEGER NOT NULL,
  metric_id          INTEGER NOT NULL,
  source_system_id   INTEGER NOT NULL,
  formula_expression VARCHAR(255),
  confidence         FLOAT,
  observed_at        DATETIME
);
SQL

aio_reviewer_feedback table (DDL) — Gap 3 made concrete

CREATE TABLE aio_reviewer_feedback (
  feedback_id    INTEGER NOT NULL PRIMARY KEY,
  claim_id       INTEGER NOT NULL,
  reviewer       VARCHAR(64),
  new_confidence FLOAT,
  reviewed_at    DATETIME
);
SQL

ALTER TABLE — adding validFrom/validTo after initial load

Applied AFTER initial load, deliberately, to demonstrate the article's own recommended fix as an addition. VERIFIED: both statements returned "Done".

ALTER TABLE aio_mined_claim ADD valid_from DATE;
ALTER TABLE aio_mined_claim ADD valid_to   DATE;
SQL

Sample data — reproducing the article's own win-rate contradiction

VERIFIED loaded. Row 1 (CRM) is deliberately superseded so the current-vs-historical distinction is visible in the data, not just asserted in prose.

INSERT INTO aio_source_system (system_id, system_name, system_type) VALUES
  (1, 'Cloud Warehouse', 'warehouse'),
  (2, 'CRM', 'crm'),
  (3, 'BI Tool', 'bi_tool'),
  (4, 'Metadata Catalog', 'catalog');

INSERT INTO aio_business_entity (entity_id, entity_name, entity_type) VALUES
  (1, 'Opportunity', 'entity'),
  (2, 'Deal', 'entity'),
  (3, 'Pipeline', 'entity'),
  (4, 'Customer', 'entity');

INSERT INTO aio_business_metric (metric_id, metric_name) VALUES
  (1, 'win_rate'),
  (2, 'pipeline_value');

INSERT INTO aio_mined_claim (claim_id, entity_id, metric_id, source_system_id, formula_expression, confidence, observed_at, valid_from, valid_to) VALUES
  (1, 1, 1, 2, 'closed_won / all closed opportunities', 0.82, '2026-06-01', '2026-06-01', '2026-06-30'),
  (2, 1, 1, 1, 'closed_won / closed opportunities excluding unqualified', 0.76, '2026-06-03', '2026-06-03', NULL),
  (3, 1, 1, 3, 'closed_won / new-business pipeline only', 0.65, '2026-06-05', '2026-06-05', NULL),
  (4, 3, 2, 4, 'sum(open opportunity amount)', 0.90, '2026-06-02', '2026-06-02', NULL);

INSERT INTO aio_reviewer_feedback (feedback_id, claim_id, reviewer, new_confidence, reviewed_at) VALUES
  (1, 1, 'data_steward_1', 0.55, '2026-07-01');
Turtle

Ontology TBox — reusing PROV-O and SKOS, adding a SHACL shape

VERIFIED loaded into named graph http://demo.openlinksw.com/schemas/aiontology at 85 triples. systemName/entityName/metricName each carry owl:equivalentProperty skos:prefLabel (authority-control reuse); fromSystem carries owl:equivalentProperty prov:wasDerivedFrom (provenance reuse, instead of a bespoke predicate). aio:MinedClaimShape is the axioms/constraints layer Gap 2 says was missing.

@prefix : <http://demo.openlinksw.com/schemas/aiontology/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

:SourceSystem a owl:Class ; rdfs:label "Source System"@en .
:BusinessEntity a owl:Class ; rdfs:label "Business Entity"@en .
:BusinessMetric a owl:Class ; rdfs:label "Business Metric"@en .
:MinedClaim a owl:Class ; rdfs:label "Mined Claim"@en .
:ReviewerFeedback a owl:Class ; rdfs:label "Reviewer Feedback"@en .

:systemName a owl:DatatypeProperty ; owl:equivalentProperty skos:prefLabel .
:entityName a owl:DatatypeProperty ; owl:equivalentProperty skos:prefLabel .
:metricName a owl:DatatypeProperty ; owl:equivalentProperty skos:prefLabel .
:fromSystem a owl:ObjectProperty ; owl:equivalentProperty prov:wasDerivedFrom .
:validFrom a owl:DatatypeProperty ; rdfs:range xsd:date .
:validTo   a owl:DatatypeProperty ; rdfs:range xsd:date .
:confidence a owl:DatatypeProperty ; rdfs:range xsd:float .

:MinedClaimShape a sh:NodeShape ;
    sh:targetClass :MinedClaim ;
    sh:property [ sh:path :confidence ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ] ;
    sh:property [ sh:path :validFrom ; sh:lessThan :validTo ] .

R2RML — the mapping document, now the PRIMARY deployment path

Superseding the hand-authored Quad Map below as the primary deployment path: an R2RML mapping document — W3C standard vocabulary, prefix rr: — is authored as ordinary Turtle (116 triples, 5 rr:TriplesMap instances), uploaded to a Virtuoso-fetchable WebDAV path, then Virtuoso's own DB.DBA.R2RML_GENERATE_LINKED_VIEW procedure auto-generates the same Quad Map previously typed by hand. Because R2RML is itself RDF, the mapping is asserted directly below as real, parseable triples in this document's own companion graph — not merely quoted as a code-block string — which is the whole point: a proprietary Quad Map DSL cannot be published as RDF, a W3C-standard mapping language can. Functional equivalence with the hand-authored mapping is CONFIRMED, not assumed (see the equivalence card below).

Turtle

aiontology-r2rml.ttl — the R2RML mapping document, asserted as real RDF

VERIFIED valid Turtle (116 triples via rdflib) before upload. Five rr:TriplesMap instances — SourceSystemMap, BusinessEntityMap, BusinessMetricMap, MinedClaimMap (3 rr:parentTriplesMap joins: aboutEntity/aboutMetric/fromSystem), ReviewerFeedbackMap (1 join: feedbackOn) — mapping the same five DB.kidehen tables to the same aio: ontology as the hand-authored Quad Map below. Each map subject carries an explicit a rr:TriplesMap type, required by Virtuoso's R2RML processor. The five rr:TriplesMap subjects are asserted for real in this document's companion RDF (Turtle/JSON-LD) — not just quoted here as text.

@prefix rr: <http://www.w3.org/ns/r2rml#> .
@prefix aio: <http://demo.openlinksw.com/schemas/aiontology/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<#SourceSystemMap> a rr:TriplesMap ;
    rr:logicalTable [ rr:tableName "DB.kidehen.aio_source_system" ] ;
    rr:subjectMap [
        rr:template "http://demo.openlinksw.com/aiontology/source_system/{system_id}#this" ;
        rr:class aio:SourceSystem ;
    ] ;
    rr:predicateObjectMap [ rr:predicate aio:systemName ; rr:objectMap [ rr:column "system_name" ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:systemType ; rr:objectMap [ rr:column "system_type" ] ] .

<#BusinessEntityMap> a rr:TriplesMap ;
    rr:logicalTable [ rr:tableName "DB.kidehen.aio_business_entity" ] ;
    rr:subjectMap [
        rr:template "http://demo.openlinksw.com/aiontology/business_entity/{entity_id}#this" ;
        rr:class aio:BusinessEntity ;
    ] ;
    rr:predicateObjectMap [ rr:predicate aio:entityName ; rr:objectMap [ rr:column "entity_name" ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:entityType ; rr:objectMap [ rr:column "entity_type" ] ] .

<#BusinessMetricMap> a rr:TriplesMap ;
    rr:logicalTable [ rr:tableName "DB.kidehen.aio_business_metric" ] ;
    rr:subjectMap [
        rr:template "http://demo.openlinksw.com/aiontology/business_metric/{metric_id}#this" ;
        rr:class aio:BusinessMetric ;
    ] ;
    rr:predicateObjectMap [ rr:predicate aio:metricName ; rr:objectMap [ rr:column "metric_name" ] ] .

<#MinedClaimMap> a rr:TriplesMap ;
    rr:logicalTable [ rr:tableName "DB.kidehen.aio_mined_claim" ] ;
    rr:subjectMap [
        rr:template "http://demo.openlinksw.com/aiontology/mined_claim/{claim_id}#this" ;
        rr:class aio:MinedClaim ;
    ] ;
    rr:predicateObjectMap [ rr:predicate aio:formulaExpression ; rr:objectMap [ rr:column "formula_expression" ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:confidence ; rr:objectMap [ rr:column "confidence" ; rr:datatype xsd:float ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:observedAt ; rr:objectMap [ rr:column "observed_at" ; rr:datatype xsd:dateTime ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:validFrom ; rr:objectMap [ rr:column "valid_from" ; rr:datatype xsd:date ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:validTo ; rr:objectMap [ rr:column "valid_to" ; rr:datatype xsd:date ] ] ;
    rr:predicateObjectMap [
        rr:predicate aio:aboutEntity ;
        rr:objectMap [ rr:parentTriplesMap <#BusinessEntityMap> ;
                       rr:joinCondition [ rr:child "entity_id" ; rr:parent "entity_id" ] ] ;
    ] ;
    rr:predicateObjectMap [
        rr:predicate aio:aboutMetric ;
        rr:objectMap [ rr:parentTriplesMap <#BusinessMetricMap> ;
                       rr:joinCondition [ rr:child "metric_id" ; rr:parent "metric_id" ] ] ;
    ] ;
    rr:predicateObjectMap [
        rr:predicate aio:fromSystem ;
        rr:objectMap [ rr:parentTriplesMap <#SourceSystemMap> ;
                       rr:joinCondition [ rr:child "source_system_id" ; rr:parent "system_id" ] ] ;
    ] .

<#ReviewerFeedbackMap> a rr:TriplesMap ;
    rr:logicalTable [ rr:tableName "DB.kidehen.aio_reviewer_feedback" ] ;
    rr:subjectMap [
        rr:template "http://demo.openlinksw.com/aiontology/reviewer_feedback/{feedback_id}#this" ;
        rr:class aio:ReviewerFeedback ;
    ] ;
    rr:predicateObjectMap [ rr:predicate aio:reviewer ; rr:objectMap [ rr:column "reviewer" ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:newConfidence ; rr:objectMap [ rr:column "new_confidence" ; rr:datatype xsd:float ] ] ;
    rr:predicateObjectMap [ rr:predicate aio:reviewedAt ; rr:objectMap [ rr:column "reviewed_at" ; rr:datatype xsd:dateTime ] ] ;
    rr:predicateObjectMap [
        rr:predicate aio:feedbackOn ;
        rr:objectMap [ rr:parentTriplesMap <#MinedClaimMap> ;
                       rr:joinCondition [ rr:child "claim_id" ; rr:parent "claim_id" ] ] ;
    ] .
Bash

Upload the R2RML document to a Virtuoso-fetchable WebDAV path

VERIFIED: HTTP 201. Uploads the R2RML Turtle document so Virtuoso's own procedure can subsequently fetch it via the internal dav: scheme (see the generation call below) rather than an anonymous public HTTP GET.

curl -X PUT -u "kidehen:<password>" -H "Content-Type: text/turtle" \
  --data-binary @aiontology-r2rml.ttl \
  http://demo.openlinksw.com/DAV/home/kidehen/r2rml/aiontology-r2rml.ttl
SQL

DB.DBA.R2RML_GENERATE_LINKED_VIEW — Virtuoso auto-generates the Quad Map from R2RML

VERIFIED: executed successfully, producing 20 auto-named sys:qm-* quad maps plus one urn:qm:* container, all added to virtrdf:DefaultQuadStorage, targeting the SAME graph the hand-authored mapping used (http://demo.openlinksw.com/aiontology#). Signature: R2RML_GENERATE_LINKED_VIEW(in source varchar, in destination_graph varchar, in graph_type int default 0, in clear_source_graph int default 1). source accepts file:, dav:, http:, https: schemes — dav: is used here (an internal-access scheme, not a public HTTP fetch, so no anonymous DAV read grant was needed). graph_type 0 = virtual graph (used here, matching the hand-authored view's own zero-copy semantics); 1 = physical. Doc reference (verified resolving via curl, HTTP 200): docs.openlinksw.com/virtuoso/r2rmlgenlviewisql.

DB.DBA.R2RML_GENERATE_LINKED_VIEW(
  'dav:/DAV/home/kidehen/r2rml/aiontology-r2rml.ttl',
  'http://demo.openlinksw.com/aiontology#',
  0
);
Note

Functional equivalence, confirmed not assumed

VERIFIED: re-running the Step 5 verification query and the SPARQL win-rate query against the R2RML-generated view returned byte-identical results to the hand-authored mapping's own version — 4 SourceSystem, 4 BusinessEntity, 4 MinedClaim, 2 BusinessMetric, 1 ReviewerFeedback; the same 3-row CRM/Cloud Warehouse/BI Tool contradiction with temporal validity attached. Equivalence is a demonstrated property of this deployment, not an assumption carried over from the hand-authored mapping's prior verification.

Alternative: Hand-Authored Quad Map (ALTER QUAD STORAGE)

SQL

Alternative: Hand-Authored Quad Map — CREATE IRI CLASS + ALTER QUAD STORAGE

VERIFIED EXECUTED: "26 RDF metadata manipulation operations done". This is a valid alternative to the R2RML-driven deployment above: the same mapping, declared directly as Virtuoso's native ALTER QUAD STORAGE syntax rather than generated from a portable R2RML document. Entity IRI templates are confirmed live via SPARQL, though NOT yet HTTP-dereferenceable — no URL rewrite rule was configured for this demo.

SPARQL CREATE IRI CLASS <aio_source_system_iri> "http://demo.openlinksw.com/aiontology/source_system/%d#this" (in system_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_business_entity_iri> "http://demo.openlinksw.com/aiontology/business_entity/%d#this" (in entity_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_business_metric_iri> "http://demo.openlinksw.com/aiontology/business_metric/%d#this" (in metric_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_mined_claim_iri> "http://demo.openlinksw.com/aiontology/mined_claim/%d#this" (in claim_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_reviewer_feedback_iri> "http://demo.openlinksw.com/aiontology/reviewer_feedback/%d#this" (in feedback_id integer not null) ;

-- All FROM tables MUST be qualifier.schema.table -- a bare table name fails
-- with a misleading "Alias <table> is not defined" error.
SPARQL
ALTER QUAD STORAGE virtrdf:DefaultQuadStorage
  FROM DB.kidehen.aio_source_system AS s
  FROM DB.kidehen.aio_business_entity AS e
  FROM DB.kidehen.aio_business_metric AS m
  FROM DB.kidehen.aio_mined_claim AS c
  FROM DB.kidehen.aio_reviewer_feedback AS f
{
  GRAPH <http://demo.openlinksw.com/aiontology#> {
    <aio_source_system_iri> (s.system_id) rdf:type <http://demo.openlinksw.com/schemas/aiontology/SourceSystem> as virtrdf:aio_map_sys_type ;
      <http://demo.openlinksw.com/schemas/aiontology/systemName> s.system_name as virtrdf:aio_map_sys_name ;
      <http://demo.openlinksw.com/schemas/aiontology/systemType> s.system_type as virtrdf:aio_map_sys_stype .
    <aio_mined_claim_iri> (c.claim_id) rdf:type <http://demo.openlinksw.com/schemas/aiontology/MinedClaim> as virtrdf:aio_map_claim_type ;
      <http://demo.openlinksw.com/schemas/aiontology/formulaExpression> c.formula_expression as virtrdf:aio_map_claim_formula ;
      <http://demo.openlinksw.com/schemas/aiontology/confidence> c.confidence as virtrdf:aio_map_claim_conf ;
      <http://demo.openlinksw.com/schemas/aiontology/validFrom> c.valid_from as virtrdf:aio_map_claim_vf ;
      <http://demo.openlinksw.com/schemas/aiontology/validTo> c.valid_to as virtrdf:aio_map_claim_vt ;
      <http://demo.openlinksw.com/schemas/aiontology/fromSystem> <aio_source_system_iri> (s.system_id)
        where ( ^{c.}^.source_system_id = ^{s.}^.system_id ) as virtrdf:aio_map_claim_sys .
    <aio_reviewer_feedback_iri> (f.feedback_id) rdf:type <http://demo.openlinksw.com/schemas/aiontology/ReviewerFeedback> as virtrdf:aio_map_fb_type ;
      <http://demo.openlinksw.com/schemas/aiontology/feedbackOn> <aio_mined_claim_iri> (c.claim_id)
        where ( ^{f.}^.claim_id = ^{c.}^.claim_id ) as virtrdf:aio_map_fb_claim .
    -- (aboutEntity/aboutMetric/BusinessEntity/BusinessMetric mappings omitted here for
    -- length; the full 26-predicate statement is in the consolidated batch script below
    -- and in the companion TTL's :aioQuadMapDeployment entity.)
  }
} ;
SPARQL

Step 5 verification — entity-type summary of the deployed RDF View

VERIFIED real output, all five mapped classes present. Re-run against the R2RML-generated view and confirmed byte-identical (both deployment paths target the same graph, http://demo.openlinksw.com/aiontology#):

Entity TypeSample EntityCount
SourceSystemsource_system/1#this4
BusinessEntitybusiness_entity/1#this4
MinedClaimmined_claim/1#this4
BusinessMetricbusiness_metric/1#this2
ReviewerFeedbackreviewer_feedback/1#this1
SPARQL SELECT ?type (SAMPLE(?entity) AS ?sampleEntity) (COUNT(?entity) AS ?entityCount)
FROM <http://demo.openlinksw.com/aiontology#>
WHERE { ?entity a ?type }
GROUP BY ?type ORDER BY DESC(?entityCount)
SQL

Consolidated batch script — aiontology.sql, copy-paste-ready

DDL + sample data + IRI classes + Quad Map + grants, in the order a fresh database needs them. The CREATE TABLE/ALTER TABLE/INSERT/GRANT portion is vanilla SQL and runs unmodified on effectively any SQL engine; the CREATE IRI CLASS/ALTER QUAD STORAGE portion is Virtuoso's own Linked Data Views syntax, VERIFIED on 08.03.3335, and the part most likely to need adjustment on a different version. The GRANT statements let this instance's public demo and vdb logins read the tables, so the live SQL/SPASQL links elsewhere in this document work for a visitor who authenticates as one of those instead of the owning account. Suggested filename aiontology.sql; suggested run command isql demo.openlinksw.com:21118 kidehen <password> < aiontology.sql (or paste into Virtuoso Conductor's Interactive SQL page). The ontology TBox above is loaded separately as Turtle, not part of this SQL batch. This script deploys the now-alternative hand-authored Quad Map path; see the R2RML batch script below for the separate, now-primary R2RML deployment script, kept deliberately distinct rather than folded into this one.

-- aiontology.sql -- run top to bottom against a fresh DB.kidehen schema
CREATE TABLE aio_source_system (system_id INTEGER NOT NULL PRIMARY KEY, system_name VARCHAR(64), system_type VARCHAR(32));
CREATE TABLE aio_business_entity (entity_id INTEGER NOT NULL PRIMARY KEY, entity_name VARCHAR(64), entity_type VARCHAR(32));
CREATE TABLE aio_business_metric (metric_id INTEGER NOT NULL PRIMARY KEY, metric_name VARCHAR(64));
CREATE TABLE aio_mined_claim (claim_id INTEGER NOT NULL PRIMARY KEY, entity_id INTEGER NOT NULL, metric_id INTEGER NOT NULL,
  source_system_id INTEGER NOT NULL, formula_expression VARCHAR(255), confidence FLOAT, observed_at DATETIME);
CREATE TABLE aio_reviewer_feedback (feedback_id INTEGER NOT NULL PRIMARY KEY, claim_id INTEGER NOT NULL, reviewer VARCHAR(64),
  new_confidence FLOAT, reviewed_at DATETIME);

ALTER TABLE aio_mined_claim ADD valid_from DATE;
ALTER TABLE aio_mined_claim ADD valid_to   DATE;

INSERT INTO aio_source_system VALUES (1,'Cloud Warehouse','warehouse'), (2,'CRM','crm'), (3,'BI Tool','bi_tool'), (4,'Metadata Catalog','catalog');
INSERT INTO aio_business_entity VALUES (1,'Opportunity','entity'), (2,'Deal','entity'), (3,'Pipeline','entity'), (4,'Customer','entity');
INSERT INTO aio_business_metric VALUES (1,'win_rate'), (2,'pipeline_value');
INSERT INTO aio_mined_claim VALUES
  (1,1,1,2,'closed_won / all closed opportunities',0.82,'2026-06-01','2026-06-01','2026-06-30'),
  (2,1,1,1,'closed_won / closed opportunities excluding unqualified',0.76,'2026-06-03','2026-06-03',NULL),
  (3,1,1,3,'closed_won / new-business pipeline only',0.65,'2026-06-05','2026-06-05',NULL),
  (4,3,2,4,'sum(open opportunity amount)',0.90,'2026-06-02','2026-06-02',NULL);
INSERT INTO aio_reviewer_feedback VALUES (1,1,'data_steward_1',0.55,'2026-07-01');

GRANT SELECT ON aio_source_system TO "demo";
GRANT SELECT ON aio_source_system TO "vdb";
GRANT SELECT ON aio_business_entity TO "demo";
GRANT SELECT ON aio_business_entity TO "vdb";
GRANT SELECT ON aio_business_metric TO "demo";
GRANT SELECT ON aio_business_metric TO "vdb";
GRANT SELECT ON aio_mined_claim TO "demo";
GRANT SELECT ON aio_mined_claim TO "vdb";
GRANT SELECT ON aio_reviewer_feedback TO "demo";
GRANT SELECT ON aio_reviewer_feedback TO "vdb";

SPARQL CREATE IRI CLASS <aio_source_system_iri> "http://demo.openlinksw.com/aiontology/source_system/%d#this" (in system_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_business_entity_iri> "http://demo.openlinksw.com/aiontology/business_entity/%d#this" (in entity_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_business_metric_iri> "http://demo.openlinksw.com/aiontology/business_metric/%d#this" (in metric_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_mined_claim_iri> "http://demo.openlinksw.com/aiontology/mined_claim/%d#this" (in claim_id integer not null) ;
SPARQL CREATE IRI CLASS <aio_reviewer_feedback_iri> "http://demo.openlinksw.com/aiontology/reviewer_feedback/%d#this" (in feedback_id integer not null) ;

-- Full ALTER QUAD STORAGE statement (26 mapping predicates, one per target
-- graph as required) is in the companion TTL's :aioQuadMapDeployment entity.
Bash

Consolidated batch script — aiontology-r2rml-deploy.sh, the R2RML deployment path end to end

A second, separate consolidated script for the R2RML deployment path — it does not replace aiontology.sql above, which remains the correct script for the hand-authored ALTER QUAD STORAGE path. Runs the same DDL/data/grants as that script's opening section (vanilla SQL, any engine), then uploads the R2RML mapping document via curl PUT, then calls DB.DBA.R2RML_GENERATE_LINKED_VIEW, with the RDF_VIEW_DROP_STMT_BY_GRAPH safety check commented in as the recommended first step on any redeploy against a non-empty target graph. Suggested filename aiontology-r2rml-deploy.sh; the SQL portions are intended to be piped through isql (isql demo.openlinksw.com:21118 kidehen <password>), the curl portion runs as shown from any shell.

#!/bin/sh
# aiontology-r2rml-deploy.sh -- R2RML deployment path, end to end
# Run the DDL/data/grants portion via isql first (identical to aiontology.sql's
# opening section above), then:

# 1. (Redeploy only) If the target graph already has ANY quad-map registered
#    against it, drop every one first:
#    SELECT RDF_VIEW_DROP_STMT_BY_GRAPH('http://demo.openlinksw.com/aiontology#');
#    -- run whatever it returns, then verify the graph is empty (COUNT(*) = 0).

# 2. Upload the R2RML mapping document (aiontology-r2rml.ttl) to a
#    Virtuoso-fetchable WebDAV path:
curl -X PUT -u "kidehen:<password>" -H "Content-Type: text/turtle" \
  --data-binary @aiontology-r2rml.ttl \
  http://demo.openlinksw.com/DAV/home/kidehen/r2rml/aiontology-r2rml.ttl

# 3. Ask Virtuoso to generate the Quad Map from it (run via isql):
#    DB.DBA.R2RML_GENERATE_LINKED_VIEW(
#      'dav:/DAV/home/kidehen/r2rml/aiontology-r2rml.ttl',
#      'http://demo.openlinksw.com/aiontology#',
#      0
#    );

# 4. Verify (see the Step 5 query and the SPARQL win-rate query below) --
#    results are byte-identical to the hand-authored Quad Map path.
Three Query Surfaces

The Same Claims, Queried Three Ways

Adopting the ontology layer does not mean abandoning the SQL access path a team already has. All three surfaces below were run live against the exact same claims.

SQL

1. Plain SQL — the win-rate contradiction via ordinary joins

The win-rate contradiction via ordinary joins across four tables — no RDF involved. Runnable live through the same SPASQL Query Builder tool used below for the SPASQL example, since it executes plain SQL just as readily as SPARQL-embedded SQL.

Verified4 rows returned, matching the sample data exactly.
Login requiredThe live link needs a demo.openlinksw.com SQL account. demo and vdb are both open for public use for exactly this purpose — username and password are the same value (demo/demo or vdb/vdb).
SELECT c.claim_id, e.entity_name, m.metric_name, s.system_name, c.formula_expression, c.confidence, c.valid_from, c.valid_to
FROM aio_mined_claim c, aio_business_entity e, aio_business_metric m, aio_source_system s
WHERE c.entity_id=e.entity_id AND c.metric_id=m.metric_id AND c.source_system_id=s.system_id
ORDER BY c.claim_id
SPARQL

2. SPARQL — the same contradiction, now with temporal validity attached

The article's own recommended fix, demonstrated live: the same contradiction, but every row now states the period it was true for. The WHERE clause never restricts by fromSystem, so it naturally returns the union of every source system's own world view of win_rate — three independent, disagreeing perspectives, side by side, none preferred over another. That union is exactly what Addendum B's Horn rule (below) takes as its body and reconciles down to one fact.

Verified3 rows, obtained via isql.
SystemFormulaConfidencevalidFromvalidTo
CRMclosed_won / all closed opportunities0.822026-06-012026-06-30
Cloud Warehouseclosed_won / closed opportunities excluding unqualified0.762026-06-03—
BI Toolclosed_won / new-business pipeline only0.652026-06-05—
Correction found in reviewThe live link below is not anonymously readable — this named graph was never granted public SPARQL read access, so an unauthenticated request 401s even though a plain default-graph SPARQL query on this same endpoint works anonymously. Log in with an account that can read this graph (e.g. the owning kidehen login) before running it.
PREFIX aio: <http://demo.openlinksw.com/schemas/aiontology/>
SELECT ?system ?formula ?confidence ?validFrom ?validTo
FROM <http://demo.openlinksw.com/aiontology#>
WHERE {
  ?claim a aio:MinedClaim ; aio:aboutMetric ?metric ; aio:formulaExpression ?formula ;
         aio:confidence ?confidence ; aio:fromSystem ?sys ; aio:validFrom ?validFrom .
  OPTIONAL { ?claim aio:validTo ?validTo }
  ?metric aio:metricName "win_rate" .
  ?sys aio:systemName ?system .
}
ORDER BY ?validFrom
SPASQL

3. SPASQL — SPARQL embedded in SQL, run via isql

SPARQL embedded in SQL, run via isql. SPASQL is a SQL-client feature (isql/ODBC/JDBC), not an HTTP SPARQL protocol call, so its live-execution surface is the SPASQL Query Builder web tool, not a sparql?query= endpoint link.

VerifiedSame three rows as query 2 (date columns not projected here), confirmed via isql.
Login requiredThe live link needs a demo.openlinksw.com SQL account. demo and vdb are both open for public use for exactly this purpose — username and password are the same value (demo/demo or vdb/vdb).
SELECT * FROM (
  SPARQL DEFINE input:default-graph-uri <http://demo.openlinksw.com/aiontology#>
  PREFIX aio: <http://demo.openlinksw.com/schemas/aiontology/>
  SELECT ?system ?formula ?confidence
  WHERE {
    ?claim a aio:MinedClaim ; aio:aboutMetric ?metric ; aio:formulaExpression ?formula ;
           aio:confidence ?confidence ; aio:fromSystem ?sys .
    ?metric aio:metricName "win_rate" .
    ?sys aio:systemName ?system .
  }
) AS win_rate_claims

CONSTRUCT as a Rules Language

SPARQL CONSTRUCT can function as a rules language, in the same sense Datalog is: a Horn rule has exactly one head atom and a body of conjoined literals. In SPARQL terms, the CONSTRUCT clause is the head and must be a single triple pattern; the WHERE clause is the body. Two worked examples follow, deliberately contrasted:

  • Addendum A is an ordinary bulk CONSTRUCT — six predicates in its head — and is not a Horn rule by this definition; it classifies all three disagreeing win_rate claims under one shared type without resolving their disagreement.
  • Addendum B is a genuine Horn rule: its head is exactly one triple, aio:currentEstimate, and its body reconciles the same three disagreeing values down to the one an explicit, stated policy prefers — the actual demonstration of harmonizing disparate data via custom inference.
SPARQL

Addendum A — CONSTRUCT deriving a shared classification across disparate sources

The same WHERE body as query 2 above, with the SELECT list replaced by a CONSTRUCT template whose head has six predicates per claim — not a Horn rule by the single-triple-head definition, shown here for contrast with the genuine Horn rule in Addendum B below. Every win_rate claim, regardless of which system computed it or by which formula, gets stamped with one shared aio:HarmonizedWinRateClaim type: a classification, not a reconciliation — all three disagreeing confidence values are preserved side by side, none is resolved.

Verified16 triples across the 3 win_rate claims, via isql — rendered here as prettified Turtle (text/x-html-nice-turtle, this document's convention for CONSTRUCT/DESCRIBE; SELECT results use text/x-html+tr).
Login requiredSame as the query above: the live link needs an account that can read this graph.
PREFIX aio: <http://demo.openlinksw.com/schemas/aiontology/>
CONSTRUCT {
  ?claim a aio:HarmonizedWinRateClaim ;
         aio:aboutMetric ?metric ;
         aio:fromSystem ?sys ;
         aio:confidence ?confidence ;
         aio:validFrom ?validFrom ;
         aio:validTo ?validTo .
}
FROM <http://demo.openlinksw.com/aiontology#>
WHERE {
  ?claim a aio:MinedClaim ; aio:aboutMetric ?metric ; aio:formulaExpression ?formula ;
         aio:confidence ?confidence ; aio:fromSystem ?sys ; aio:validFrom ?validFrom .
  OPTIONAL { ?claim aio:validTo ?validTo }
  ?metric aio:metricName "win_rate" .
}

Verified result, rendered as prettified Turtle:

@prefix aio: <http://demo.openlinksw.com/schemas/aiontology/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://demo.openlinksw.com/aiontology/mined_claim/1#this>
    a aio:HarmonizedWinRateClaim ;
    aio:aboutMetric <http://demo.openlinksw.com/aiontology/business_metric/1#this> ;
    aio:confidence 0.82 ;
    aio:fromSystem <http://demo.openlinksw.com/aiontology/source_system/2#this> ;
    aio:validFrom "2026-06-01"^^xsd:date ;
    aio:validTo "2026-06-30"^^xsd:date .

<http://demo.openlinksw.com/aiontology/mined_claim/2#this>
    a aio:HarmonizedWinRateClaim ;
    aio:aboutMetric <http://demo.openlinksw.com/aiontology/business_metric/1#this> ;
    aio:confidence 0.76 ;
    aio:fromSystem <http://demo.openlinksw.com/aiontology/source_system/1#this> ;
    aio:validFrom "2026-06-03"^^xsd:date .

<http://demo.openlinksw.com/aiontology/mined_claim/3#this>
    a aio:HarmonizedWinRateClaim ;
    aio:aboutMetric <http://demo.openlinksw.com/aiontology/business_metric/1#this> ;
    aio:confidence 0.65 ;
    aio:fromSystem <http://demo.openlinksw.com/aiontology/source_system/3#this> ;
    aio:validFrom "2026-06-05"^^xsd:date .
SPARQL

Addendum B — a genuine Horn rule: single-triple head reconciling disparate values to one

Plain-language summary

Three different systems calculated the same business number, "win rate," three different ways, and got three different results (82%, 76%, and 65%). One of those (82%) is old news — everyone agrees it no longer applies. But the other two (76% and 65%) are both still considered live and current, so there is not yet one obvious answer to "what is our win rate right now?" This rule settles that by applying one clear, spelled-out tie-breaker: whichever current number has the higher confidence wins. That picks 76%, so the system records just that one number as the answer — not all the competing numbers side by side, a single resolved figure. The tie-breaker is a deliberate choice, not the only one that could have been made ("trust the newest number" or "trust one system over the others" would work just as well) — but whichever rule is used, it has to be written down somewhere plainly, which is exactly the point this example makes.

A genuine Horn rule: the CONSTRUCT head is exactly one triple — ?metric aio:currentEstimate ?confidence — a single custom predicate minted directly on aio:BusinessMetric, contrasted with Addendum A's six-predicate, non-Horn-rule head above. The rule body (the WHERE clause) is, again, unrestricted by fromSystem: it matches over the union of every source system's own world view of win_rate (the same union query 2 above surfaces as three raw rows) — every currently-valid claim (OPTIONAL + FILTER(!bound(?vt)), since no validTo is bound), ordered by confidence descending, LIMIT 1 — an explicit, stated reconciliation policy: prefer the highest-confidence currently-valid claim. Two claims disagree going in (0.76 from Cloud Warehouse, 0.65 from BI Tool); the rule materializes exactly one head triple out, harmonizing the disagreement to the value the stated policy prefers.

This is what harmonization of disparate data via a custom inference rule actually means: not preserving every source's own figure under a new label (Addendum A), but a rule body resolving them to a single fact. The policy is a deliberate, visible choice — "prefer most recent observedAt" or "prefer a designated authoritative system" would be equally defensible and would need their own rule body — and that a policy must be stated at all is the article's own open/closed-world and authority-control point, made concrete.

Verified1 triple, via isql.
Login requiredSame as the other SPARQL examples: the live link needs an account that can read this graph.
PREFIX aio: <http://demo.openlinksw.com/schemas/aiontology/>
CONSTRUCT {
  ?metric aio:currentEstimate ?confidence .
}
FROM <http://demo.openlinksw.com/aiontology#>
WHERE {
  SELECT ?metric ?confidence
  WHERE {
    ?claim a aio:MinedClaim ; aio:aboutMetric ?metric ; aio:confidence ?confidence .
    OPTIONAL { ?claim aio:validTo ?vt }
    FILTER ( !bound(?vt) )
    ?metric aio:metricName "win_rate" .
  }
  ORDER BY DESC(?confidence)
  LIMIT 1
}

Verified result, rendered as prettified Turtle:

@prefix aio: <http://demo.openlinksw.com/schemas/aiontology/> .

<http://demo.openlinksw.com/aiontology/business_metric/1#this>
    aio:currentEstimate 0.76 .
SPARQL

Complement to Addendum B — the same rule body previewed as an ordinary SELECT row

The exact same rule body as Addendum B above, but SELECTed instead of CONSTRUCTed: (aio:currentEstimate AS ?relation) binds the predicate itself as a constant, so each output row reads subject/predicate/object — ?metric, ?relation, ?confidence — as a plain table, previewing the one triple the Horn rule would materialize before committing to it as RDF.

VerifiedVia isql and via authenticated curl against the live SPARQL endpoint — ?metric resolves fully to .../business_metric/1#this in both. The query itself has no defect.
Correction found in reviewClicking through to that IRI's own /describe/ page shows "No further information is available" for its properties, even fully authenticated — this is not a permissions error (a plain SPARQL DESCRIBE against the same entity, tested directly, correctly returns both its rdf:type and its aio:metricName triple). Two separate, verified site-tooling limitations, neither a defect in this RDF View: the page wraps every fresh visit in a non-bypassable "Open this link?" interstitial carrying no entity data, and even past that, its internal property-listing query does not surface what a direct SPARQL DESCRIBE does. The reliable way to inspect these entities remains the query surfaces documented in this section (SQL, SPARQL, SPASQL) — not that browsing page.
Login requiredSeparately, and still true: an unauthenticated request to the SELECT endpoint below returns a hard Permission denied: authentication required, since this named graph has not been granted anonymous SPARQL read access (DB.DBA.RDF_GRAPH_USER_PERMS_SET(graph, 'nobody', 1), the operator's to apply). Log in with an account able to read this graph before running it.
PREFIX aio: <http://demo.openlinksw.com/schemas/aiontology/>
SELECT ?metric ?relation ?confidence
FROM <http://demo.openlinksw.com/aiontology#>
WHERE {
  SELECT ?metric (aio:currentEstimate AS ?relation) ?confidence
  WHERE {
    ?claim a aio:MinedClaim ; aio:aboutMetric ?metric ; aio:confidence ?confidence .
    OPTIONAL { ?claim aio:validTo ?vt }
    FILTER ( !bound(?vt) )
    ?metric aio:metricName "win_rate" .
  }
  ORDER BY DESC(?confidence)
  LIMIT 1
}

Verified result:

metric                                                          relation                                                        confidence
http://demo.openlinksw.com/aiontology/business_metric/1#this    http://demo.openlinksw.com/schemas/aiontology/currentEstimate  0.76

Resolvable Identifiers

Two live DESCRIBE queries against real entity IRIs this RDF View and the Horn rule above produced, complementing the addendum's own SELECT/CONSTRUCT examples with genuinely clickable identifiers. Both return a real Concise Bounded Description live — unlike demo.openlinksw.com's own /describe/ browsing page (see the caveat on the complement card above), which wraps every fresh visit in a non-bypassable confirmation interstitial and, past that, does not reliably surface the same result a direct SPARQL DESCRIBE does.

SPARQL

Resolvable identifier 1 — a raw MinedClaim entity, showcasing RDF Views utility

DESCRIBE on the Cloud Warehouse win_rate claim — the one the Horn rule above picks as the reconciled winner — returns its full description live from the RDF View: rdf:type, aboutEntity, aboutMetric, confidence, formulaExpression, fromSystem, observedAt, validFrom. Every value comes straight from the underlying SQL row at query time — no ETL, no duplicate copy — exactly what an RDF View is for.

VerifiedReal result via authenticated curl.
Login requiredSame as the rest of this section: requires logging in with an account able to read this graph.
DEFINE sql:describe-mode "CBD"
DESCRIBE <http://demo.openlinksw.com/aiontology/mined_claim/2#this>
FROM <http://demo.openlinksw.com/aiontology#>

Verified result, rendered as prettified Turtle:

@prefix aio: <http://demo.openlinksw.com/schemas/aiontology/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://demo.openlinksw.com/aiontology/mined_claim/2#this>
    a aio:MinedClaim ;
    aio:aboutEntity <http://demo.openlinksw.com/aiontology/business_entity/1#this> ;
    aio:aboutMetric <http://demo.openlinksw.com/aiontology/business_metric/1#this> ;
    aio:confidence 0.76 ;
    aio:formulaExpression "closed_won / closed opportunities excluding unqualified" ;
    aio:fromSystem <http://demo.openlinksw.com/aiontology/source_system/1#this> ;
    aio:observedAt "2026-06-03T00:00:00"^^xsd:dateTime ;
    aio:validFrom "2026-06-03"^^xsd:date .
SPARQL

Resolvable identifier 2 — the reconciled BusinessMetric, showcasing the reconciliation basis

DESCRIBE on the same BusinessMetric Addendum B resolves to, spanning two graphs: the RDF View graph (live from SQL) and a second, physical graph where the Horn rule's own conclusion was actually materialized via INSERT ... WHERE, run once against the same rule body as Addendum B.

Discovery: RDF Views don't accept writesA plain SPARQL INSERT targeting the RDF-View graph directly reports "Done" but persists nothing — that graph is purely virtual, computed at query time, and does not accept writes. The reconciled fact had to be inserted into a separate physical graph instead, then combined at query time via a second FROM clause.

The result: one resolvable identifier whose description now carries both its live RDF-View-derived facts (rdf:type, aio:metricName) and the Horn rule's own materialized conclusion (aio:currentEstimate 0.76) side by side — RDF Views utility and the reconciliation basis, demonstrated at the same clickable identifier.

VerifiedReal result via authenticated curl.
Login requiredSame as the rest of this section: requires logging in with an account able to read this graph.
DEFINE sql:describe-mode "CBD"
DESCRIBE <http://demo.openlinksw.com/aiontology/business_metric/1#this>
FROM <http://demo.openlinksw.com/aiontology#>
FROM <http://demo.openlinksw.com/aiontology-derived#>

Verified result, rendered as prettified Turtle:

@prefix aio: <http://demo.openlinksw.com/schemas/aiontology/> .

<http://demo.openlinksw.com/aiontology/business_metric/1#this>
    a aio:BusinessMetric ;
    aio:currentEstimate 0.76 ;
    aio:metricName "win_rate" .
HowTo

Closing Three Representational Gaps, Step by Step

This path applies where systems of record already live in a SQL RDBMS setup — the common case, and the one this demo's own claims-mining sample data assumes. See the RDF-native HowTo below for the alternative when the source is already RDF/SPARQL-queryable, where the SQL-to-RDF-View machinery in steps 1 and 5–9 simply does not arise.

1

Model the claims-mining output as five SQL tables

Create aio_source_system, aio_business_entity, aio_business_metric, aio_mined_claim, and aio_reviewer_feedback — the exact shape the article describes: a claim ties an entity, a metric, a source system, a formula, and a confidence together, with a source but (at this point) no validity period.

2

Apply the article's own recommended fix: ALTER TABLE to add validFrom/validTo

After the initial load — deliberately, to demonstrate this as an addition rather than a redesign — ALTER TABLE aio_mined_claim ADD valid_from DATE; ADD valid_to DATE;. This is the "two dates and a rule for assignment" the article recommends.

3

Load sample data reproducing the win-rate contradiction

Insert four mined claims — three contradictory win_rate formulas from CRM, Cloud Warehouse, and BI Tool, plus one pipeline_value claim — and one reviewer_feedback row that retroactively revises the CRM claim's confidence, making Gap 3 concrete.

4

Declare an ontology that reuses SKOS, PROV-O, and SHACL instead of reinventing representation

Type the five tables as owl:Class terms; align systemName/entityName/metricName to skos:prefLabel and fromSystem to prov:wasDerivedFrom via owl:equivalentProperty; add a SHACL NodeShape bounding confidence to [0,1] and ordering validFrom before validTo.

5

Deploy the RDF View: CREATE IRI CLASS, then one ALTER QUAD STORAGE statement (now the alternative path)

Mint one IRI class per table, then map all five tables into a single ALTER QUAD STORAGE virtrdf:DefaultQuadStorage statement targeting one GRAPH — critically, with every FROM table name fully qualified as qualifier.schema.table, and all mappings for that graph issued together, since a second statement targeting the same graph replaces rather than adds to the first. This hand-authored route is now the ALTERNATIVE deployment path — see step 8 for the R2RML-based route this demo now uses as primary.

6

Verify the RDF View with a SAMPLE/COUNT/GROUP BY entity-type summary

Run SELECT ?type (SAMPLE(?entity) AS ?sampleEntity) (COUNT(?entity) AS ?entityCount) FROM <graph> WHERE { ?entity a ?type } GROUP BY ?type ORDER BY DESC(?entityCount) and confirm all five mapped classes appear with the expected counts.

7

Query the identical claims three ways: plain SQL, SPARQL, SPASQL

Run the same win-rate question as an ordinary relational join, as a SPARQL graph pattern over the RDF View (now carrying validFrom/validTo), and as SPASQL (SPARQL embedded in a SQL SELECT via isql) — proving the ontology layer is additive to, not a replacement for, whichever SQL access path a team already has.

8

Primary path: author the mapping as R2RML (real RDF), not a proprietary DSL

Write the same table-to-triple mapping as an R2RML Turtle document (5 rr:TriplesMap instances, W3C standard vocabulary) instead of hand-authoring ALTER QUAD STORAGE — explicitly type every map as a rr:TriplesMap (Virtuoso's parser does not infer TriplesMap-hood structurally on this version, stricter than the spec technically requires), upload it to a Virtuoso-fetchable WebDAV path, then let DB.DBA.R2RML_GENERATE_LINKED_VIEW generate the actual Quad Map for you. Because the mapping is R2RML, it is itself publishable RDF.

9

Redeploy safely: drop every existing quad-map container before regenerating

Before generating or regenerating a Quad Map against any graph that might already have one — hand-authored or R2RML-generated — call SELECT RDF_VIEW_DROP_STMT_BY_GRAPH('<graph>'), run whatever statements it returns, and confirm the graph is empty (COUNT(*) = 0) before calling R2RML_GENERATE_LINKED_VIEW (or ALTER QUAD STORAGE) again. Skipping this step silently doubles every triple: R2RML's auto-generated quad-map names never match a prior hand-authored name, so the old replace-by-same-name behavior never triggers, and both mappings serve results at once.

10

Resolve the disagreement itself: a SPARQL CONSTRUCT Horn rule with a single-triple head

Adding validFrom/validTo (step 2) is necessary but not sufficient: two systems' claims can still both be "currently valid" at the same time, exactly as they are here (0.76 from Cloud Warehouse, 0.65 from BI Tool). Closing that gap for real means writing a genuine Horn rule: a SPARQL CONSTRUCT whose head is exactly one triple — ?metric aio:currentEstimate ?confidence, one custom predicate, nothing else — and whose body (the WHERE clause) states an explicit tie-breaking policy: prefer the highest-confidence currently-valid claim (ORDER BY DESC(?confidence) LIMIT 1). Run over the same R2RML-generated RDF View, that body-to-head rule turns two disagreeing numbers into one materialized fact — not by hiding the disagreement, but by writing down, in the query itself, exactly how it gets settled. See Addendum B below for the full worked example, live-verified against demo.openlinksw.com.

Alternative HowTo

Closing the Same Three Gaps When the Source Is Already RDF, Not SQL

The alternative to the HowTo above, for systems of record that are already RDF/SPARQL-queryable rather than relational — a claims-mining pipeline that emits RDF directly, for instance. Every SQL-specific step disappears: no CREATE TABLE, no ALTER TABLE, no RDF View, no R2RML mapping, no relational-to-RDF verification step. What carries over unchanged is the actual point of the article and this demo: the same ontology (reusing SKOS, PROV-O, and SHACL), the same SHACL shape, and the same single-triple-head Horn rule — proving those are properties of the representation layer, not of any particular source system.

1

Assert the claims-mining output directly as RDF instances — no relational staging

Where step 1 of the SQL-first path creates five tables, here the same five classes (aio:SourceSystem, aio:BusinessEntity, aio:BusinessMetric, aio:MinedClaim, aio:ReviewerFeedback) are populated directly: a SPARQL INSERT DATA (or whatever the RDF-native pipeline's own write path is) asserts instances under the identical ontology terms from step 4 below. There is no relational schema to expose, so there is nothing for an RDF View or R2RML mapping to do.

2

Carry validFrom/validTo from day one — the gap the SQL path closes with a later ALTER never opens

The SQL-first path's step 2 is a deliberate ALTER TABLE performed after an initial load, to show temporal validity being retrofitted onto an existing schema. Here there is no schema migration to demonstrate: aio:validFrom/aio:validTo are simply part of the initial assertion, because there is no separate DDL step for them to be added to afterward.

3

Assert the identical win-rate contradiction, this time as one SPARQL INSERT DATA block

The same four mined claims — three contradictory win_rate formulas from CRM, Cloud Warehouse, and BI Tool, plus one pipeline_value claim — and the same reviewer-feedback claim retroactively revising the CRM claim's confidence, asserted directly into a physical graph rather than computed by an RDF View over SQL rows. Byte-identical instance data to the SQL-first path; a different origin for it.

4

Reuse the identical TBox: SKOS, PROV-O, SHACL — proving the ontology is source-agnostic

The exact same ontology terms as the SQL-first path's step 4: skos:prefLabel via owl:equivalentProperty on systemName/entityName/metricName, prov:wasDerivedFrom on fromSystem, and the aio:MinedClaimShape SHACL shape. Nothing about the ontology changes based on whether the instance data underneath is computed by an RDF View or asserted directly — which is the point: standards reuse is a property of the representation layer, not of the source system.

5

Validate directly against the SHACL shape — no RDF View correctness check needed

The SQL-first path's step 6 verifies that the RDF View faithfully exposes the underlying SQL rows. That check has no equivalent here: since the data is asserted rather than computed by a mapping, there is no view-versus-source parity to confirm. Run aio:MinedClaimShape directly against the graph instead, and confirm it validates.

6

Resolve the disagreement with the identical Horn rule

The exact same single-triple-head SPARQL CONSTRUCT as step 10 of the SQL-first path, unchanged: ?metric aio:currentEstimate ?confidence, with a body that prefers the highest-confidence currently-valid claim (ORDER BY DESC(?confidence) LIMIT 1). A Horn rule's WHERE-clause body only cares about which triples are present in the graph, never how they got there — so the reconciliation logic, not just the ontology, turns out to be source-agnostic too.

FAQ

Frequently Asked Questions

Bottom-up answered where definitions should come from; it was mistakenly treated as an answer to how they should be represented too. Discovering local business meaning must be bottom-up, but representing that meaning formally should reuse established standards for provenance, time, and constraints rather than reinvent them.

32,237 claims mined across four systems — a cloud warehouse, a CRM, a BI tool, and an internal metadata catalog — reconciled into 14 entities, 79 metrics, 61 properties, and 35 relationships.

Three systems compute the same named metric, win_rate, three different ways: closed-won over all closed opportunities (CRM), excluding unqualified deals (warehouse), or counting only new-business pipeline (BI tool). It matters because discovering the term "win rate" bottom-up does not by itself resolve what the term means — that still requires representing which formula was true, for whom, and when.

Every mined claim carried a source but no validity period. This demo closes the gap with an ALTER TABLE adding validFrom/validTo columns to an already-loaded aio_mined_claim table, then re-running the win-rate SPARQL query with both dates projected.

The article's output had entities, metrics, properties, and relationships, but nothing enforcing a single rule about any of them. This demo adds a SHACL NodeShape, aio:MinedClaimShape, constraining confidence to [0,1] and requiring validFrom to precede validTo.

Reviewer feedback can retroactively alter a claim's confidence, contaminating historical judgments. This demo loads one aio_reviewer_feedback row revising the CRM win-rate claim's confidence from 0.82 to 0.55 on 2026-07-01 — itself a claim about a claim, with the same missing-validity-period problem one level up.

aio:fromSystem carries owl:equivalentProperty prov:wasDerivedFrom — exactly the article's recommended path: discover the local term bottom-up, but align its formal representation to the W3C provenance standard rather than leaving it as an unaligned bespoke property.

aio:systemName, aio:entityName, and aio:metricName each carry owl:equivalentProperty skos:prefLabel — SKOS's authority-control vocabulary for preferred labels, reused rather than reinvented, the same move the article recommends for BARTOC-style controlled terminology.

A Quad Map Pattern is Virtuoso's native syntax for exposing relational tables as a virtual RDF graph, compiled back to SQL at query time — no ETL, no duplicate copy. It predates R2RML, the later W3C standard for the same declarative table-to-triple mapping idea, and expresses an equivalent mapping. The Quad Map for this demo is generated from an R2RML mapping document via Virtuoso's own DB.DBA.R2RML_GENERATE_LINKED_VIEW procedure, rather than hand-authored — the primary path here precisely because it lets the mapping itself be published as real, standard RDF instead of a proprietary-DSL code block.

Plain SQL, SPARQL (with temporal validity attached), and SPASQL (SPARQL embedded inside a SQL SELECT, run via isql/ODBC/JDBC). Showing all three demonstrates that adopting the ontology layer does not require abandoning the SQL access path already in use.

It was actually executed against demo.openlinksw.com (Virtuoso 08.03.3335): the DDL statements returned "Done", the Quad Map statement returned "26 RDF metadata manipulation operations done", the ontology graph counted 85 triples, the Step 5 verification query returned the expected five entity-type rows, and the SPARQL win-rate query returned the expected three rows with dates attached.

Much smaller, deliberately: 4 sample claims here versus the article's 32,237. This demo does not claim the article's scale — it claims mechanism parity, that the representational gaps the article names are closable today, with standards that already exist, on infrastructure already running.

Three reasons: portability (R2RML is a W3C standard understood by any R2RML-compliant processor, not just Virtuoso's own DSL); the mapping itself becomes publishable RDF — it can be asserted, queried, and cross-referenced in the same graph as everything else, which a proprietary Quad Map statement cannot; and Virtuoso does the DSL translation for you via DB.DBA.R2RML_GENERATE_LINKED_VIEW, verified to produce a functionally identical view — byte-identical entity counts and query results — to the hand-authored version.

The ten-step HowTo above assumes systems of record already live in a SQL RDBMS — the common case, and the one this demo's own sample data models. When the source is already RDF/SPARQL-queryable instead, the SQL-to-RDF-View machinery (CREATE TABLE, ALTER TABLE, the RDF View or R2RML mapping, and the view-versus-source verification step) simply does not apply. See the second HowTo, “Closing the Same Three Gaps When the Source Is Already RDF, Not SQL,” for that path: the same ontology, the same SHACL shape, and the same single-triple-head Horn rule carry over completely unchanged — only the six SQL-specific steps disappear.

Glossary

Terms

Bottom-up ontology discovery

Deriving business terms from what live systems actually contain, rather than a pre-designed top-down schema. Answers where definitions come from; not by itself how they should be represented.

OWL (Web Ontology Language)

The W3C standard for formal class/property axioms. This demo's ontology uses owl:Class, owl:DatatypeProperty, owl:ObjectProperty, and owl:equivalentProperty rather than an ad hoc schema.

SHACL (Shapes Constraint Language)

The W3C standard for validating RDF against shape constraints — the axioms/constraints layer Gap 2 says was missing. aio:MinedClaimShape bounds confidence and orders validFrom/validTo.

SKOS (Simple Knowledge Organization System)

The W3C standard for controlled vocabularies, including skos:prefLabel — reused here via owl:equivalentProperty on systemName/entityName/metricName instead of a bespoke label property.

Dublin Core

A long-standing, minimal metadata vocabulary (creator, date, source) named by the article as a standard claims-mining output should inherit rather than reinvent.

PROV-O

The W3C provenance ontology. aio:fromSystem carries owl:equivalentProperty prov:wasDerivedFrom, aligning a locally-discovered term to an inherited provenance standard.

Reification

Making a statement itself a first-class subject that can carry its own metadata — the RDF-star tradition the article says its claims-mining output only partially achieves.

Open-world assumption

RDF/OWL's default stance: absence of a stated fact is not evidence of its falsehood. SHACL constraints add closed-world-style validation on top of an otherwise open-world graph.

Named graph

A labeled, separately addressable subset of a quad store's triples. This demo uses two: one for the TBox, one for the RDF-View-mapped instance data.

BARTOC

The Basel Register of Thesauri, Ontologies & Classifications — named by the article as a place authority-controlled terminology already exists.

RDF View

Virtuoso's mechanism for exposing relational tables as a virtual RDF graph without copying data. This demo's entire instance graph is one.

R2RML

The W3C standard for mapping relational databases to RDF. Virtuoso's Quad Map Patterns predate it but express an equivalent mapping. Virtuoso's DB.DBA.R2RML_GENERATE_LINKED_VIEW procedure now consumes an R2RML document directly and auto-generates the equivalent Quad Map — the path this demo uses as primary. Because R2RML is itself RDF, unlike the proprietary Quad Map DSL, the mapping can be asserted directly in a graph rather than only quoted as text.

SPASQL

SPARQL embedded inside a SQL SELECT statement, executed via isql/ODBC/JDBC rather than the HTTP SPARQL protocol — this demo's third query surface.

Quad Map Pattern

Virtuoso's native syntax for declaring an RDF View, deployed via ALTER QUAD STORAGE virtrdf:DefaultQuadStorage. This session's live deployment mapped five tables in one statement.

Knowledge Graph Explorer 208 nodes · 500 links

Interactive graph visualization derived from the companion RDF. Click nodes to resolve, drag to explore. Graph data embedded from companion RDF at generation time.

AI Ontology Bottom-Up — Article, Gaps, and the Live Virtuoso Demonstration

Nodes: 0 Links: 0
Click SVG to activate zoom, click outside to release | Drag nodes to pin, double-click to unpin
Classes Properties Instances

Explore The Live Deployment Using SPARQL

Choose a query recipe, edit the SPARQL if needed, then open the encoded live query against demo.openlinksw.com — every recipe below targets the actual deployment described above, not a simulation.

Run live query

SELECT uses text/x-html+tr. DESCRIBE and CONSTRUCT use text/x-html-nice-turtle. This workbench targets http://demo.openlinksw.com/sparql directly — the same real graphs verified earlier in this document, not a copy awaiting upload.