@base           <https://www.databricks.com/blog/connecting-customer-context-measurable-roi-agentic-marketing> .
@prefix :       <https://www.databricks.com/blog/connecting-customer-context-measurable-roi-agentic-marketing#> .
@prefix schema: <http://schema.org/> .
@prefix xsd:    <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
@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 cdx:    <https://linkeddata.uriburner.com/DAV/demos/daas/ontology-terms#> .
@prefix ckg:    <http://demo.openlinksw.com/schemas/CustomerKG/> .


# ═══════════════════════════════════════════════════════════════════════════
# Document entity and provenance
# ═══════════════════════════════════════════════════════════════════════════

<> a schema:CreativeWork ;
    schema:name "Agentic Marketing: Databricks, Virtuoso, and Both Together"@en ;
    schema:description "RDF/HTML collection comparing the agentic-marketing approach in the Databricks blog post of 2026-10-01 with two OpenLink Virtuoso paths: a Virtuoso-native alternative with the agent-rdf-memory harness, and a complementary path that attaches Databricks tables to Virtuoso over ODBC and generates RDF Views over them. Evidenced by a purpose-built synthetic SQL schema, an RDF View with Linked Data rewrite rules and verified SQL, SPASQL and SPARQL queries running live on demo.openlinksw.com."@en ;
    schema:dateCreated "2026-10-02T22:12:12Z"^^xsd:dateTime ;
    schema:dateModified "2026-10-02T22:12:12Z"^^xsd:dateTime ;
    schema:author <https://www.linkedin.com/in/kidehen#this> ;
    schema:inLanguage "en"@en ;
    schema:about :article ;
    schema:hasPart :meshup .

:article a schema:Article ;
    schema:headline "Connecting customer context to measurable ROI with agentic marketing"@en ;
    schema:url <https://www.databricks.com/blog/connecting-customer-context-measurable-roi-agentic-marketing> ;
    schema:mainEntityOfPage <https://www.databricks.com/blog/connecting-customer-context-measurable-roi-agentic-marketing> ;
    schema:datePublished "2026-10-01"^^xsd:date ;
    schema:abstract "Agentic marketing uses AI agents grounded in trusted customer, business and decision context to recommend the next best action for each customer within guardrails marketers set. The post argues that identity resolution is the foundation, that agents need four kinds of context (customer, business, decision and control), that an agentic CDP such as Databricks CustomerLake turns that context into governed action, and that measurement belongs inside the decision loop, with incrementality as the shared language between marketing and finance."@en ;
    schema:author :elenaTesser, :alexandraHaefele ;
    schema:publisher <http://dbpedia.org/resource/Databricks> ;
    schema:keywords "agentic marketing"@en, "identity resolution"@en, "CustomerLake"@en, "incrementality"@en, "Unity Catalog"@en, "agentic CDP"@en ;
    schema:about <http://dbpedia.org/resource/Databricks>, <http://dbpedia.org/resource/Customer_data_platform>, :agenticMarketing, :identityResolution, :incrementality ;
    schema:mentions <http://dbpedia.org/resource/Acxiom>, <https://lovelytics.com/>, <http://dbpedia.org/resource/Marketing_mix_modeling>, <http://dbpedia.org/resource/Attribution_(marketing)>, :customerLake, :unityCatalog, :genie ;
    prov:wasGeneratedBy <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/kg-generator#this> .

:elenaTesser a schema:Person ;
    schema:name "Elena Tesser"@en ;
    schema:url <https://www.databricks.com/blog/author/elena-tesser> ;
    schema:worksFor <http://dbpedia.org/resource/Databricks> .

:alexandraHaefele a schema:Person ;
    schema:name "Alexandra Haefele"@en ;
    schema:url <https://www.databricks.com/blog/author/alexandra-haefele> ;
    schema:worksFor <http://dbpedia.org/resource/Databricks> .

<https://www.linkedin.com/in/jake-laduke#this> a schema:Person ;
    schema:name "Jake LaDuke"@en ;
    schema:jobTitle "Global GTM Lead for Media, Entertainment & Advertising"@en ;
    schema:worksFor <http://dbpedia.org/resource/Databricks> ;
    schema:description "Quoted in the article describing a shift from an impression currency to a prediction economy."@en ;
    schema:url <https://www.linkedin.com/in/jake-laduke-61a60587/> .

<https://www.linkedin.com/in/zacharyvandoren#this> a schema:Person ;
    schema:name "Zachary Van Doren"@en ;
    schema:jobTitle "SVP of Product Strategy and Ecosystem"@en ;
    schema:worksFor <http://dbpedia.org/resource/Acxiom> ;
    schema:description "Quoted in the article framing identity as powering the action."@en ;
    schema:url <https://www.linkedin.com/in/zacharyvandoren/> .

<https://www.linkedin.com/in/jaygoebel#this> a schema:Person ;
    schema:name "Jay Goebel"@en ;
    schema:jobTitle "Practice Lead for Communications and Media"@en ;
    schema:worksFor <https://lovelytics.com/> ;
    schema:description "Quoted in the article on the Databricks, Acxiom and Lovelytics division of labour as early CustomerLake launch partners."@en ;
    schema:url <https://www.linkedin.com/in/jaygoebel/> .

<https://www.linkedin.com/in/dedraberg#this> a schema:Person ;
    schema:name "Dedra Berg"@en ;
    schema:jobTitle "Value Realization Leader"@en ;
    schema:worksFor <https://lovelytics.com/> ;
    schema:description "Quoted in the article on marketing, sales and finance starting from the same trusted foundation."@en ;
    schema:url <https://www.linkedin.com/in/dedraberg/> .

<http://dbpedia.org/resource/Databricks> a schema:Organization ;
    schema:name "Databricks"@en ;
    schema:description "Vendor of the Databricks Data and AI platform, publisher of the source article and maker of CustomerLake, Unity Catalog and Genie."@en ;
    schema:url <https://www.databricks.com/> .

<http://dbpedia.org/resource/Acxiom> a schema:Organization ;
    schema:name "Acxiom"@en ;
    schema:description "Identity and data company whose Real ID identity resolution engine, per the article, now runs natively within Databricks and underpins CustomerLake's Profile Agents."@en ;
    schema:url <https://www.acxiom.com/> .

<https://lovelytics.com/> a schema:Organization ;
    schema:name "Lovelytics"@en ;
    schema:description "Services partner that, per the article, worked with Acxiom to rebuild identity services as a native application layer on Databricks and is an early CustomerLake launch partner."@en ;
    schema:url <https://lovelytics.com/> .

<http://dbpedia.org/resource/Virtuoso_Universal_Server> a schema:SoftwareApplication ;
    schema:name "Virtuoso Universal Server"@en ;
    schema:description "OpenLink Software's multi-model database engine providing SQL, SPARQL, RDF and GraphQL, RDF Views over relational data, attached remote tables over ODBC and JDBC, RDFS/OWL inference and replication."@en ;
    schema:url <https://virtuoso.openlinksw.com/> .


# ═══════════════════════════════════════════════════════════════════════════
# Locally minted vocabulary — characterised and cross-referenced
# ═══════════════════════════════════════════════════════════════════════════

:vocabulary a owl:Ontology ;
    schema:name "Meshup Comparison Vocabulary"@en ;
    schema:description "The five properties minted by this document to express a two-sided architectural comparison between the Databricks agentic-marketing approach and its Virtuoso counterparts. The ComparisonDimension class is reused from the corpus registry via the cdx: prefix rather than re-minted."@en ;
    rdfs:comment "Properties only; the class comes from entities/ontology-terms.ttl."@en ;
    owl:versionInfo "1.0"@en .

:databricksPosition a owl:DatatypeProperty ;
    rdfs:label "Databricks position"@en ;
    rdfs:comment "How the Databricks approach described in the source article handles one axis of comparison."@en ;
    rdfs:domain cdx:ComparisonDimension ;
    rdfs:range xsd:string ;
    rdfs:isDefinedBy :vocabulary ;
    skos:closeMatch schema:description .

:virtuosoPosition a owl:DatatypeProperty ;
    rdfs:label "Virtuoso position"@en ;
    rdfs:comment "How the OpenLink Virtuoso stack, with the agent-rdf-memory harness deployed, handles the same axis."@en ;
    rdfs:domain cdx:ComparisonDimension ;
    rdfs:range xsd:string ;
    rdfs:isDefinedBy :vocabulary ;
    skos:closeMatch schema:description .

:assessment a owl:DatatypeProperty ;
    rdfs:label "assessment"@en ;
    rdfs:comment "The analytical verdict on one axis: what the difference costs or buys, not a verdict on either platform."@en ;
    rdfs:domain cdx:ComparisonDimension ;
    rdfs:range xsd:string ;
    rdfs:isDefinedBy :vocabulary ;
    skos:closeMatch schema:reviewBody .

:favours a owl:DatatypeProperty ;
    rdfs:label "favours"@en ;
    rdfs:comment "Which side the assessment leans towards on this axis: Databricks, Virtuoso or Even."@en ;
    rdfs:domain cdx:ComparisonDimension ;
    rdfs:range xsd:string ;
    rdfs:isDefinedBy :vocabulary ;
    skos:closeMatch schema:ratingValue .

:evidence a owl:DatatypeProperty ;
    rdfs:label "evidence"@en ;
    rdfs:comment "What the lean rests on: demonstrated on this page, documented by a vendor, or stated in the article."@en ;
    rdfs:domain cdx:ComparisonDimension ;
    rdfs:range xsd:string ;
    rdfs:isDefinedBy :vocabulary ;
    skos:closeMatch schema:citation .


# ═══════════════════════════════════════════════════════════════════════════
# The meshup article
# ═══════════════════════════════════════════════════════════════════════════

:meshup a schema:Article ;
    schema:headline "Same Decision Loop, Three Paths: Databricks, Virtuoso with agent-rdf-memory, and the Two Together over ODBC"@en ;
    schema:abstract "The Databricks post describes a loop: resolve identity, assemble four kinds of context, decide within guardrails, act, measure incrementality, feed the result back. This page runs that loop three ways. Databricks does it natively in CustomerLake. Virtuoso does it as linked, resolvable, queryable data, with the agent-rdf-memory harness keeping the agent's own standing rules and session memory in the same store. And the two combine: Databricks tables stay where they are, Virtuoso attaches them over ODBC and projects RDF Views over them. A synthetic estate on demo.openlinksw.com proves the Virtuoso mechanics with SQL, SPASQL and SPARQL that ran before they were written down."@en ;
    schema:author <https://www.linkedin.com/in/kidehen#this> ;
    schema:datePublished "2026-10-02"^^xsd:date ;
    schema:hasPart :introSection, :articleSection, :pathsSection, :comparisonSection, :liveDemoSection, :odbcSection, :agentMemorySection, :faqPage, :glossarySet, :howto, :vocabulary ;
    prov:wasGeneratedBy <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/kg-generator#this>,
        <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/rdf-infographic-skill#this> .

:introSection a schema:WebPageElement ;
    schema:name "A Decision Loop, Not a Dashboard"@en ;
    schema:position 1 ;
    schema:text "The article's closing sequence is a loop: define the decision, assemble the customer and business context, set guardrails and approval points, agree in advance how incremental value will be measured, then use what you learn to improve the next decision. This page takes that loop as its spine and asks what each part needs from a data layer: identity that resolves, context that carries meaning, decisions that remember why, controls that can be audited, and outcomes that can be compared against doing nothing."@en .

:articleSection a schema:WebPageElement ;
    schema:name "What the Article Says"@en ;
    schema:position 2 ;
    schema:text "Seven sections. Identity resolution is the foundation, maintained continuously rather than resolved once, and Acxiom with Lovelytics rebuilt identity services as a native layer on Databricks, citing roughly 30 percent faster time to actionable insight and about 15 percent lower operating cost. Agents need four kinds of context: customer, business, decision and control. A lapsed quick-service-restaurant customer shows five considerations: decision, context, economic judgment, action and guardrails, and measurement. CustomerLake is an agentic CDP embedded in the lakehouse, governed by Unity Catalog, with Profile Agents and Campaign Agents and Genie for natural-language audience building. Measurement is incrementality, and CMOs need to show five things. Start with one meaningful decision."@en .

:pathsSection a schema:WebPageElement ;
    schema:name "Three Paths Through the Same Loop"@en ;
    schema:position 3 ;
    schema:text "Path A is Databricks-native: CustomerLake in the lakehouse under Unity Catalog. Path B is Virtuoso-native: the same context modelled as an RDF View over SQL tables or as triples in the quad store, with entity IRIs that resolve and the agent-rdf-memory harness holding the agent's standing rules and session memory. Path C is complementary: Databricks tables remain the system of record, Virtuoso attaches them over ODBC and generates RDF Views, rewrite rules and an ontology over them, so the Databricks estate becomes a resolvable knowledge graph without a copy."@en .

:comparisonSection a schema:WebPageElement ;
    schema:name "Thirteen Axes Where the Approaches Diverge"@en ;
    schema:position 4 ;
    schema:text "A head-to-head of the Databricks approach against the deployed Virtuoso stack, meaning Virtuoso with the agent-rdf-memory harness and its skills, on thirteen dimensions, each ranked on the evidence for both sides and labelled as demonstrated on this page, documented by OpenLink, stated without independent checking, or taken from the article. No axis leans towards Databricks on this evidence, eleven lean towards Virtuoso and two are genuinely even, the last being the combination of the two."@en .

:liveDemoSection a schema:WebPageElement ;
    schema:name "CustomerKG — The Decision Loop, Running"@en ;
    schema:position 5 ;
    schema:text "Twelve SQL tables modelling a quick-service-restaurant estate of customers, identity signals, consent, channels, offers, guardrails, campaigns, decisions, context items and outcomes were created on demo.openlinksw.com. One ALTER QUAD STORAGE statement projects them as RDF at query time, URL rewrite rules make every entity IRI resolve, and every query shown was executed against the live deployment before it was written down."@en .

:odbcSection a schema:WebPageElement ;
    schema:name "The Complementary Path: Databricks Tables Attached over ODBC"@en ;
    schema:position 6 ;
    schema:text "Virtuoso's virtual database attaches remote tables over ODBC so that SQL, SPARQL and RDF Views treat them like local ones. Demo already has a databricks_odbc data source with attached Databricks sample tables; this page reads its metadata, asks the RDF View generator for a view, an ontology and rewrite rules over one of those tables, and shows the output without executing it, because executing it would publish the remote table's columns, including a card number column, as predicates."@en .

:agentMemorySection a schema:WebPageElement ;
    schema:name "The Harness Layer: agent-rdf-memory"@en ;
    schema:position 7 ;
    schema:text "The article's Control layer is the intent, guardrails, permissions and approval points that agents must respect. The agent-rdf-memory harness applies the same idea to the coding agent that built this page: standing preferences are schema:HowToStep entities, retrieval is ontology-routed, and every memory document is its own named graph in the same Virtuoso store that serves the CustomerKG view."@en .


# ═══════════════════════════════════════════════════════════════════════════
# Product and concept entities
# ═══════════════════════════════════════════════════════════════════════════

:customerLake a schema:SoftwareApplication ;
    schema:name "Databricks CustomerLake"@en ;
    schema:description "The Agentic CDP from Databricks. Per the article it is embedded in the Databricks lakehouse, governed by Unity Catalog, organised around Profile Agents and Campaign Agents, and powers always-on engagement loops called Infinity Campaigns. It launched with an open partner ecosystem including Acxiom."@en ;
    schema:applicationCategory "Customer data platform"@en ;
    schema:url <https://www.databricks.com/product/customerlake-cdp> ;
    schema:creator <http://dbpedia.org/resource/Databricks> .

:unityCatalog a schema:SoftwareApplication ;
    schema:name "Databricks Unity Catalog"@en ;
    schema:description "The governance layer that the article says governs CustomerLake, so marketing engagement and personalization operate on the same data and AI foundation the rest of the business relies on."@en ;
    schema:url <https://www.databricks.com/product/unity-catalog> ;
    schema:creator <http://dbpedia.org/resource/Databricks> .

:genie a schema:SoftwareApplication ;
    schema:name "Databricks Genie"@en ;
    schema:description "The article's natural-language interface through which marketers explore customer context and build audiences without waiting for custom data pulls."@en ;
    schema:url <https://www.databricks.com/product/genie/one> ;
    schema:creator <http://dbpedia.org/resource/Databricks> .

:profileAgents a schema:DefinedTerm ;
    schema:name "Profile Agents"@en ;
    schema:description "CustomerLake agents that convert fragmented customer data into business-ready Customer 360 profiles, using Agentic Identity Resolution that blends deterministic, probabilistic and agentic matching with Acxiom's graph-based identity capabilities."@en ;
    schema:inDefinedTermSet :glossarySet .

:campaignAgents a schema:DefinedTerm ;
    schema:name "Campaign Agents"@en ;
    schema:description "CustomerLake agents that use governed customer context to build audiences, recommend next-best actions, activate across channels and continuously optimise around business goals."@en ;
    schema:inDefinedTermSet :glossarySet .

:virtuosoEngine a schema:SoftwareApplication ;
    schema:name "Virtuoso Universal Server"@en ;
    schema:description "OpenLink Software's multi-model database engine: SQL, SPARQL, RDF, GraphQL, RDF Views over relational data, attached ODBC and JDBC tables, RDFS/OWL inference and replication."@en ;
    schema:url <https://virtuoso.openlinksw.com/> ;
    owl:sameAs <http://dbpedia.org/resource/Virtuoso_Universal_Server> .

:agentRdfMemoryHarness a schema:SoftwareApplication ;
    schema:name "agent-rdf-memory"@en ;
    schema:description "Queryable RDF-Turtle behavioural-contract and memory harness for AI coding agents: identity, standing preferences as HowToSteps, ontology-routed context selection, per-document named graphs and session memory."@en ;
    schema:url <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/agent-rdf-memory> ;
    owl:sameAs <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/agent-rdf-memory#this> .

:demoServer a schema:SoftwareApplication ;
    schema:name "demo.openlinksw.com"@en ;
    schema:description "OpenLink's public Virtuoso demonstration server: SQL, SPARQL endpoint, SPASQL Query Builder and XMLA endpoint used to host the CustomerKG estate."@en ;
    schema:url <https://demo.openlinksw.com/> .

:uriburnerServer a schema:SoftwareApplication ;
    schema:name "URIBurner"@en ;
    schema:description "OpenLink's public Virtuoso-backed Linked Data service, used here for entity description pages and the DAV folder that hosts this collection."@en ;
    schema:url <https://linkeddata.uriburner.com/> .

:customerKgDataset a schema:Dataset ;
    schema:name "CustomerKG SQL estate"@en ;
    schema:description "Twelve normalised, foreign-key-constrained tables under CustomerKG.kidehen holding 228 synthetic rows: 5 channels, 5 products, 20 customers, 36 identity signals, 38 consent records, 5 offers, 5 guardrails, 2 campaigns, 20 decisions, 52 decision-to-guardrail links, 20 context items and 20 outcomes. The data is synthetic and illustrative; every name is invented and it is not Databricks data."@en ;
    schema:url <https://demo.openlinksw.com/> ;
    schema:encodingFormat "application/sql"@en .

:customerKgOntology a schema:Dataset ;
    schema:name "CustomerKG TBox"@en ;
    schema:description "419 RDFS/OWL triples in the named graph http://demo.openlinksw.com/schemas/CustomerKG/: sixteen classes under schema.org supertypes with a four-group context hierarchy, and forty-seven properties with domains and ranges, including a groundedIn property hierarchy."@en ;
    schema:url <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    schema:encodingFormat "text/turtle"@en .

:customerKgView a schema:Dataset ;
    schema:name "CustomerKG RDF View"@en ;
    schema:description "1,160 triples in the named graph http://demo.openlinksw.com/CustomerKG#, projected at query time from the twelve source tables by one ALTER QUAD STORAGE statement binding eleven IRI classes to table columns. A hand count from the source rows gives the same 1,160. No triple is stored; every answer is computed from the live rows."@en ;
    schema:url <http://demo.openlinksw.com/CustomerKG#> .

:identityGraphDataset a schema:Dataset ;
    schema:name "CustomerKG identity graph"@en ;
    schema:description "73 triples in the named graph http://demo.openlinksw.com/CustomerKG/identity#: customer names and email hashes derived from the live view, five records from other systems and one explicit owl:sameAs. Its vocabulary, in http://demo.openlinksw.com/schemas/CustomerKG/identity/, declares ckg:emailHash an owl:InverseFunctionalProperty, and the rule set urn:customerkg:identity-inference is built from it. The data is synthetic."@en ;
    schema:url <http://demo.openlinksw.com/CustomerKG/identity#> .

:attachedBakehouseTable a schema:Dataset ;
    schema:name "databricks.bakehouse.sales_transactions_viritual_odbc"@en ;
    schema:description "A table that already existed on demo.openlinksw.com before this session, attached through the ODBC data source databricks_odbc to a Databricks Bakehouse sample table with ten columns: transactionID, customerID, franchiseID, dateTime, product, quantity, unitPrice, totalPrice, paymentMethod and cardNumber. This session read its column metadata only; it did not query its rows and did not modify it."@en ;
    schema:url <https://demo.openlinksw.com/> .


# ═══════════════════════════════════════════════════════════════════════════
# Thirteen comparison dimensions (cdx:ComparisonDimension)
# ═══════════════════════════════════════════════════════════════════════════

:dimIdentityEngine a cdx:ComparisonDimension ;
    schema:name "Resolving who is who"@en ;
    schema:description "How signals and records from different systems are reconciled to one customer."@en ;
    schema:position 1 ;
    :databricksPosition "Agentic Identity Resolution blends deterministic, probabilistic and agentic matching with Acxiom's graph-based identity, hygiene, matching and enrichment. Acxiom's Real ID engine runs natively inside Databricks, so brands resolve data where it lives. The article describes matching engines and a partner graph; it does not describe ontology-based reasoning."@en ;
    :virtuosoPosition "Identity reconciliation is native reasoning, with no third-party engine: owl:sameAs applies explicitly and an owl:InverseFunctionalProperty designation applies implicitly. On demo, a graph of CRM, app and loyalty records is reconciled with the customers in the view (Query 3): with no reasoning each customer has one name; with explicit owl:sameAs Avery Quinn gains her loyalty record; with ckg:emailHash declared inverse functional the CRM export and the app profile that share her hash join too, so she is known under four records and Blake Moreno under two, while a record that matches nobody stays apart. Attaching Databricks tables over ODBC brings the same reasoning to that data without a copy. For master lookups, OpenLink hosts DBpedia and many enclaves of the LOD Cloud (stated by OpenLink). Match method and confidence are recorded per signal (Query 2). A probabilistic or agentic matcher is not shown here; matches from any engine can be held as owl:sameAs and reasoned over."@en ;
    :assessment "Virtuoso leads on reconciliation, because reasoning applies the same identity rules to every record in the store, explicitly or by property designation, without a pipeline or a partner, and it is demonstrated here and extends to Databricks data attached over ODBC. The article describes a matching engine of a different kind, probabilistic and agentic with a partner graph, which this page does not show for Virtuoso; the two are complementary, since matches from either can be asserted as owl:sameAs and reasoned over."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 3 and 2, over synthetic records, on a physical graph; DBpedia and LOD Cloud hosting stated by OpenLink. Databricks: the article, including the Acxiom and Lovelytics figures, which are vendor statements not independently checked."@en .

:dimStableIdentifier a cdx:ComparisonDimension ;
    schema:name "Identity that outlives the system"@en ;
    schema:description "Whether a customer or decision carries an identifier that resolves outside the store that holds it."@en ;
    schema:position 2 ;
    :databricksPosition "The article treats identity as a maintained profile and a framework that traces a result back to the customer and the decision that produced it. It does not describe identifiers that resolve outside the platform."@en ;
    :virtuosoPosition "Every entity in the CustomerKG view has an HTTP IRI that answers with a 303 to a description page and can be referenced from any other graph. A decision, an offer and a guardrail are as linkable as a customer. Hyperlinks act as stable identifiers across stores, and 176 entity IRIs were checked to resolve."@en ;
    :assessment "For one estate this is a convenience. It becomes the main difference when exposure, engagement and outcome live in different organisations' systems and must be joined without a shared key."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, entity IRIs resolve. Databricks: the article is silent."@en .

:dimWhereContextLives a cdx:ComparisonDimension ;
    schema:name "Where governed context lives"@en ;
    schema:description "Whether customer context is copied into a separate system or used where it already lives."@en ;
    schema:position 3 ;
    :databricksPosition "CustomerLake is embedded in the lakehouse and governed by Unity Catalog, so marketing operates on the same data and AI foundation as the rest of the business instead of creating another copy."@en ;
    :virtuosoPosition "An RDF View projects the tables in place and inherits their SQL grants (demonstrated). Over ODBC, Databricks tables stay in Databricks and Virtuoso attaches them, so the view is computed from the remote rows at query time. SPARQL SERVICE reads the same view from a second instance without copying it (Queries 13 and 14)."@en ;
    :assessment "Genuine parity on this axis: both keep context where it already lives and neither asks the marketer to maintain another copy. The differences between the approaches are on the neighbouring rows."@en ;
    :favours "Even"@en ;
    :evidence "Virtuoso: demonstrated on this page for local tables and across two instances; the ODBC attachment is documented and its metadata read, not queried. Databricks: the article."@en .

:dimFourContexts a cdx:ComparisonDimension ;
    schema:name "Modelling the four kinds of context"@en ;
    schema:description "How customer, business, decision and control context are represented so an agent can read them."@en ;
    schema:position 4 ;
    :databricksPosition "The article names the four kinds of context and notes that one profile can run to thousands of columns, but it does not publish a schema for them."@en ;
    :virtuosoPosition "An ontology makes the four groups classes: ConsentRecord is both a customer fact and a control, and an RDFS rule set lets one query for ControlElement return the 5 guardrails and 38 consent records that are only ever typed as their subclasses (Query 6). Without the rule set the same query returns nothing."@en ;
    :assessment "A silence in an article is not a gap in a product, so this axis records what is demonstrated. Virtuoso shows an explicit, queryable model; Databricks may well have one that the article does not describe."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 6 and 7 and the ontology. Databricks: the article names the four kinds only."@en .

:dimDecisionMemory a cdx:ComparisonDimension ;
    schema:name "Remembering what was decided and why"@en ;
    schema:description "Whether past decisions, their rationale and the customer's response are kept where the next decision can read them."@en ;
    schema:position 5 ;
    :databricksPosition "Decision context captures the offers a customer has already received, how they responded and why earlier decisions were made, and the result goes back into the customer's context for the next decision."@en ;
    :virtuosoPosition "Each decision is a row with its arm, action, offer, channel, rationale and approval flag, linked to the outcome that followed (Queries 1 and 8). Context items record which of the four kinds each fact was read from. The agent-rdf-memory harness applies the same pattern to the working agent: sessions, lessons and standing preferences are queryable named graphs retrieved by an ontology-routed query."@en ;
    :assessment "Both describe the same loop. Virtuoso's version is demonstrated, with a rationale column an auditor can read, context items tagged by kind and each outcome linked to its decision; Databricks' is described as product behaviour in the article. The lean follows what is shown, not a measured advantage."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 1 and 8; harness memory documented in the repository. Databricks: the article."@en .

:dimGuardrails a cdx:ComparisonDimension ;
    schema:name "Guardrails, consent and approval points"@en ;
    schema:description "How limits on discounts, contact frequency, consent and human approval are expressed and audited."@en ;
    schema:position 6 ;
    :databricksPosition "Control is the layer of intent, guardrails, permissions and approval points that people define and agents must respect, including where human approval is required."@en ;
    :virtuosoPosition "Guardrails are entities linked to every decision they constrain (52 links). Query 4 audits the human-approval rules against the decisions that triggered them, and Query 9 joins withdrawn consent in the graph to the native decision table and finds no contact on a withdrawn channel. The harness holds the working agent's own standing rules the same way, as HowToSteps."@en ;
    :assessment "Both treat control as first-class. What differs is what is shown: Virtuoso's guardrails, their application and their audit are demonstrated as queries, while Databricks' control layer is described in the article. Enforcement, meaning whether an agent can be stopped from breaking a rule, was tested on neither side."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 4, 6 and 9, as audit rather than enforcement. Databricks: the article."@en .

:dimNaturalLanguage a cdx:ComparisonDimension ;
    schema:name "Asking in plain language"@en ;
    schema:description "How a marketer explores context and builds an audience without waiting for a data pull."@en ;
    schema:position 7 ;
    :databricksPosition "Genie lets marketers use natural language to explore customer context and build audiences, reducing the wait for custom data pulls."@en ;
    :virtuosoPosition "Natural-language access is part of the deployed stack: OPAL agents, the OpenLink MCP tools (the virtuoso-support-agent skill documents 25), and packaged SKILL.md skills such as data-twingler, dbpedia-query-skill and wikidata-query-skill that turn a question into SQL, SPARQL or SPASQL. This whole page was produced from a plain-language request, with the view, ontology and rewrite-rule generators called through the OpenLink MCP tools and the DDL and DML run through exec over XMLA."@en ;
    :assessment "Both offer plain-language access to the data. Virtuoso's is demonstrated here in building and querying the estate; Genie's is described in the article. Genie is presented as an integrated experience for marketers and this page shows no marketer-facing interface on the Virtuoso side, so the lean rests on demonstrated availability, not on a comparison of usability."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated by how this page was built; skills and MCP tooling documented in the repository. Databricks: the article."@en .

:dimActivation a cdx:ComparisonDimension ;
    schema:name "Acting across channels"@en ;
    schema:description "Whether the platform turns the chosen action into a consent-checked instruction for the channel where the customer is reached."@en ;
    schema:position 8 ;
    :databricksPosition "Campaign Agents activate across channels and Infinity Campaigns are always-on loops that identify an audience, recommend the next-best offer or channel, activate across destinations, and optimise or suppress based on performance. CustomerLake works with partners already in a brand's stack."@en ;
    :virtuosoPosition "The estate yields an activation queue as a query (Query 5): each decision that has a channel, joined to the consent record that allows that channel, so every row says what would be sent, to whom, on which owned or paid channel and under which consent, and suppressions, holdouts and unconsented contacts are excluded by the join. Delivery uses the deployed stack's own channels: email through the OpenLink send_email function and the weblog digests of the weblog-from-webdav skill, ActivityPub publishing through the fediverse-crud skill, and commerce flows through the acp-client and ucp-client skills. Nothing was sent from this page."@en ;
    :assessment "Virtuoso leads on what is shown: the consent-checked handoff is demonstrated as a query over the decision data, where the article describes activation across destinations without showing it. Delivery was run for neither side here and no destination catalogue is shown for either, so the lean rests on the demonstrated handoff and not on delivery breadth."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Query 5, as a queue rather than a send; delivery skills documented in the repository. Databricks: the article."@en .

:dimIncrementality a cdx:ComparisonDimension ;
    schema:name "Measuring what changed because of the action"@en ;
    schema:description "How outcomes are compared against doing nothing and turned into a shared basis for investment, and how far the analysis can be extended."@en ;
    schema:position 9 ;
    :databricksPosition "Incrementality is the common language between marketing and finance, supported by attribution, incrementality testing and marketing mix modeling, with conversational analytics to explore what happened and why. The article lists five things a CMO must show and gives no schema, numbers or worked calculation."@en ;
    :virtuosoPosition "A randomised holdout arm and an outcome per decision make the lift a SQL query (Query 10): in the reactivation campaign the treated order rate is 66 percent against 40, with about 4 dollars of incremental profit; in the discount test the treated rate is 75 percent against 100 and incremental profit is minus 15 dollars, a subsidy. Query 11 splits contribution by owned channel, paid channel and no channel. Every interaction, administrative or querying, can be carried out in natural language against the same linked data that the RDF Views publish, so a further analysis is a question put to that data rather than a product feature to wait for."@en ;
    :assessment "Virtuoso leads on what is shown: a holdout, an outcome per decision and the lift, profit contribution, contribution by lever and a subsidy finding as queries over linked rows, on synthetic numbers, with any further analysis available by asking in natural language, where the article names the concept, the methods and the five proofs without showing one. Neither side demonstrates attribution or a marketing mix model here, so no method-count lead is claimed for either."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 10 and 11, on synthetic data; natural-language access demonstrated by how this page was built. Databricks: the article."@en .

:dimQueryOpenness a cdx:ComparisonDimension ;
    schema:name "Languages and standards"@en ;
    schema:description "The query languages and data standards an application or agent uses."@en ;
    schema:position 10 ;
    :databricksPosition "Natural language through Genie over the lakehouse. The article names no query language."@en ;
    :virtuosoPosition "SQL, SPARQL, SPASQL and GraphQL in one engine, with GQL stated by OpenLink; SPASQL nests a SPARQL query in a SQL SELECT so a graph result joins native tables (Queries 8 and 9), and the SPARQL compiler's generated SQL can be read (Query 12)."@en ;
    :assessment "An open W3C query language, several languages in one engine and one-statement graph-to-table joins are advantages for integration and audit, and the generated SQL can be inspected. The article describes one interface, natural language, and is silent on inspecting what it generates."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 1 to 14; GQL stated by OpenLink. Databricks: the article."@en .

:dimPartnerJoins a cdx:ComparisonDimension ;
    schema:name "Joining with identity and measurement partners"@en ;
    schema:description "How context from other organisations is combined without a new silo."@en ;
    schema:position 11 ;
    :databricksPosition "CustomerLake is designed to work with the identity, activation, measurement and customer-experience partners a brand already uses, so trusted context flows across channels without a new silo. It launched with Acxiom and is supported by Lovelytics."@en ;
    :virtuosoPosition "Resolvable IRIs and SPARQL federation let a query span graphs held by different instances, each answering for its own data. Demonstrated here between two OpenLink instances: Queries 13 and 14 run on URIBurner and read the CustomerKG view on demo through SERVICE. That is cross-instance, not cross-organisation; a query across different parties' graphs was not run."@en ;
    :assessment "Databricks' route is a partner ecosystem on one platform, as the article states; Virtuoso's is links between platforms, and the mechanism is demonstrated across two instances. The lean rests on the demonstrated mechanism; whether a given partner exposes a SPARQL endpoint is a separate question."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Virtuoso: demonstrated on this page, Queries 13 and 14, across two instances. Databricks: the article."@en .

:dimOperating a cdx:ComparisonDimension ;
    schema:name "Operating the platform"@en ;
    schema:description "Who runs the infrastructure and how the capabilities are packaged for a marketing and data team."@en ;
    schema:position 12 ;
    :databricksPosition "A managed lakehouse platform with the CDP, governance, assistant and agents delivered as products, and a services ecosystem to put them into production."@en ;
    :virtuosoPosition "Deployment is a choice: on premises, Docker containers, cloud machine images on AWS, Azure and Google Cloud including pay-as-you-go offers, and a self-managed service on AWS. OpenLink Managed services are announced for September 2026; their availability was not checked and they are not credited here. Configuration and administration are available in natural language through the OpenLink MCP tooling and packaged SKILL.md skills, and this build is an instance of that."@en ;
    :assessment "Databricks offers a managed platform with its products integrated; Virtuoso offers broader deployment choice and agent-driven administration, which this page demonstrates, while its managed service is only announced. The lean follows deployment breadth and demonstrated agent administration, not measured operational cost or effort."@en ;
    :favours "Virtuoso"@en ;
    :evidence "Databricks: the article. Virtuoso: cloud offers confirmed in OpenLink community posts on 2026-09-30; other deployment options and managed services stated by OpenLink without independent checking; agent administration demonstrated on this page."@en .

:dimTogether a cdx:ComparisonDimension ;
    schema:name "Using the two together"@en ;
    schema:description "Whether the approaches exclude each other."@en ;
    schema:position 13 ;
    :databricksPosition "Databricks is the platform where the data, models and governance already sit."@en ;
    :virtuosoPosition "Virtuoso attaches Databricks tables over ODBC and generates RDF Views, rewrite rules and an ontology over them, so the Databricks estate gains resolvable entity IRIs, SPARQL and identity reconciliation by reasoning without a copy. Demo already holds such an attachment, and the generator's output for it is shown on this page without being executed."@en ;
    :assessment "Complementary rather than competing, which is genuine parity on this axis. The lakehouse stays the system of record and the place for identity matching, models and activation; Virtuoso adds a linked, resolvable, auditable view over the same rows. The one extra step is a privacy review of every column the mapping publishes, as the card-number column shows."@en ;
    :favours "Even"@en ;
    :evidence "Virtuoso: ODBC attachment present on demo, view generated and not executed. Databricks: tables remain in place."@en .


# ═══════════════════════════════════════════════════════════════════════════
# Live queries (all executed before being written down: eleven on demo.openlinksw.com, two on URIBurner)
# ═══════════════════════════════════════════════════════════════════════════

:queryQ1 a schema:SoftwareSourceCode ;
    schema:name "The decision ledger"@en ;
    schema:position 1 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Every decision with its customer, campaign, experiment arm, action, offer, channel and the order that followed. Seven patterns and two OPTIONALs walk decision, customer, campaign, offer, channel and outcome, and the decision IRI leads every row so each row is a key that resolves."@en ;
    schema:result "20 rows: 14 decisions in the reactivation campaign (9 treatment, 5 holdout) and 6 in the discount test (4 treatment, 2 holdout). Casey Lindholm received the family bundle by app push and ordered 48 dollars; holdout customer Jules Barrow was never contacted and ordered 25."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
PREFIX schema: <http://schema.org/>
SELECT ?decisionIri ?customer ?campaign ?arm ?action ?offer ?channel ?ordered ?revenueUsd
FROM <http://demo.openlinksw.com/CustomerKG#>
WHERE {
  ?decisionIri a ckg:Decision ; ckg:forCustomer ?customerIri ; ckg:inCampaign ?campaignIri ;
               ckg:arm ?arm ; ckg:actionKind ?action .
  ?customerIri schema:name ?customer .
  ?campaignIri schema:name ?campaign .
  OPTIONAL { ?decisionIri ckg:usesOffer ?offerIri . ?offerIri schema:name ?offer }
  OPTIONAL { ?decisionIri ckg:viaChannel ?channelIri . ?channelIri schema:name ?channel }
  ?outcomeIri ckg:ofDecision ?decisionIri ; ckg:ordered ?ordered ; ckg:revenueUsd ?revenueUsd .
}
ORDER BY ?campaign ?arm ?customer"""@en .

:queryQ2 a schema:SoftwareSourceCode ;
    schema:name "Identity signals by match method"@en ;
    schema:position 2 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "How many signals each matching method produced, how many resolved to a customer and the average confidence, with one example signal IRI per group. Unmatched pseudonymous signals have no resolvesTo edge, which the OPTIONAL keeps visible."@en ;
    schema:result "4 rows: deterministic 22 signals, all resolved, confidence 100; probabilistic 7, all resolved, 79; agentic 3, all resolved, 91; unmatched 4, none resolved."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
PREFIX schema: <http://schema.org/>
SELECT ?matchMethod (SAMPLE(?signalIri) AS ?exampleSignalIri) (COUNT(?signalIri) AS ?signals) (COUNT(?customerIri) AS ?resolved) (ROUND(AVG(?confidence)) AS ?avgConfidence)
FROM <http://demo.openlinksw.com/CustomerKG#>
WHERE {
  ?signalIri a ckg:IdentitySignal ; ckg:matchMethod ?matchMethod ; ckg:confidence ?confidence .
  OPTIONAL { ?signalIri ckg:resolvesTo ?customerIri }
}
GROUP BY ?matchMethod
ORDER BY DESC(?signals)"""@en .

:queryQ3 a schema:SoftwareSourceCode ;
    schema:name "Identity reconciliation by reasoning: one person, several records"@en ;
    schema:position 3 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Reads the identity graph, which holds customer facts derived from the live view plus CRM, app and loyalty records. Each branch asks for the names of one customer. With no pragmas the answer is one name per customer; with input:same-as an explicit owl:sameAs adds the loyalty record; with the rule set that declares ckg:emailHash inverse functional, the CRM export and the app profile that share a hash join implicitly. Removing the pragmas shows the contrast."@en ;
    schema:result "7 rows with both pragmas: customer 1, Avery Quinn, under four names (her own, the CRM export, the app profile and the loyalty member) and customer 2, Blake Moreno, under two, while customer 3, Casey Lindholm, has no matching records and keeps one. With input:same-as alone there are 4 rows, the loyalty record being the only addition, and with neither there are 3. The CRM record that matches nobody never joins a customer."@en ;
    schema:citation "Signed-in only: the same query without DISTINCT returned the 100,000-row cap of repeated rows on the anonymous HTTP endpoint, and the DISTINCT form did not return within the five-minute limit, so only the signed-in route, the XMLA connection the Query Builder uses, is verified. owl:sameAs merging works over physical graphs, which is why the customer facts are copied into the identity graph from the view rather than joined to it live."@en ;
    schema:category "Instance data interactions"@en ;
    schema:conditionsOfAccess "Needs a signed-in session. Authenticate to demo.openlinksw.com with the account demo or vdb, where the user id and the password are identical. The instance is protected by attribute-based access control (ABAC) ACLs."@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """DEFINE input:inference "urn:customerkg:identity-inference"
DEFINE input:same-as "yes"
PREFIX schema: <http://schema.org/>
SELECT DISTINCT ?personIri ?name
FROM <http://demo.openlinksw.com/CustomerKG/identity#>
WHERE {
  { <http://demo.openlinksw.com/CustomerKG/customer/1#this> schema:name ?name . BIND(<http://demo.openlinksw.com/CustomerKG/customer/1#this> AS ?personIri) } UNION { <http://demo.openlinksw.com/CustomerKG/customer/2#this> schema:name ?name . BIND(<http://demo.openlinksw.com/CustomerKG/customer/2#this> AS ?personIri) } UNION { <http://demo.openlinksw.com/CustomerKG/customer/3#this> schema:name ?name . BIND(<http://demo.openlinksw.com/CustomerKG/customer/3#this> AS ?personIri) }
}"""@en .

:queryQ4 a schema:SoftwareSourceCode ;
    schema:name "Human-approval audit"@en ;
    schema:position 4 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Which decisions were constrained by a human-approval guardrail, what the offer's discount was, the rule's threshold, and whether a person approved. Decisions reach guardrails through the decision_guardrail link table, which is mapped as the constrainedBy predicate."@en ;
    schema:result "4 rows: decisions 3 and 9 (Casey Lindholm and Indigo Raman), each on the 20 percent family bundle and each constrained by both the above-15-percent rule and the bundle rule, approvedByHuman 1 on all four."@en ;
    schema:citation "Adding ORDER BY to this query raised SQ142 on this build, so it carries none."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
PREFIX schema: <http://schema.org/>
SELECT ?decisionIri ?customer ?offer ?discountPct ?rule ?limitValue ?approvedByHuman
FROM <http://demo.openlinksw.com/CustomerKG#>
WHERE {
  ?decisionIri ckg:constrainedBy ?guardrailIri ; ckg:forCustomer ?customerIri ; ckg:usesOffer ?offerIri ; ckg:approvedByHuman ?approvedByHuman .
  ?guardrailIri ckg:ruleKind "human-approval" ; schema:name ?rule ; ckg:limitValue ?limitValue .
  ?customerIri schema:name ?customer .
  ?offerIri schema:name ?offer ; ckg:discountPct ?discountPct .
}"""@en .

:queryQ5 a schema:SoftwareSourceCode ;
    schema:name "The activation queue: consent-checked messages ready to send"@en ;
    schema:position 5 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Which message goes to whom, on which channel, under which action, with the consent record that allows it. A decision appears only when its customer has a granted consent record for the channel the decision uses, so suppressions, holdouts and any unconsented contact are excluded by the join. It is the handoff a delivery tool would consume; nothing is sent."@en ;
    schema:result "12 rows: 10 on owned channels (app push, email and SMS) and 2 on paid channels (display retargeting and paid social), each led by the decision IRI and carrying the IRI of the consent record that allows it. The 8 decisions that are suppressions or holdouts do not appear."@en ;
    schema:citation "Delivery was not run: the queue shows what would be sent and that consent allows it, not that anything was sent."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
PREFIX schema: <http://schema.org/>
SELECT ?decisionIri ?customer ?channel ?channelKind ?offer ?actionKind ?consentIri
FROM <http://demo.openlinksw.com/CustomerKG#>
WHERE {
  ?decisionIri a ckg:Decision ; ckg:forCustomer ?custIri ; ckg:viaChannel ?chanIri ; ckg:actionKind ?actionKind .
  ?custIri schema:name ?customer .
  ?chanIri schema:name ?channel ; ckg:channelKind ?channelKind .
  ?consentIri a ckg:ConsentRecord ; ckg:forCustomer ?custIri ; ckg:forChannel ?chanIri ; ckg:consentStatus "granted" .
  OPTIONAL { ?decisionIri ckg:usesOffer ?offerIri . ?offerIri schema:name ?offer }
}"""@en .

:queryQ6 a schema:SoftwareSourceCode ;
    schema:name "Entailment: everything that is a control"@en ;
    schema:position 6 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Nothing in the data is typed ControlElement; guardrails are typed Guardrail and consent records ConsentRecord. The TBox says both are subclasses of ControlElement, and one DEFINE input:inference pragma activates the rule set derived from it."@en ;
    schema:result "43 rows: the 5 guardrails and the 38 consent records. Without the pragma the same query returns 0."@en ;
    schema:citation "Under the pragma the same pattern without DISTINCT returned 68 rows for 5 guardrails, so SELECT DISTINCT is required here; a variable class in the pattern raised SQ200 and COUNT raised SQ142, so the query lists members instead of counting them."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """DEFINE input:inference "urn:customerkg:inference"
PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
PREFIX schema: <http://schema.org/>
SELECT DISTINCT ?controlIri ?ruleKind ?consentStatus
FROM <http://demo.openlinksw.com/CustomerKG#>
WHERE {
  ?controlIri a ckg:ControlElement .
  OPTIONAL { ?controlIri ckg:ruleKind ?ruleKind }
  OPTIONAL { ?controlIri ckg:consentStatus ?consentStatus }
}"""@en .

:queryQ7 a schema:SoftwareSourceCode ;
    schema:name "The property hierarchy, read from the TBox"@en ;
    schema:position 7 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Five properties are declared sub-properties of groundedIn. The query joins the TBox graph in explicitly, finds every sub-property used on a decision, and counts the links, as the earlier ScaleKG estate required."@en ;
    schema:result "5 rows: constrained by 52 links over 20 decisions, in campaign 20, for customer 20, via channel 12 and uses offer 10."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
PREFIX schema: <http://schema.org/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?relation (COUNT(DISTINCT ?decisionIri) AS ?decisions) (COUNT(*) AS ?links) (SAMPLE(?decisionIri) AS ?exampleDecisionIri)
FROM <http://demo.openlinksw.com/CustomerKG#>
FROM <http://demo.openlinksw.com/schemas/CustomerKG/>
WHERE {
  ?decisionIri a ckg:Decision ; ?relationIri ?targetIri .
  ?relationIri rdfs:subPropertyOf ckg:groundedIn ; rdfs:label ?relation .
}
GROUP BY ?relation
ORDER BY DESC(?links)"""@en .

:queryQ8 a schema:SoftwareSourceCode ;
    schema:name "Profit per decision: SQL arithmetic over graph rows"@en ;
    schema:position 8 ;
    schema:programmingLanguage "SPASQL"@en ;
    schema:description "A SPARQL query nested in a SQL SELECT. The graph side returns each decision's IRI, customer, arm and its margin, media cost and discount cost; the SQL side computes profit as margin minus media minus discount and sorts by it."@en ;
    schema:result "20 rows, each led by the decision IRI, from a profit of 9 dollars (Avery Quinn, Jules Barrow and Sage Whitlock, the last two in holdout arms) down to minus 2 for each of the two paid-channel switches, which cost media and produced no order."@en ;
    schema:citation "Wider nested queries and SQL aggregates over a SPARQL derived table raised SQ142 on this build; this five-variable form with row-level arithmetic ran cleanly."@en ;
    schema:category "Instance data interactions"@en ;
    schema:conditionsOfAccess "Needs a signed-in session. Authenticate to demo.openlinksw.com with the account demo or vdb, where the user id and the password are identical. The instance is protected by attribute-based access control (ABAC) ACLs."@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """SELECT d, customer, arm, margin_usd, media_usd, discount_usd, margin_usd - media_usd - discount_usd AS profit_usd
FROM (SPARQL
  PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
  PREFIX schema: <http://schema.org/>
  SELECT ?d ?customer ?arm ?margin_usd ?media_usd ?discount_usd
  FROM <http://demo.openlinksw.com/CustomerKG#>
  WHERE {
    ?d a ckg:Decision ; ckg:forCustomer ?cust ; ckg:arm ?arm .
    ?cust schema:name ?customer .
    ?o ckg:ofDecision ?d ; ckg:marginUsd ?margin_usd ; ckg:mediaCostUsd ?media_usd ; ckg:discountCostUsd ?discount_usd .
  }
) AS x
ORDER BY profit_usd DESC, customer"""@en .

:queryQ9 a schema:SoftwareSourceCode ;
    schema:name "Withdrawn consent joined to native decisions"@en ;
    schema:position 9 ;
    schema:programmingLanguage "SPASQL"@en ;
    schema:description "SPARQL finds consent records whose status is withdrawn, with the customer and channel names; SQL joins those rows to the native customer, channel and decision tables and counts decisions that used the withdrawn channel. The consent IRI leads each row."@en ;
    schema:result "3 rows, each with 0 contacts after withdrawal: Harper Doyle on app push, Kendall Osei on email and Noel Ferreira on email. The graph supplies the rule, the table supplies the evidence."@en ;
    schema:category "Instance data interactions"@en ;
    schema:conditionsOfAccess "Needs a signed-in session. Authenticate to demo.openlinksw.com with the account demo or vdb, where the user id and the password are identical. The instance is protected by attribute-based access control (ABAC) ACLs."@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """SELECT x.consent, x.customer_name, x.channel_name, COUNT(d.decision_id) AS contacts_after_withdrawal
FROM (SPARQL
  PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
  PREFIX schema: <http://schema.org/>
  SELECT ?consent ?customer_name ?channel_name
  FROM <http://demo.openlinksw.com/CustomerKG#>
  WHERE {
    ?consent a ckg:ConsentRecord ; ckg:consentStatus "withdrawn" ; ckg:forCustomer ?cust ; ckg:forChannel ?chan .
    ?cust schema:name ?customer_name .
    ?chan schema:name ?channel_name .
  }
) AS x
JOIN CustomerKG.kidehen.customer cu ON cu.full_name = x.customer_name
JOIN CustomerKG.kidehen.channel ch ON ch.channel_name = x.channel_name
LEFT JOIN CustomerKG.kidehen.decision d ON d.customer_id = cu.customer_id AND d.channel_id = ch.channel_id
GROUP BY x.consent, x.customer_name, x.channel_name
ORDER BY x.customer_name"""@en .

:queryQ10 a schema:SoftwareSourceCode ;
    schema:name "Incrementality: treated against holdout"@en ;
    schema:position 10 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "Plain SQL over the native tables: per campaign, the order rate and profit contribution of the treated arm against the randomised holdout, and the profit the treated customers added beyond what holdout behaviour predicts."@en ;
    schema:result "2 rows. Reactivation: treated order rate 66 percent against 40 (a 26 point lift), treated profit 35 dollars against 17 for the holdout, incremental profit about 4. Discount test: treated 75 percent against 100, incremental profit minus 15 dollars, so the incentive subsidised orders."@en ;
    schema:citation "Order rates use integer division, so 6 of 9 shows as 66. Incremental profit is the treated profit minus the treated count times the holdout's profit per customer, which is 4.4 for the first campaign by hand."@en ;
    schema:category "Instance data interactions"@en ;
    schema:conditionsOfAccess "Needs a signed-in session. Authenticate to demo.openlinksw.com with the account demo or vdb, where the user id and the password are identical. The instance is protected by attribute-based access control (ABAC) ACLs."@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """SELECT ca.campaign_id, ca.campaign_name,
       SUM(CASE WHEN de.arm = 'treatment' THEN 1 ELSE 0 END) AS treated,
       SUM(CASE WHEN de.arm = 'holdout' THEN 1 ELSE 0 END) AS held_out,
       SUM(CASE WHEN de.arm = 'treatment' THEN ou.ordered ELSE 0 END) * 100 / SUM(CASE WHEN de.arm = 'treatment' THEN 1 ELSE 0 END) AS treated_rate_pct,
       SUM(CASE WHEN de.arm = 'holdout' THEN ou.ordered ELSE 0 END) * 100 / SUM(CASE WHEN de.arm = 'holdout' THEN 1 ELSE 0 END) AS holdout_rate_pct,
       SUM(CASE WHEN de.arm = 'treatment' THEN ou.margin_usd - ou.media_cost_usd - ou.discount_cost_usd ELSE 0 END) AS treated_profit_usd,
       SUM(CASE WHEN de.arm = 'holdout' THEN ou.margin_usd - ou.media_cost_usd - ou.discount_cost_usd ELSE 0 END) AS holdout_profit_usd,
       ROUND(SUM(CASE WHEN de.arm = 'treatment' THEN ou.margin_usd - ou.media_cost_usd - ou.discount_cost_usd ELSE 0 END)
             - SUM(CASE WHEN de.arm = 'treatment' THEN 1 ELSE 0 END)
               * CAST(SUM(CASE WHEN de.arm = 'holdout' THEN ou.margin_usd - ou.media_cost_usd - ou.discount_cost_usd ELSE 0 END) AS DOUBLE PRECISION)
               / SUM(CASE WHEN de.arm = 'holdout' THEN 1 ELSE 0 END), 1) AS incremental_profit_usd
FROM CustomerKG.kidehen.decision de
JOIN CustomerKG.kidehen.outcome ou ON ou.decision_id = de.decision_id
JOIN CustomerKG.kidehen.campaign ca ON ca.campaign_id = de.campaign_id
GROUP BY ca.campaign_id, ca.campaign_name
ORDER BY ca.campaign_id"""@en .

:queryQ11 a schema:SoftwareSourceCode ;
    schema:name "Contribution by lever"@en ;
    schema:position 11 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "Plain SQL: decisions, orders, revenue, media cost, discount cost and profit grouped by whether the decision used an owned channel, a paid channel or none, which is the article's contribution-by-lever question in miniature."@en ;
    schema:result "3 rows: owned 10 decisions, 9 orders, 260 dollars revenue, profit 58; none (suppressions and holdouts) 8 decisions, 4 orders, 92 dollars, profit 34; paid 2 decisions, no orders, profit minus 4."@en ;
    schema:category "Instance data interactions"@en ;
    schema:conditionsOfAccess "Needs a signed-in session. Authenticate to demo.openlinksw.com with the account demo or vdb, where the user id and the password are identical. The instance is protected by attribute-based access control (ABAC) ACLs."@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """SELECT COALESCE(ch.channel_kind, 'none') AS lever,
       COUNT(*) AS decisions,
       SUM(ou.ordered) AS orders,
       SUM(ou.revenue_usd) AS revenue_usd,
       SUM(ou.media_cost_usd) AS media_usd,
       SUM(ou.discount_cost_usd) AS discount_usd,
       SUM(ou.margin_usd - ou.media_cost_usd - ou.discount_cost_usd) AS profit_usd
FROM CustomerKG.kidehen.decision de
JOIN CustomerKG.kidehen.outcome ou ON ou.decision_id = de.decision_id
LEFT JOIN CustomerKG.kidehen.channel ch ON ch.channel_id = de.channel_id
GROUP BY COALESCE(ch.channel_kind, 'none')
ORDER BY revenue_usd DESC"""@en .

:queryQ12 a schema:SoftwareSourceCode ;
    schema:name "The SQL Virtuoso compiles a SPARQL query into"@en ;
    schema:position 12 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "sparql_to_sql_text returns the SQL Virtuoso generates for a SPARQL query, so the translation can be read rather than trusted. length() and subseq() keep the transfer small."@en ;
    schema:result "One row: the generated SQL is 20,204 characters long and begins with a __ro2sq conversion over the customer table's mapped columns."@en ;
    schema:citation "The generated SQL is large because the compiler considers every quad map that could supply each pattern and prunes with filters; the answer is still computed from the live rows."@en ;
    schema:category "Instance data interactions"@en ;
    schema:conditionsOfAccess "Needs a signed-in session. Authenticate to demo.openlinksw.com with the account demo or vdb, where the user id and the password are identical. The instance is protected by attribute-based access control (ABAC) ACLs."@en ;
    schema:runtimePlatform :demoServer ;
    schema:text """SELECT length(sparql_to_sql_text('SPARQL PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/> PREFIX schema: <http://schema.org/> SELECT ?customer ?lifecycle FROM <http://demo.openlinksw.com/CustomerKG#> WHERE { ?c a ckg:Customer ; schema:name ?customer ; ckg:lifecycleStage ?lifecycle }')) AS sql_length,
       subseq(sparql_to_sql_text('SPARQL PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/> PREFIX schema: <http://schema.org/> SELECT ?customer ?lifecycle FROM <http://demo.openlinksw.com/CustomerKG#> WHERE { ?c a ckg:Customer ; schema:name ?customer ; ckg:lifecycleStage ?lifecycle }'), 0, 1500) AS sql_head"""@en .

:queryQ13 a schema:SoftwareSourceCode ;
    schema:name "Class counts across instances: URIBurner asks demo"@en ;
    schema:position 13 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Run on URIBurner's SPARQL endpoint. A SERVICE call asks demo.openlinksw.com for the number of entities per class in the CustomerKG view. URIBurner holds no CustomerKG data, so every number is fetched live from demo at query time, and each class IRI is a resolvable key."@en ;
    schema:result "11 rows, one per class: ConsentRecord 38, IdentitySignal 36, ContextItem, Customer, Decision and Outcome 20 each, Channel, Product, Offer and Guardrail 5 each, Campaign 2. They match the class counts read directly on demo."@en ;
    schema:citation "Slow on the shared servers, about 30 seconds, and the first attempt returned a transaction deadlock in iri_to_id that cleared on the second try."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :uriburnerServer ;
    schema:text """SELECT ?classIri ?entities
WHERE {
  SERVICE <https://demo.openlinksw.com/sparql> {
    SELECT ?classIri (COUNT(DISTINCT ?entityIri) AS ?entities)
    FROM <http://demo.openlinksw.com/CustomerKG#>
    WHERE { ?entityIri a ?classIri }
    GROUP BY ?classIri
  }
}
ORDER BY DESC(?entities)"""@en .

:queryQ14 a schema:SoftwareSourceCode ;
    schema:name "The four kinds of context behind one decision, fetched across instances"@en ;
    schema:position 14 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Run on URIBurner. The SERVICE call returns the context items attached to decision 3 on demo, one per kind of context, each led by the context item's IRI. It is the article's four kinds of context read by an agent that does not host the data."@en ;
    schema:result "4 rows for decision 3, Casey Lindholm's family bundle: customer behaviour (browsed a family meal six times without checkout), business margin (31 percent), decision prior offer (10 percent off ignored five weeks ago) and control approval (a 20 percent discount needs human approval: granted)."@en ;
    schema:category "Instance data interactions"@en ;
    schema:runtimePlatform :uriburnerServer ;
    schema:text """PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/>
SELECT ?itemIri ?contextType ?contextKey ?contextValue
WHERE {
  SERVICE <https://demo.openlinksw.com/sparql> {
    SELECT ?itemIri ?contextType ?contextKey ?contextValue
    FROM <http://demo.openlinksw.com/CustomerKG#>
    WHERE {
      ?itemIri a ckg:ContextItem ; ckg:informs <http://demo.openlinksw.com/CustomerKG/decision/3#this> ;
               ckg:contextType ?contextType ; ckg:contextKey ?contextKey ; ckg:contextValue ?contextValue .
    }
  }
}"""@en .


# ═══════════════════════════════════════════════════════════════════════════
# SQL, RDF View and ODBC cards
# ═══════════════════════════════════════════════════════════════════════════

:sqlSchema a schema:SoftwareSourceCode ;
    schema:category "DDL"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "Twelve tables, foreign keys declared"@en ;
    schema:position 1 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "The relational estate: channels, products, customers, identity signals, consent, offers, guardrails, campaigns, decisions, the decision-to-guardrail link table, context items and outcomes. Standard SQL; nullable foreign keys are deliberate, for unmatched signals and for actions without an offer or channel."@en ;
    schema:text """CREATE TABLE CustomerKG.kidehen.channel (channel_id INTEGER NOT NULL PRIMARY KEY, channel_name VARCHAR(40) NOT NULL, channel_kind VARCHAR(8) NOT NULL);

CREATE TABLE CustomerKG.kidehen.product (product_id INTEGER NOT NULL PRIMARY KEY, product_name VARCHAR(48) NOT NULL, category VARCHAR(16) NOT NULL, margin_pct INTEGER NOT NULL);

CREATE TABLE CustomerKG.kidehen.customer (customer_id INTEGER NOT NULL PRIMARY KEY, full_name VARCHAR(48) NOT NULL, lifecycle_stage VARCHAR(12) NOT NULL, loyalty_tier VARCHAR(8) NOT NULL, home_city VARCHAR(24) NOT NULL, usual_order_day VARCHAR(10) NOT NULL);

CREATE TABLE CustomerKG.kidehen.identity_signal (signal_id INTEGER NOT NULL PRIMARY KEY, customer_id INTEGER, signal_kind VARCHAR(16) NOT NULL, signal_value VARCHAR(24) NOT NULL, match_method VARCHAR(16) NOT NULL, confidence INTEGER NOT NULL, seen_date DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES CustomerKG.kidehen.customer (customer_id));

CREATE TABLE CustomerKG.kidehen.consent (consent_id INTEGER NOT NULL PRIMARY KEY, customer_id INTEGER NOT NULL, channel_id INTEGER NOT NULL, status VARCHAR(10) NOT NULL, updated_date DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES CustomerKG.kidehen.customer (customer_id), FOREIGN KEY (channel_id) REFERENCES CustomerKG.kidehen.channel (channel_id));

CREATE TABLE CustomerKG.kidehen.offer (offer_id INTEGER NOT NULL PRIMARY KEY, offer_name VARCHAR(40) NOT NULL, offer_kind VARCHAR(16) NOT NULL, discount_pct INTEGER NOT NULL, product_id INTEGER, FOREIGN KEY (product_id) REFERENCES CustomerKG.kidehen.product (product_id));

CREATE TABLE CustomerKG.kidehen.guardrail (guardrail_id INTEGER NOT NULL PRIMARY KEY, rule_name VARCHAR(48) NOT NULL, rule_kind VARCHAR(20) NOT NULL, limit_value INTEGER NOT NULL, offer_id INTEGER, FOREIGN KEY (offer_id) REFERENCES CustomerKG.kidehen.offer (offer_id));

CREATE TABLE CustomerKG.kidehen.campaign (campaign_id INTEGER NOT NULL PRIMARY KEY, campaign_name VARCHAR(48) NOT NULL, goal VARCHAR(80) NOT NULL, start_date DATE NOT NULL);

CREATE TABLE CustomerKG.kidehen.decision (decision_id INTEGER NOT NULL PRIMARY KEY, customer_id INTEGER NOT NULL, campaign_id INTEGER NOT NULL, arm VARCHAR(10) NOT NULL, action_kind VARCHAR(16) NOT NULL, offer_id INTEGER, channel_id INTEGER, decided_date DATE NOT NULL, rationale VARCHAR(160) NOT NULL, approved_by_human INTEGER NOT NULL, FOREIGN KEY (customer_id) REFERENCES CustomerKG.kidehen.customer (customer_id), FOREIGN KEY (campaign_id) REFERENCES CustomerKG.kidehen.campaign (campaign_id), FOREIGN KEY (offer_id) REFERENCES CustomerKG.kidehen.offer (offer_id), FOREIGN KEY (channel_id) REFERENCES CustomerKG.kidehen.channel (channel_id));

CREATE TABLE CustomerKG.kidehen.decision_guardrail (decision_id INTEGER NOT NULL, guardrail_id INTEGER NOT NULL, PRIMARY KEY (decision_id, guardrail_id), FOREIGN KEY (decision_id) REFERENCES CustomerKG.kidehen.decision (decision_id), FOREIGN KEY (guardrail_id) REFERENCES CustomerKG.kidehen.guardrail (guardrail_id));

CREATE TABLE CustomerKG.kidehen.context_item (item_id INTEGER NOT NULL PRIMARY KEY, decision_id INTEGER NOT NULL, context_type VARCHAR(10) NOT NULL, context_key VARCHAR(24) NOT NULL, context_value VARCHAR(120) NOT NULL, FOREIGN KEY (decision_id) REFERENCES CustomerKG.kidehen.decision (decision_id));

CREATE TABLE CustomerKG.kidehen.outcome (outcome_id INTEGER NOT NULL PRIMARY KEY, decision_id INTEGER NOT NULL, ordered INTEGER NOT NULL, revenue_usd INTEGER NOT NULL, margin_usd INTEGER NOT NULL, media_cost_usd INTEGER NOT NULL, discount_cost_usd INTEGER NOT NULL, repeat_visit_30d INTEGER NOT NULL, window_end DATE NOT NULL, FOREIGN KEY (decision_id) REFERENCES CustomerKG.kidehen.decision (decision_id));"""@en .

:sqlData a schema:SoftwareSourceCode ;
    schema:category "DDL"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "Synthetic rows, one per table shown"@en ;
    schema:position 2 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "Two hundred and twenty-eight rows in all. The data is synthetic and illustrative: it models the article's quick-service-restaurant scenario and is not Databricks data; every customer name is invented."@en ;
    schema:text """-- One representative row per table; the batch script carries all 228.
INSERT INTO CustomerKG.kidehen.channel VALUES (1, 'App push', 'owned');
INSERT INTO CustomerKG.kidehen.product VALUES (1, 'Classic Burger Meal', 'meal', 38);
INSERT INTO CustomerKG.kidehen.customer VALUES (1, 'Avery Quinn', 'lapsing', 'gold', 'Austin', 'Friday');
INSERT INTO CustomerKG.kidehen.identity_signal VALUES (1, 1, 'email', 'hem:a1f3', 'deterministic', 100, stringdate('2026-08-02'));
INSERT INTO CustomerKG.kidehen.consent VALUES (1, 1, 2, 'granted', stringdate('2026-08-01'));
INSERT INTO CustomerKG.kidehen.offer VALUES (1, 'Friday reminder', 'reminder', 0, 4);
INSERT INTO CustomerKG.kidehen.guardrail VALUES (1, 'Maximum discount depth 20 percent', 'discount-cap', 20, NULL);
INSERT INTO CustomerKG.kidehen.campaign VALUES (1, 'Reactivate lapsed customers', 'reactivate lapsed customers', stringdate('2026-09-22'));
INSERT INTO CustomerKG.kidehen.decision VALUES (1, 1, 1, 'treatment', 'send-reminder', 1, 1, stringdate('2026-09-22'), 'Usual Friday order missed once, usual order browsed on Thursday: a reminder is enough', 0);
INSERT INTO CustomerKG.kidehen.decision_guardrail VALUES (1, 2);
INSERT INTO CustomerKG.kidehen.context_item VALUES (1, 1, 'customer', 'usual_order_day', 'Friday; skipped last week');
INSERT INTO CustomerKG.kidehen.outcome VALUES (1, 1, 1, 24, 9, 0, 0, 1, stringdate('2026-10-06'));"""@en .

:sqlIriClasses a schema:SoftwareSourceCode ;
    schema:category "RDF View generation"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "Eleven IRI classes"@en ;
    schema:position 3 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Each IRI class turns a key column into a dereferenceable IRI. Through isql the statement needs the SPARQL keyword in front and the class name as an angle-bracket IRI. On the shared demo server the eleven statements together took several minutes."@en ;
    schema:text """SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/channel_iri> "http://demo.openlinksw.com/CustomerKG/channel/%d#this" (in channel_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/product_iri> "http://demo.openlinksw.com/CustomerKG/product/%d#this" (in product_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri> "http://demo.openlinksw.com/CustomerKG/customer/%d#this" (in customer_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri> "http://demo.openlinksw.com/CustomerKG/signal/%d#this" (in signal_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/consent_iri> "http://demo.openlinksw.com/CustomerKG/consent/%d#this" (in consent_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri> "http://demo.openlinksw.com/CustomerKG/offer/%d#this" (in offer_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri> "http://demo.openlinksw.com/CustomerKG/guardrail/%d#this" (in guardrail_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/campaign_iri> "http://demo.openlinksw.com/CustomerKG/campaign/%d#this" (in campaign_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri> "http://demo.openlinksw.com/CustomerKG/decision/%d#this" (in decision_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/context_iri> "http://demo.openlinksw.com/CustomerKG/context/%d#this" (in item_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri> "http://demo.openlinksw.com/CustomerKG/outcome/%d#this" (in outcome_id integer not null);"""@en .

:sqlOntology a schema:SoftwareSourceCode ;
    schema:category "RDF View generation"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "The ontology: every term the view uses, declared"@en ;
    schema:position 4 ;
    schema:programmingLanguage "Turtle"@en ;
    schema:description "The vocabulary for the view's graph: 16 classes and 47 properties with labels, comments, domains and ranges, cross-referenced to schema.org supertypes and close matches, and the four-group context hierarchy and groundedIn property hierarchy the entailment queries use."@en ;
    schema:text """@prefix :       <http://demo.openlinksw.com/schemas/CustomerKG/> .
@prefix ckg:    <http://demo.openlinksw.com/schemas/CustomerKG/> .
@prefix schema: <http://schema.org/> .
@prefix xsd:    <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl:    <http://www.w3.org/2002/07/owl#> .
@prefix skos:   <http://www.w3.org/2004/02/skos/core#> .

# TBox for the CustomerKG RDF View on demo.openlinksw.com. The ontology is the entity
# <http://demo.openlinksw.com/schemas/CustomerKG/>; this document is a separate CreativeWork.

<http://demo.openlinksw.com/schemas/CustomerKG/ontology-document> a schema:CreativeWork ;
    schema:name "CustomerKG ontology (TBox)"@en ;
    schema:description "RDFS/OWL vocabulary for the CustomerKG demonstration: customers, identity signals, consent, channels, products, offers, guardrails, campaigns, decisions, context items and outcomes, grouped under the four kinds of context the Databricks agentic-marketing article names. Loaded into the named graph http://demo.openlinksw.com/schemas/CustomerKG/."@en ;
    schema:dateCreated "2026-10-02T00:00:00Z"^^xsd:dateTime ;
    schema:dateModified "2026-10-02T00:00:00Z"^^xsd:dateTime ;
    schema:author <https://www.linkedin.com/in/kidehen#this> ;
    schema:about <http://demo.openlinksw.com/schemas/CustomerKG/> .

<http://demo.openlinksw.com/schemas/CustomerKG/> a owl:Ontology ;
    rdfs:label "CustomerKG ontology"@en ;
    rdfs:comment "Vocabulary for describing the customer, business, decision and control context an agent reads before choosing a marketing action, and the measured outcome that follows."@en ;
    owl:versionInfo "1.0"@en .

# ── Classes ────────────────────────────────────────────────────────────────

ckg:ContextElement a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Context element"@en ;
    rdfs:comment "Anything an agent may read before deciding what to do for a customer. The four groups below follow the four kinds of context the Databricks article names: customer, business, decision and control."@en ;
    rdfs:subClassOf schema:Thing .

ckg:CustomerContextElement a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Customer context element"@en ;
    rdfs:comment "Identity, behaviour, preference, consent and lifecycle facts about a customer."@en ;
    rdfs:subClassOf ckg:ContextElement .

ckg:BusinessContextElement a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Business context element"@en ;
    rdfs:comment "Growth objectives, margin, brand rules, inventory and channel constraints."@en ;
    rdfs:subClassOf ckg:ContextElement .

ckg:DecisionContextElement a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Decision context element"@en ;
    rdfs:comment "The actions already taken, how customers responded and why earlier decisions were made."@en ;
    rdfs:subClassOf ckg:ContextElement .

ckg:ControlElement a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Control element"@en ;
    rdfs:comment "The intent, guardrails, permissions and approval points that people define and agents must respect."@en ;
    rdfs:subClassOf ckg:ContextElement .

ckg:Customer a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Customer"@en ;
    rdfs:comment "A person who buys from the brand, with a lifecycle stage and a loyalty tier."@en ;
    rdfs:subClassOf schema:Person, ckg:CustomerContextElement .

ckg:IdentitySignal a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Identity signal"@en ;
    rdfs:comment "One observation that may identify a customer: an email hash, device, app session, ad exposure or loyalty id, with the method that matched it and the confidence. Unmatched pseudonymous signals have no customer."@en ;
    rdfs:subClassOf schema:Observation, ckg:CustomerContextElement .

ckg:ConsentRecord a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Consent record"@en ;
    rdfs:comment "Whether a customer allows contact on one channel. It is both a customer fact and a control, so it sits under both groups."@en ;
    rdfs:subClassOf schema:Permit, ckg:CustomerContextElement, ckg:ControlElement .

ckg:Channel a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Channel"@en ;
    rdfs:comment "A way to reach a customer, owned or paid."@en ;
    rdfs:subClassOf schema:Thing, ckg:BusinessContextElement .

ckg:Product a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Product"@en ;
    rdfs:comment "A menu item with a category and a margin percentage."@en ;
    rdfs:subClassOf schema:Product, ckg:BusinessContextElement .

ckg:Offer a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Offer"@en ;
    rdfs:comment "A reminder or incentive the brand may extend, with a discount depth and an optional promoted product."@en ;
    rdfs:subClassOf schema:Offer, ckg:BusinessContextElement .

ckg:Campaign a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Campaign"@en ;
    rdfs:comment "A goal-driven effort such as reactivating lapsed customers, within which decisions are made."@en ;
    rdfs:subClassOf schema:Action, ckg:BusinessContextElement .

ckg:Guardrail a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Guardrail"@en ;
    rdfs:comment "A rule the agent must respect: a discount cap, a contact-frequency limit, a consent requirement or a human-approval threshold."@en ;
    rdfs:subClassOf schema:Rule, ckg:ControlElement .

ckg:Decision a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Decision"@en ;
    rdfs:comment "One choice about one customer: act or not, with which offer and channel, in which experiment arm, and why."@en ;
    rdfs:subClassOf schema:Action, ckg:DecisionContextElement .

ckg:ContextItem a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Context item"@en ;
    rdfs:comment "A single labelled fact an agent read when making a decision, tagged with the kind of context it belongs to."@en ;
    rdfs:subClassOf schema:PropertyValue, ckg:ContextElement .

ckg:Outcome a owl:Class ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "Outcome"@en ;
    rdfs:comment "What happened after a decision: whether the customer ordered and the revenue, margin and costs involved."@en ;
    rdfs:subClassOf schema:Event, ckg:DecisionContextElement .

# ── Properties ─────────────────────────────────────────────────────────────

ckg:channelKind a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "channel kind"@en ;
    rdfs:comment "Whether a channel is owned (the brand controls it) or paid (media is bought)."@en ;
    rdfs:domain ckg:Channel ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:category .

ckg:category a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "category"@en ;
    rdfs:comment "Menu category such as meal, bundle, side or dessert."@en ;
    rdfs:domain ckg:Product ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:category .

ckg:marginPct a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "margin percentage"@en ;
    rdfs:comment "Gross margin of the product, in percent of price."@en ;
    rdfs:domain ckg:Product ;
    rdfs:range xsd:integer .

ckg:lifecycleStage a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "lifecycle stage"@en ;
    rdfs:comment "Where the customer sits in the relationship: active, lapsing or lapsed."@en ;
    rdfs:domain ckg:Customer ;
    rdfs:range xsd:string .

ckg:loyaltyTier a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "loyalty tier"@en ;
    rdfs:comment "Loyalty programme tier: bronze, silver or gold."@en ;
    rdfs:domain ckg:Customer ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:memberOf .

ckg:homeCity a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "home city"@en ;
    rdfs:comment "The city the customer usually orders in. Residence, never birthplace."@en ;
    rdfs:domain ckg:Customer ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:homeLocation .

ckg:usualOrderDay a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "usual order day"@en ;
    rdfs:comment "The weekday on which the customer usually orders."@en ;
    rdfs:domain ckg:Customer ;
    rdfs:range xsd:string .

ckg:signalKind a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "signal kind"@en ;
    rdfs:comment "The kind of signal: email, loyalty_id, device, app_session or ad_exposure."@en ;
    rdfs:domain ckg:IdentitySignal ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:additionalType .

ckg:signalValue a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "signal value"@en ;
    rdfs:comment "A pseudonymous token for the signal, never a raw identifier."@en ;
    rdfs:domain ckg:IdentitySignal ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:value .

ckg:matchMethod a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "match method"@en ;
    rdfs:comment "How the signal was tied to a customer: deterministic, probabilistic, agentic, or unmatched."@en ;
    rdfs:domain ckg:IdentitySignal ;
    rdfs:range xsd:string .

ckg:confidence a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "confidence"@en ;
    rdfs:comment "Match confidence in percent; zero for an unmatched signal."@en ;
    rdfs:domain ckg:IdentitySignal ;
    rdfs:range xsd:integer .

ckg:seenDate a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "seen date"@en ;
    rdfs:comment "The date the signal was last observed."@en ;
    rdfs:domain ckg:IdentitySignal ;
    rdfs:range xsd:date ;
    skos:closeMatch schema:observationDate .

ckg:resolvesTo a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "resolves to"@en ;
    rdfs:comment "The customer a signal has been resolved to. Absent for unmatched signals."@en ;
    rdfs:domain ckg:IdentitySignal ;
    rdfs:range ckg:Customer ;
    skos:closeMatch schema:about .

ckg:consentStatus a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "consent status"@en ;
    rdfs:comment "granted or withdrawn."@en ;
    rdfs:domain ckg:ConsentRecord ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:status .

ckg:updatedDate a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "updated date"@en ;
    rdfs:comment "The date the consent status last changed."@en ;
    rdfs:domain ckg:ConsentRecord ;
    rdfs:range xsd:date ;
    skos:closeMatch schema:dateModified .

ckg:forChannel a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "for channel"@en ;
    rdfs:comment "The channel the consent applies to."@en ;
    rdfs:domain ckg:ConsentRecord ;
    rdfs:range ckg:Channel .

ckg:offerKind a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "offer kind"@en ;
    rdfs:comment "reminder, incentive or bundle-incentive."@en ;
    rdfs:domain ckg:Offer ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:category .

ckg:discountPct a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "discount percentage"@en ;
    rdfs:comment "Discount depth in percent of price."@en ;
    rdfs:domain ckg:Offer ;
    rdfs:range xsd:integer ;
    skos:closeMatch schema:discount .

ckg:promotes a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "promotes"@en ;
    rdfs:comment "The product an offer promotes, when it promotes one."@en ;
    rdfs:domain ckg:Offer ;
    rdfs:range ckg:Product ;
    skos:closeMatch schema:itemOffered .

ckg:ruleKind a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "rule kind"@en ;
    rdfs:comment "discount-cap, contact-frequency, consent-required or human-approval."@en ;
    rdfs:domain ckg:Guardrail ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:category .

ckg:limitValue a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "limit value"@en ;
    rdfs:comment "The threshold the rule enforces."@en ;
    rdfs:domain ckg:Guardrail ;
    rdfs:range xsd:integer ;
    skos:closeMatch schema:value .

ckg:appliesToOffer a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "applies to offer"@en ;
    rdfs:comment "Narrows a guardrail to one offer; absent means the rule is global."@en ;
    rdfs:domain ckg:Guardrail ;
    rdfs:range ckg:Offer .

ckg:goal a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "goal"@en ;
    rdfs:comment "The business outcome the campaign is meant to move."@en ;
    rdfs:domain ckg:Campaign ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:description .

ckg:startDate a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "start date"@en ;
    rdfs:comment "When the campaign started."@en ;
    rdfs:domain ckg:Campaign ;
    rdfs:range xsd:date ;
    skos:closeMatch schema:startDate .

ckg:arm a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "experiment arm"@en ;
    rdfs:comment "treatment, or holdout for the randomised group that is deliberately left alone."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range xsd:string .

ckg:actionKind a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "action kind"@en ;
    rdfs:comment "send-reminder, send-incentive, switch-channel, suppress or no-action."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:additionalType .

ckg:decidedDate a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "decided date"@en ;
    rdfs:comment "When the decision was taken."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range xsd:date ;
    skos:closeMatch schema:startTime .

ckg:rationale a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "rationale"@en ;
    rdfs:comment "Why the agent chose this action, in words a marketer can audit."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:description .

ckg:approvedByHuman a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "approved by human"@en ;
    rdfs:comment "1 when a person approved the action, 0 otherwise."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range xsd:integer .

ckg:groundedIn a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "grounded in"@en ;
    rdfs:comment "Super-property of every link from a decision to a thing it was grounded in: its customer, campaign, offer, channel and guardrails."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range ckg:ContextElement .

ckg:forCustomer a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "for customer"@en ;
    rdfs:comment "The customer a decision or consent record concerns."@en ;
    rdfs:domain ckg:ContextElement ;
    rdfs:range ckg:Customer ;
    rdfs:subPropertyOf ckg:groundedIn ;
    skos:closeMatch schema:about .

ckg:inCampaign a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "in campaign"@en ;
    rdfs:comment "The campaign a decision belongs to."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range ckg:Campaign ;
    rdfs:subPropertyOf ckg:groundedIn ;
    skos:closeMatch schema:isPartOf .

ckg:usesOffer a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "uses offer"@en ;
    rdfs:comment "The offer a decision extends. Absent for reminders without an offer, suppressions and holdouts."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range ckg:Offer ;
    rdfs:subPropertyOf ckg:groundedIn ;
    skos:closeMatch schema:object .

ckg:viaChannel a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "via channel"@en ;
    rdfs:comment "The channel a decision acts through. Absent for a suppression or a holdout."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range ckg:Channel ;
    rdfs:subPropertyOf ckg:groundedIn ;
    skos:closeMatch schema:instrument .

ckg:constrainedBy a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "constrained by"@en ;
    rdfs:comment "A guardrail that applied to the decision."@en ;
    rdfs:domain ckg:Decision ;
    rdfs:range ckg:Guardrail ;
    rdfs:subPropertyOf ckg:groundedIn .

ckg:contextType a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "context type"@en ;
    rdfs:comment "customer, business, decision or control: which of the four kinds of context the item is."@en ;
    rdfs:domain ckg:ContextItem ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:additionalType .

ckg:contextKey a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "context key"@en ;
    rdfs:comment "A short label for the fact."@en ;
    rdfs:domain ckg:ContextItem ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:name .

ckg:contextValue a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "context value"@en ;
    rdfs:comment "The fact itself."@en ;
    rdfs:domain ckg:ContextItem ;
    rdfs:range xsd:string ;
    skos:closeMatch schema:value .

ckg:informs a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "informs"@en ;
    rdfs:comment "The decision the item was read for."@en ;
    rdfs:domain ckg:ContextItem ;
    rdfs:range ckg:Decision .

ckg:ordered a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "ordered"@en ;
    rdfs:comment "1 when the customer ordered inside the measurement window, 0 otherwise."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:integer .

ckg:revenueUsd a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "revenue (USD)"@en ;
    rdfs:comment "Revenue from the order, in whole US dollars."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:integer ;
    skos:closeMatch schema:price .

ckg:marginUsd a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "margin (USD)"@en ;
    rdfs:comment "Gross margin on the order before discount and media costs."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:integer .

ckg:mediaCostUsd a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "media cost (USD)"@en ;
    rdfs:comment "Paid-media cost attributed to the decision."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:integer .

ckg:discountCostUsd a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "discount cost (USD)"@en ;
    rdfs:comment "Discount given on the order."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:integer .

ckg:repeatVisit30d a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "repeat visit within 30 days"@en ;
    rdfs:comment "1 when the customer returned within 30 days of the order."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:integer .

ckg:windowEnd a owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "window end"@en ;
    rdfs:comment "The last day of the measurement window."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range xsd:date ;
    skos:closeMatch schema:endTime .

ckg:ofDecision a owl:ObjectProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/> ;
    rdfs:label "of decision"@en ;
    rdfs:comment "The decision whose result this is."@en ;
    rdfs:domain ckg:Outcome ;
    rdfs:range ckg:Decision ."""@en .

:sqlQuadMap a schema:SoftwareSourceCode ;
    schema:category "RDF View generation"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "The RDF View: one ALTER QUAD STORAGE statement"@en ;
    schema:position 5 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "One statement maps all twelve tables to 1,160 triples at query time in the named graph http://demo.openlinksw.com/CustomerKG#. Every table in the FROM list is aliased, five nullable foreign keys carry an IS NOT NULL guard, and the link table maps the constrainedBy predicate."@en ;
    schema:text """SPARQL
ALTER QUAD STORAGE virtrdf:DefaultQuadStorage
  FROM CustomerKG.kidehen.channel AS ch
  FROM CustomerKG.kidehen.product AS pr
  FROM CustomerKG.kidehen.customer AS cu
  FROM CustomerKG.kidehen.identity_signal AS sg
  FROM CustomerKG.kidehen.consent AS co
  FROM CustomerKG.kidehen.offer AS ofr
  FROM CustomerKG.kidehen.guardrail AS gr
  FROM CustomerKG.kidehen.campaign AS ca
  FROM CustomerKG.kidehen.decision AS de
  FROM CustomerKG.kidehen.decision_guardrail AS dg
  FROM CustomerKG.kidehen.context_item AS ci
  FROM CustomerKG.kidehen.outcome AS ou
{
  GRAPH <http://demo.openlinksw.com/CustomerKG#>
  {
    <http://demo.openlinksw.com/schemas/CustomerKG/channel_iri>(ch.channel_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Channel> as virtrdf:CustomerKG-channel-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/channel_iri>(ch.channel_id) <http://schema.org/name> ch.channel_name as virtrdf:CustomerKG-channel-name .
    <http://demo.openlinksw.com/schemas/CustomerKG/channel_iri>(ch.channel_id) <http://demo.openlinksw.com/schemas/CustomerKG/channelKind> ch.channel_kind as virtrdf:CustomerKG-channel-kind .
    <http://demo.openlinksw.com/schemas/CustomerKG/product_iri>(pr.product_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Product> as virtrdf:CustomerKG-product-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/product_iri>(pr.product_id) <http://schema.org/name> pr.product_name as virtrdf:CustomerKG-product-name .
    <http://demo.openlinksw.com/schemas/CustomerKG/product_iri>(pr.product_id) <http://demo.openlinksw.com/schemas/CustomerKG/category> pr.category as virtrdf:CustomerKG-product-category .
    <http://demo.openlinksw.com/schemas/CustomerKG/product_iri>(pr.product_id) <http://demo.openlinksw.com/schemas/CustomerKG/marginPct> pr.margin_pct as virtrdf:CustomerKG-product-margin .
    <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(cu.customer_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Customer> as virtrdf:CustomerKG-customer-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(cu.customer_id) <http://schema.org/name> cu.full_name as virtrdf:CustomerKG-customer-name .
    <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(cu.customer_id) <http://demo.openlinksw.com/schemas/CustomerKG/lifecycleStage> cu.lifecycle_stage as virtrdf:CustomerKG-customer-lifecycle .
    <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(cu.customer_id) <http://demo.openlinksw.com/schemas/CustomerKG/loyaltyTier> cu.loyalty_tier as virtrdf:CustomerKG-customer-tier .
    <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(cu.customer_id) <http://demo.openlinksw.com/schemas/CustomerKG/homeCity> cu.home_city as virtrdf:CustomerKG-customer-city .
    <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(cu.customer_id) <http://demo.openlinksw.com/schemas/CustomerKG/usualOrderDay> cu.usual_order_day as virtrdf:CustomerKG-customer-day .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/IdentitySignal> as virtrdf:CustomerKG-signal-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://demo.openlinksw.com/schemas/CustomerKG/signalKind> sg.signal_kind as virtrdf:CustomerKG-signal-kind .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://demo.openlinksw.com/schemas/CustomerKG/signalValue> sg.signal_value as virtrdf:CustomerKG-signal-value .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://demo.openlinksw.com/schemas/CustomerKG/matchMethod> sg.match_method as virtrdf:CustomerKG-signal-method .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://demo.openlinksw.com/schemas/CustomerKG/confidence> sg.confidence as virtrdf:CustomerKG-signal-confidence .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://demo.openlinksw.com/schemas/CustomerKG/seenDate> sg.seen_date as virtrdf:CustomerKG-signal-seen .
    <http://demo.openlinksw.com/schemas/CustomerKG/signal_iri>(sg.signal_id) <http://demo.openlinksw.com/schemas/CustomerKG/resolvesTo> <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(sg.customer_id) where (^{sg.}^.customer_id is not null) as virtrdf:CustomerKG-signal-customer .
    <http://demo.openlinksw.com/schemas/CustomerKG/consent_iri>(co.consent_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/ConsentRecord> as virtrdf:CustomerKG-consent-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/consent_iri>(co.consent_id) <http://demo.openlinksw.com/schemas/CustomerKG/consentStatus> co.status as virtrdf:CustomerKG-consent-status .
    <http://demo.openlinksw.com/schemas/CustomerKG/consent_iri>(co.consent_id) <http://demo.openlinksw.com/schemas/CustomerKG/updatedDate> co.updated_date as virtrdf:CustomerKG-consent-updated .
    <http://demo.openlinksw.com/schemas/CustomerKG/consent_iri>(co.consent_id) <http://demo.openlinksw.com/schemas/CustomerKG/forCustomer> <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(co.customer_id) as virtrdf:CustomerKG-consent-customer .
    <http://demo.openlinksw.com/schemas/CustomerKG/consent_iri>(co.consent_id) <http://demo.openlinksw.com/schemas/CustomerKG/forChannel> <http://demo.openlinksw.com/schemas/CustomerKG/channel_iri>(co.channel_id) as virtrdf:CustomerKG-consent-channel .
    <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(ofr.offer_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Offer> as virtrdf:CustomerKG-offer-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(ofr.offer_id) <http://schema.org/name> ofr.offer_name as virtrdf:CustomerKG-offer-name .
    <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(ofr.offer_id) <http://demo.openlinksw.com/schemas/CustomerKG/offerKind> ofr.offer_kind as virtrdf:CustomerKG-offer-kind .
    <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(ofr.offer_id) <http://demo.openlinksw.com/schemas/CustomerKG/discountPct> ofr.discount_pct as virtrdf:CustomerKG-offer-discount .
    <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(ofr.offer_id) <http://demo.openlinksw.com/schemas/CustomerKG/promotes> <http://demo.openlinksw.com/schemas/CustomerKG/product_iri>(ofr.product_id) where (^{ofr.}^.product_id is not null) as virtrdf:CustomerKG-offer-product .
    <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri>(gr.guardrail_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Guardrail> as virtrdf:CustomerKG-guardrail-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri>(gr.guardrail_id) <http://schema.org/name> gr.rule_name as virtrdf:CustomerKG-guardrail-name .
    <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri>(gr.guardrail_id) <http://demo.openlinksw.com/schemas/CustomerKG/ruleKind> gr.rule_kind as virtrdf:CustomerKG-guardrail-kind .
    <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri>(gr.guardrail_id) <http://demo.openlinksw.com/schemas/CustomerKG/limitValue> gr.limit_value as virtrdf:CustomerKG-guardrail-limit .
    <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri>(gr.guardrail_id) <http://demo.openlinksw.com/schemas/CustomerKG/appliesToOffer> <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(gr.offer_id) where (^{gr.}^.offer_id is not null) as virtrdf:CustomerKG-guardrail-offer .
    <http://demo.openlinksw.com/schemas/CustomerKG/campaign_iri>(ca.campaign_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Campaign> as virtrdf:CustomerKG-campaign-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/campaign_iri>(ca.campaign_id) <http://schema.org/name> ca.campaign_name as virtrdf:CustomerKG-campaign-name .
    <http://demo.openlinksw.com/schemas/CustomerKG/campaign_iri>(ca.campaign_id) <http://demo.openlinksw.com/schemas/CustomerKG/goal> ca.goal as virtrdf:CustomerKG-campaign-goal .
    <http://demo.openlinksw.com/schemas/CustomerKG/campaign_iri>(ca.campaign_id) <http://demo.openlinksw.com/schemas/CustomerKG/startDate> ca.start_date as virtrdf:CustomerKG-campaign-start .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Decision> as virtrdf:CustomerKG-decision-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/arm> de.arm as virtrdf:CustomerKG-decision-arm .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/actionKind> de.action_kind as virtrdf:CustomerKG-decision-action .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/decidedDate> de.decided_date as virtrdf:CustomerKG-decision-date .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/rationale> de.rationale as virtrdf:CustomerKG-decision-rationale .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/approvedByHuman> de.approved_by_human as virtrdf:CustomerKG-decision-approved .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/forCustomer> <http://demo.openlinksw.com/schemas/CustomerKG/customer_iri>(de.customer_id) as virtrdf:CustomerKG-decision-customer .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/inCampaign> <http://demo.openlinksw.com/schemas/CustomerKG/campaign_iri>(de.campaign_id) as virtrdf:CustomerKG-decision-campaign .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/usesOffer> <http://demo.openlinksw.com/schemas/CustomerKG/offer_iri>(de.offer_id) where (^{de.}^.offer_id is not null) as virtrdf:CustomerKG-decision-offer .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(de.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/viaChannel> <http://demo.openlinksw.com/schemas/CustomerKG/channel_iri>(de.channel_id) where (^{de.}^.channel_id is not null) as virtrdf:CustomerKG-decision-channel .
    <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(dg.decision_id) <http://demo.openlinksw.com/schemas/CustomerKG/constrainedBy> <http://demo.openlinksw.com/schemas/CustomerKG/guardrail_iri>(dg.guardrail_id) as virtrdf:CustomerKG-decision-guardrail .
    <http://demo.openlinksw.com/schemas/CustomerKG/context_iri>(ci.item_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/ContextItem> as virtrdf:CustomerKG-context-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/context_iri>(ci.item_id) <http://demo.openlinksw.com/schemas/CustomerKG/contextType> ci.context_type as virtrdf:CustomerKG-context-kind .
    <http://demo.openlinksw.com/schemas/CustomerKG/context_iri>(ci.item_id) <http://demo.openlinksw.com/schemas/CustomerKG/contextKey> ci.context_key as virtrdf:CustomerKG-context-key .
    <http://demo.openlinksw.com/schemas/CustomerKG/context_iri>(ci.item_id) <http://demo.openlinksw.com/schemas/CustomerKG/contextValue> ci.context_value as virtrdf:CustomerKG-context-value .
    <http://demo.openlinksw.com/schemas/CustomerKG/context_iri>(ci.item_id) <http://demo.openlinksw.com/schemas/CustomerKG/informs> <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(ci.decision_id) as virtrdf:CustomerKG-context-decision .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/CustomerKG/Outcome> as virtrdf:CustomerKG-outcome-type .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/ordered> ou.ordered as virtrdf:CustomerKG-outcome-ordered .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/revenueUsd> ou.revenue_usd as virtrdf:CustomerKG-outcome-revenue .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/marginUsd> ou.margin_usd as virtrdf:CustomerKG-outcome-margin .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/mediaCostUsd> ou.media_cost_usd as virtrdf:CustomerKG-outcome-media .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/discountCostUsd> ou.discount_cost_usd as virtrdf:CustomerKG-outcome-discount .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/repeatVisit30d> ou.repeat_visit_30d as virtrdf:CustomerKG-outcome-repeat .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/windowEnd> ou.window_end as virtrdf:CustomerKG-outcome-window .
    <http://demo.openlinksw.com/schemas/CustomerKG/outcome_iri>(ou.outcome_id) <http://demo.openlinksw.com/schemas/CustomerKG/ofDecision> <http://demo.openlinksw.com/schemas/CustomerKG/decision_iri>(ou.decision_id) as virtrdf:CustomerKG-outcome-decision .
  }
} ;"""@en .

:sqlRewriteRules a schema:SoftwareSourceCode ;
    schema:category "Linked Data deployment"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "URL rewrite rules: making the entity IRIs resolve"@en ;
    schema:position 6 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "The RDF View defines the IRIs but does not by itself make them resolve. The generator RDFVIEW_GENERATE_DATA_RULES was called with iri_path_segment CustomerKG and include_ontology_rules 1 and returned these fourteen statements: six regular-expression rules, two rule lists and two virtual directories, /CustomerKG and /schemas/CustomerKG. After they ran, an entity IRI answers with a 303 to a describe page."@en ;
    schema:text """DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_rule2',
1,
'(/[^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^%U%%23this%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/CustomerKG%%23%%3E&format=%U',
vector('path', '*accept*'),
null,
'(text/rdf.n3)|(application/rdf.xml)|(text/n3)|(application/json)|(text/turtle)',
2,
null
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_rule4',
1,
'/CustomerKG/stat([^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^/CustomerKG/stat%%23%%3E+%%3Fo+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/CustomerKG%%23%%3E+WHERE+{+%%3Chttp%%3A//^{URIQADefaultHost}^/CustomerKG/stat%%23%%3E+%%3Fp+%%3Fo+}&format=%U',
vector('*accept*'),
null,
'(text/rdf.n3)|(application/rdf.xml)|(text/n3)|(application/json)|(text/turtle)',
2,
null
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_rule6',
1,
'/CustomerKG/objects/([^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^/CustomerKG/objects/%U%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/CustomerKG%%23%%3E&format=%U',
vector('path', '*accept*'),
null,
'(text/rdf.n3)|(application/rdf.xml)|(text/n3)|(application/json)|(text/turtle)',
2,
null
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_rule1',
1,
'([^#]*)',
vector('path'),
1,
'/describe/?url=http%%3A//^{URIQADefaultHost}^%U%%23this&graph=http%%3A//^{URIQADefaultHost}^/CustomerKG%%23&distinct=0',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_rule7',
1,
'/CustomerKG/stat([^#]*)',
vector('path'),
1,
'/describe/?url=http%%3A//^{URIQADefaultHost}^/CustomerKG/stat%%23&graph=http%%3A//^{URIQADefaultHost}^/CustomerKG%%23',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_rule5',
1,
'/CustomerKG/objects/(.*)',
vector('path'),
1,
'/services/rdf/object.binary?path=%%2FCustomerKG%%2Fobjects%%2F%U&accept=%U',
vector('path', '*accept*'),
null,
null,
2,
null
);
DB.DBA.URLREWRITE_CREATE_RULELIST ( 'customerkg_rule_list1', 1, vector ( 'customerkg_rule1', 'customerkg_rule7', 'customerkg_rule5', 'customerkg_rule2', 'customerkg_rule4', 'customerkg_rule6'));
DB.DBA.VHOST_REMOVE (lpath=>'/CustomerKG');
DB.DBA.VHOST_DEFINE (lpath=>'/CustomerKG', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'customerkg_rule_list1')
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_owl_rule2',
1,
'(/[^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^%U%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/schemas/CustomerKG%%23%%3E&format=%U',
vector('path', '*accept*'),
null,
'(text/rdf.n3)|(application/rdf.xml)|(text/n3)|(application/json)|(text/turtle)',
2,
null
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'customerkg_owl_rule1',
1,
'([^#]*)',
vector('path'),
1,
'/describe/?url=http://^{URIQADefaultHost}^%U',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_RULELIST ( 'customerkg_owl_rule_list1', 1, vector ( 'customerkg_owl_rule1', 'customerkg_owl_rule2'));
DB.DBA.VHOST_REMOVE (lpath=>'/schemas/CustomerKG');
DB.DBA.VHOST_DEFINE (lpath=>'/schemas/CustomerKG', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'customerkg_owl_rule_list1')
);"""@en .

:sqlIdentityGraph a schema:SoftwareSourceCode ;
    schema:category "RDF View generation"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "The identity graph: reconciliation by reasoning"@en ;
    schema:position 7 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "A small physical graph for identity reconciliation. A vocabulary declares ckg:emailHash an owl:InverseFunctionalProperty, a rule set is built from it, customer facts are derived from the live view with SPARQL INSERT ... WHERE, and records from other systems are added: two that share a customer's email hash, one linked by an explicit owl:sameAs, and one that matches nobody. It is separate from the view because owl:sameAs merging works over physical graphs. Query 3 reads it."@en ;
    schema:text """-- 1. Vocabulary: declare the identifying property inverse functional, in its own graph
DB.DBA.TTLP(file_to_string_output('customerkg-identity-vocabulary-claude_sonnet_5_5-1.ttl'), '', 'http://demo.openlinksw.com/schemas/CustomerKG/identity/');
-- The Turtle loaded by that call:
@prefix ckg: <http://demo.openlinksw.com/schemas/CustomerKG/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://demo.openlinksw.com/schemas/CustomerKG/identity/> a owl:Ontology ;
    rdfs:label "CustomerKG identity reconciliation vocabulary"@en .

ckg:emailHash a owl:InverseFunctionalProperty, owl:DatatypeProperty ;
    rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/CustomerKG/identity/> ;
    rdfs:label "email hash"@en ;
    rdfs:comment "A pseudonymous hash of an email address. Two records that carry the same hash describe the same person, so the property is declared inverse functional and a reasoner derives owl:sameAs between them."@en ;
    rdfs:domain schema:Person ;
    rdfs:range xsd:string .

-- 2. Rule set: derive reasoning rules from that vocabulary
DB.DBA.rdfs_rule_set('urn:customerkg:identity-inference', 'http://demo.openlinksw.com/schemas/CustomerKG/identity/');

-- 3. Identity graph, part one: facts derived from the live RDF View (nothing is retyped)
SPARQL PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/> PREFIX schema: <http://schema.org/>
INSERT INTO GRAPH <http://demo.openlinksw.com/CustomerKG/identity#> { ?c ckg:emailHash ?v }
WHERE { GRAPH <http://demo.openlinksw.com/CustomerKG#> { ?s a ckg:IdentitySignal ; ckg:signalKind "email" ; ckg:signalValue ?v ; ckg:resolvesTo ?c } };

SPARQL PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/> PREFIX schema: <http://schema.org/>
INSERT INTO GRAPH <http://demo.openlinksw.com/CustomerKG/identity#> { ?c a schema:Person ; schema:name ?n }
WHERE { GRAPH <http://demo.openlinksw.com/CustomerKG#> { ?c a ckg:Customer ; schema:name ?n } };

-- 4. Identity graph, part two: records from other systems, one explicit owl:sameAs, one record that matches nobody
SPARQL PREFIX ckg: <http://demo.openlinksw.com/schemas/CustomerKG/> PREFIX schema: <http://schema.org/> PREFIX owl: <http://www.w3.org/2002/07/owl#>
INSERT DATA INTO GRAPH <http://demo.openlinksw.com/CustomerKG/identity#> {
  <http://demo.openlinksw.com/CustomerKG/external/crm/501#this> a schema:Person ; schema:name "A. Quinn (CRM export)" ; ckg:emailHash "hem:a1f3" .
  <http://demo.openlinksw.com/CustomerKG/external/app/77#this> a schema:Person ; schema:name "Avery Q. (app profile)" ; ckg:emailHash "hem:a1f3" .
  <http://demo.openlinksw.com/CustomerKG/external/crm/502#this> a schema:Person ; schema:name "B. Moreno (CRM export)" ; ckg:emailHash "hem:b2c4" .
  <http://demo.openlinksw.com/CustomerKG/external/crm/599#this> a schema:Person ; schema:name "Unmatched (CRM export)" ; ckg:emailHash "hem:ffff" .
  <http://demo.openlinksw.com/CustomerKG/external/loyalty/LY-10001#this> a schema:Person ; schema:name "Loyalty member LY-10001" ; owl:sameAs <http://demo.openlinksw.com/CustomerKG/customer/1#this> .
};"""@en .

:sqlGrants a schema:SoftwareSourceCode ;
    schema:category "DDL"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "Access: the view inherits SQL grants"@en ;
    schema:position 8 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "An RDF View has no access rules of its own; it inherits the SQL privileges of its source tables. SPARQL serves the anonymous endpoint, PUBLIC the rest, and demo and vdb the Query Builder and XMLA routes."@en ;
    schema:text """GRANT SELECT ON CustomerKG.kidehen.channel TO "SPARQL";
GRANT SELECT ON CustomerKG.kidehen.channel TO PUBLIC;
GRANT SELECT ON CustomerKG.kidehen.channel TO "demo";
GRANT SELECT ON CustomerKG.kidehen.channel TO "vdb";
-- ...repeated for each of the 12 tables (48 statements in the batch script)"""@en .

:odbcDsn a schema:SoftwareSourceCode ;
    schema:category "Connection configuration"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "The ODBC data source for a Databricks SQL warehouse"@en ;
    schema:position 9 ;
    schema:programmingLanguage "Config"@en ;
    schema:description "The shape of a DSN for the Databricks ODBC driver. Placeholders stand where the workspace host, HTTP path and token go; the token belongs in a credential store and never in a file that is committed. Not executed in this session; the existing databricks_odbc data source on demo was not modified."@en ;
    schema:text """; odbc.ini (iODBC or unixODBC). Placeholders in angle brackets; keep the token in a credential store, not in this file.
[databricks_odbc]
Driver  = <path to the Databricks ODBC driver library>
Host    = <workspace-host>
Port    = 443
HTTPPath= <SQL warehouse HTTP path>
SSL     = 1
ThriftTransport = 2
AuthMech= 3
UID     = token
PWD     = <personal access token>"""@en .

:odbcAttach a schema:SoftwareSourceCode ;
    schema:category "DDL"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "Attach the Databricks table to Virtuoso"@en ;
    schema:position 10 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "The documented form of ATTACH TABLE: the remote name, an optional primary key, a local name and the data source. Once attached, the table answers SQL, appears in the schema browser and can be the subject of an RDF View. Shown as the shape of the statement; this session did not attach new tables."@en ;
    schema:text """-- Shape of the statement: remote name, key, local name, data source.
-- Run in isql or Conductor as an account allowed to register remote tables.
ATTACH TABLE bakehouse.sales_transactions PRIMARY KEY (transactionID)
  AS "databricks"."bakehouse"."sales_transactions_viritual_odbc"
  FROM 'databricks_odbc' ;

-- Then give the SPARQL account read access, as for any other table:
GRANT SELECT ON "databricks"."bakehouse"."sales_transactions_viritual_odbc" TO "SPARQL" ;"""@en .

:odbcMetadata a schema:SoftwareSourceCode ;
    schema:category "Catalog reads"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "What demo already holds: metadata read, rows untouched"@en ;
    schema:position 11 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "Two read-only queries against Virtuoso's own catalog tables, which make no call to Databricks: the remote-table registry that shows databricks_odbc and the remote name behind the attached table, and its ten column names and types."@en ;
    schema:text """-- 1. Which data source and remote name sit behind the attached tables (catalog read; no call to Databricks)
SELECT RT_NAME AS local_table, RT_DSN AS data_source, RT_REMOTE_NAME AS remote_name
FROM DB.DBA.SYS_REMOTE_TABLE
WHERE lower(RT_NAME) LIKE 'databricks.%'
ORDER BY 1;

-- 2. The ten columns of the attached Bakehouse table (catalog read; no call to Databricks)
SELECT "COLUMN" AS column_name, COL_DTP AS type_code, COL_PREC AS precision
FROM DB.DBA.SYS_COLS
WHERE "TABLE" = 'databricks.bakehouse.sales_transactions_viritual_odbc'
ORDER BY COL_ID;"""@en .

:odbcRdfView a schema:SoftwareSourceCode ;
    schema:category "RDF View generation"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "The RDF View the generator proposes for the attached table"@en ;
    schema:position 12 ;
    schema:programmingLanguage "SPARQL"@en ;
    schema:description "Output of RDFVIEW_FROM_TABLES for databricks.bakehouse.sales_transactions_viritual_odbc with iri_path_segment DbxSales, generated from the live table metadata and shown verbatim, not executed. Note that it maps every column, including cardNumber, as a predicate; a real deployment would drop that column and likely customerID and transactionID too, or pseudonymise them first."@en ;
    schema:text """SPARQL
prefix DbxSales: <http://demo.openlinksw.com/schemas/DbxSales/> 
create iri class DbxSales:sales_transactions_viritual_odbc "http://^{URIQADefaultHost}^/DbxSales/sales_transactions_viritual_odbc/transactionID/%ld#this" (in _transactionID integer not null) . ;


SPARQL
prefix DbxSales: <http://demo.openlinksw.com/schemas/DbxSales/> 
prefix aowl: <http://bblfish.net/work/atom-owl/2006-06-06/> 
alter quad storage virtrdf:DefaultQuadStorage 
 from "databricks"."bakehouse"."sales_transactions_viritual_odbc" as sales_transactions_viritual_odbc_s
 { 
   create DbxSales:qm-sales_transactions_viritual_odbc as graph iri ("http://^{URIQADefaultHost}^/DbxSales#") option (exclusive) 
    { 
      # Maps from columns of "databricks.bakehouse.sales_transactions_viritual_odbc"
      DbxSales:sales_transactions_viritual_odbc (sales_transactions_viritual_odbc_s."transactionID")  a DbxSales:sales_transactions_viritual_odbc ;
      DbxSales:transactionid sales_transactions_viritual_odbc_s."transactionID" as DbxSales:bakehouse-sales_transactions_viritual_odbc-transactionid ;
      DbxSales:customerid sales_transactions_viritual_odbc_s."customerID" as DbxSales:bakehouse-sales_transactions_viritual_odbc-customerid ;
      DbxSales:franchiseid sales_transactions_viritual_odbc_s."franchiseID" as DbxSales:bakehouse-sales_transactions_viritual_odbc-franchiseid ;
      DbxSales:datetime sales_transactions_viritual_odbc_s."dateTime" as DbxSales:bakehouse-sales_transactions_viritual_odbc-datetime ;
      DbxSales:product sales_transactions_viritual_odbc_s."product" as DbxSales:bakehouse-sales_transactions_viritual_odbc-product ;
      DbxSales:quantity sales_transactions_viritual_odbc_s."quantity" as DbxSales:bakehouse-sales_transactions_viritual_odbc-quantity ;
      DbxSales:unitprice sales_transactions_viritual_odbc_s."unitPrice" as DbxSales:bakehouse-sales_transactions_viritual_odbc-unitprice ;
      DbxSales:totalprice sales_transactions_viritual_odbc_s."totalPrice" as DbxSales:bakehouse-sales_transactions_viritual_odbc-totalprice ;
      DbxSales:paymentmethod sales_transactions_viritual_odbc_s."paymentMethod" as DbxSales:bakehouse-sales_transactions_viritual_odbc-paymentmethod ;
      DbxSales:cardnumber sales_transactions_viritual_odbc_s."cardNumber" as DbxSales:bakehouse-sales_transactions_viritual_odbc-cardnumber .

    }
 }

;"""@en .

:odbcRules a schema:SoftwareSourceCode ;
    schema:category "Linked Data deployment"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "Ontology and rewrite rules for the attached table"@en ;
    schema:position 13 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "RDFVIEW_ONTOLOGY_FROM_TABLES and RDFVIEW_GENERATE_DATA_RULES were also called for the same table. The ontology declares one class and ten datatype properties with ranges, and the rules have exactly the shape of the fourteen statements in the CustomerKG card, with the prefix dbxsales_ and the path segment DbxSales. Generated and not executed."@en ;
    schema:text """-- Called on demo with iri_path_segment = 'DbxSales' and tables = ['databricks.bakehouse.sales_transactions_viritual_odbc'].
-- RDFVIEW_ONTOLOGY_FROM_TABLES returned one rdfs:Class and ten owl:DatatypeProperty terms with xsd ranges.
-- RDFVIEW_GENERATE_DATA_RULES (include_ontology_rules = 1) returned fourteen statements. The first and the last of the data rules:
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'dbxsales_rule1', 1, '([^#]*)', vector('path'), 1,
'/describe/?url=http%%3A//^{URIQADefaultHost}^%U%%23this&graph=http%%3A//^{URIQADefaultHost}^/DbxSales%%23&distinct=0',
vector('path'), null, null, 2, 303
);
DB.DBA.VHOST_DEFINE (lpath=>'/DbxSales', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'dbxsales_rule_list1')
);
-- Not executed: running them would publish the attached table's ten columns, including cardNumber, as Linked Data."""@en .

:sqlBatch a schema:SoftwareSourceCode ;
    schema:category "DDL"@en ;
    schema:runtimePlatform :demoServer ;
    schema:name "One script: tables, rows, IRI classes, ontology step, RDF View, grants, rewrite rules"@en ;
    schema:position 20 ;
    schema:programmingLanguage "SQL"@en ;
    schema:description "The consolidated copy-paste script for running the whole CustomerKG demonstration on your own Virtuoso. Its purpose is execution, not exposition: the same statements as the cards above in the order a fresh database needs them. The table and row statements are standard SQL; the IRI class, quad map and rewrite-rule statements are Virtuoso-specific and are the part most likely to need adjustment on another version."@en ;
    schema:url <https://linkeddata.uriburner.com/DAV/demos/daas/customerkg-agentic-marketing.sql> .


# ═══════════════════════════════════════════════════════════════════════════
# Findings from the live deployment
# ═══════════════════════════════════════════════════════════════════════════

:findingSq142 a schema:HowToTip ;
    schema:name "Four query shapes raised SQ142 or SQ200 on this build; the working shapes are narrower"@en ;
    schema:position 1 ;
    schema:text "ORDER BY over a pattern that touches the link-table predicate constrainedBy raised SQ142; an aggregate SPARQL query with conditional sums by arm raised SQ142 even run directly; a SQL aggregate over a derived SPARQL table raised it too, while the same table read with SELECT star or with five-variable row arithmetic worked; and under the entailment pragma a variable class raised SQ200 and COUNT raised SQ142. The working forms are plain SQL aggregates over the native tables, row-level SPASQL and SELECT DISTINCT lists. Verified by running each shape, not by reading documentation."@en .

:findingInferenceDuplicates a schema:HowToTip ;
    schema:name "Under the entailment pragma a plain pattern returned 68 rows for 5 guardrails"@en ;
    schema:position 2 ;
    schema:text "With the rule set active, a query for guardrails with their names returned 68 solutions, because the inference expansion reaches the same quad-map rows by several routes. SELECT DISTINCT returned the correct 5. The same applies to the 43 controls in Query 6, which also needs DISTINCT. Counting under the pragma was not reliable on this build, so the page lists members."@en .

:findingNullKeys a schema:HowToTip ;
    schema:name "Nullable foreign keys carry an IS NOT NULL guard from the first deployment"@en ;
    schema:position 3 ;
    schema:text "Five foreign keys are nullable by design: the customer on an identity signal, the product on an offer, the offer a guardrail applies to, and the offer and channel on a decision. Each mapping has where (^{alias.}^.column is not null), and the deployed view held exactly 1,160 triples, the same number a hand count from the source rows gives. An earlier estate on the same server projected ten spurious triples before the guard was added."@en .

:findingDereference a schema:HowToTip ;
    schema:name "The rewrite rules were installed in the same pass as the view"@en ;
    schema:position 4 ;
    schema:text "RDFVIEW_GENERATE_DATA_RULES returned fourteen statements, which ran through SELECT exec(). Afterwards /CustomerKG/customer/3 and /CustomerKG/decision/3 answered with a 303 to the describe page, and so did a term in the ontology namespace. A view without these rules defines IRIs that return 404."@en .

:findingExistingDsn a schema:HowToTip ;
    schema:name "Demo already holds an attached Databricks data source, which made the complementary path demonstrable without a new connection"@en ;
    schema:position 5 ;
    schema:text "Reading the catalog showed a databricks_odbc data source and ten attached or virtual tables under the databricks qualifier, one of which maps a remote Bakehouse table over ODBC. This session read column metadata and generated scripts from it, and did not query its rows or change it. The generator mapped all ten columns, including cardNumber, as predicates."@en .

:findingIdentityEndpoint a schema:HowToTip ;
    schema:name "Identity reasoning over the anonymous HTTP endpoint returned 100,000 repeated rows where the signed-in route returned 7"@en ;
    schema:position 6 ;
    schema:text "The reconciliation query returned 7 rows in half a second over XMLA, and the same text on the anonymous HTTP endpoint returned the 100,000-row cap of repeats for one customer; with DISTINCT it did not return within five minutes. The page therefore offers Query 3 only through the Query Builder. Separately, owl:sameAs merging did not reach across the RDF View: a join of the view's customers to sameAs-linked records returned nothing, so the customer facts were copied into a physical graph with SPARQL INSERT ... WHERE. Inverse-functional-property reasoning was confirmed by contrast: with only input:same-as a customer had two names, and with the rule set it had four."@en .

:findingWritePath a schema:HowToTip ;
    schema:name "The demo server's write path for DDL was SELECT exec() over authenticated XMLA, and it dropped connections under load"@en ;
    schema:position 7 ;
    schema:text "The XMLA endpoint rejects non-SELECT statements directly, but SELECT exec('statement') runs DDL and DML as the authenticated user. Each IRI class took tens of seconds to minutes on the shared server, so completion was checked in the virtrdf schema graph. Several reads failed with a closed connection and succeeded on retry, so one failed fetch is not evidence of a broken query."@en .

:liveDemoSection schema:hasPart :findingSq142, :findingInferenceDuplicates, :findingNullKeys, :findingDereference, :findingIdentityEndpoint, :findingWritePath, :sqlIdentityGraph, :identityGraphDataset, :sqlSchema, :sqlData, :sqlIriClasses, :sqlOntology, :sqlQuadMap, :sqlRewriteRules, :sqlGrants, :sqlBatch, :customerKgDataset, :customerKgOntology, :customerKgView, :queryQ1, :queryQ2, :queryQ3, :queryQ4, :queryQ5, :queryQ6, :queryQ7, :queryQ8, :queryQ9, :queryQ10, :queryQ11, :queryQ12, :queryQ13, :queryQ14 .
:odbcSection schema:hasPart :findingExistingDsn, :odbcDsn, :odbcAttach, :odbcMetadata, :odbcRdfView, :odbcRules, :attachedBakehouseTable .


# ═══════════════════════════════════════════════════════════════════════════
# Concept terms
# ═══════════════════════════════════════════════════════════════════════════

:agenticMarketing a schema:DefinedTerm ;
    schema:name "Agentic marketing"@en ;
    schema:description "Marketing in which AI agents grounded in trusted customer, business and decision context recommend or take the next best action for each customer in near-real-time, within goals and guardrails that marketers define, with each action measured against what would have happened without it."@en ;
    schema:inDefinedTermSet :glossarySet .

:identityResolution a schema:DefinedTerm ;
    schema:name "Identity resolution"@en ;
    schema:description "Recognising that records, devices, sessions and ad exposures belong to one customer. In the article it is a continuous capability that supports action, not a one-off profile-building project."@en ;
    schema:inDefinedTermSet :glossarySet .

:incrementality a schema:DefinedTerm ;
    schema:name "Incrementality"@en ;
    schema:description "The difference in revenue, margin or customer behaviour compared with taking no action. It separates outcomes marketing was associated with from outcomes that changed because the brand acted."@en ;
    schema:inDefinedTermSet :glossarySet .


# ═══════════════════════════════════════════════════════════════════════════
# FAQ
# ═══════════════════════════════════════════════════════════════════════════

:faqPage a schema:FAQPage ;
    schema:name "Frequently Asked Questions"@en ;
    schema:mainEntity :faqQ1, :faqQ2, :faqQ3, :faqQ4, :faqQ5, :faqQ6, :faqQ7, :faqQ8, :faqQ9, :faqQ10, :faqQ11, :faqQ12, :faqQ13, :faqQ14, :faqQ15, :faqQ16 .

:faqQ1 a schema:Question ;
    schema:name "What does the article mean by agentic marketing?"@en ;
    schema:position 1 ;
    schema:acceptedAnswer :faqA1 .

:faqA1 a schema:Answer ;
    schema:text "AI agents that evaluate current customer and business context to choose the next best action within human-defined guardrails, including the option to take no action, and that use measured outcomes to improve future decisions. The article contrasts it with marketing automation, which executes rules and journeys that people design in advance."@en .

:faqQ2 a schema:Question ;
    schema:name "Why does the article put identity resolution first?"@en ;
    schema:position 2 ;
    schema:acceptedAnswer :faqA2 .

:faqA2 a schema:Answer ;
    schema:text "Because an agent can only act as well as the context it is given, and identity is what holds that context together. The article says identity has to be maintained continuously, connecting known records with pseudonymous signals such as anonymous app sessions or ad exposure without compromising privacy, governance or trust."@en .

:faqQ3 a schema:Question ;
    schema:name "What are the four kinds of context an agent needs?"@en ;
    schema:position 3 ;
    schema:acceptedAnswer :faqA3 .

:faqA3 a schema:Answer ;
    schema:text "Customer context (identity, behaviour, preferences, consent status, lifecycle stage), business context (growth objectives, margin, brand rules, inventory, channel constraints), decision context (offers already received, responses and the reasons for earlier decisions) and control (the intent, guardrails, permissions and approval points people define and agents must respect)."@en .

:faqQ4 a schema:Question ;
    schema:name "What is CustomerLake and how is it governed?"@en ;
    schema:position 4 ;
    schema:acceptedAnswer :faqA4 .

:faqA4 a schema:Answer ;
    schema:text "The article describes CustomerLake as the Agentic CDP from Databricks, embedded in the lakehouse and governed by Unity Catalog so that marketing engagement and personalization run on the same data and AI foundation as the rest of the business. It is organised around Profile Agents and Campaign Agents, with Genie for natural-language exploration."@en .

:faqQ5 a schema:Question ;
    schema:name "How does the article propose measuring ROI?"@en ;
    schema:position 5 ;
    schema:acceptedAnswer :faqA5 .

:faqA5 a schema:Answer ;
    schema:text "Through incrementality, the difference compared with taking no action, supported by attribution, incrementality testing and marketing mix modeling. It lists five things a CMO must show: incremental impact, true profit contribution, contribution by lever, marginal return, and customer value over time."@en .

:faqQ6 a schema:Question ;
    schema:name "What is the Virtuoso alternative this page proposes?"@en ;
    schema:position 6 ;
    schema:acceptedAnswer :faqA6 .

:faqA6 a schema:Answer ;
    schema:text "Model the same loop as linked data: customers, signals, consent, offers, guardrails, decisions and outcomes as entities with resolvable IRIs, projected from SQL tables by an RDF View or held as triples, queried with SQL, SPARQL and SPASQL, with the agent-rdf-memory harness keeping the working agent's standing rules and session memory in the same store."@en .

:faqQ7 a schema:Question ;
    schema:name "How do Databricks tables fit in over ODBC?"@en ;
    schema:position 7 ;
    schema:acceptedAnswer :faqA7 .

:faqA7 a schema:Answer ;
    schema:text "Virtuoso attaches a Databricks table through an ODBC data source so SQL treats it like a local table, and RDF View generators then produce a quad map, an ontology and rewrite rules over it. The rows stay in Databricks and are fetched at query time. Demo already holds such an attachment, and this page shows the generator's output for it without executing it."@en .

:faqQ8 a schema:Question ;
    schema:name "Is the demonstration data Databricks data?"@en ;
    schema:position 8 ;
    schema:acceptedAnswer :faqA8 .

:faqA8 a schema:Answer ;
    schema:text "No. CustomerKG is a synthetic estate of 228 rows invented for this page, on demo.openlinksw.com, with invented customer names. It proves the Virtuoso mechanics, SQL tables projected to RDF with resolvable IRIs and queried live, and simulates the loop the article describes. It makes no claim about Databricks performance or behaviour."@en .

:faqQ9 a schema:Question ;
    schema:name "Do the entity IRIs in the view actually resolve?"@en ;
    schema:position 9 ;
    schema:acceptedAnswer :faqA9 .

:faqA9 a schema:Answer ;
    schema:text "Yes, after URL rewrite rules were installed in the same pass as the view. Each entity IRI answers with a 303 to a describe page, and so do terms in the ontology namespace. A view without rewrite rules defines IRIs that return 404."@en .

:faqQ10 a schema:Question ;
    schema:name "What does the discount test show about incentives?"@en ;
    schema:position 10 ;
    schema:acceptedAnswer :faqA10 .

:faqA10 a schema:Answer ;
    schema:text "In the synthetic data the treated customers ordered at 75 percent and the holdout at 100 percent, so the incentive subsidised orders that would have happened anyway, and incremental profit is minus 15 dollars. It is the article's suppress-offers-that-subsidise scenario, with invented numbers, expressed as a SQL query over linked decision and outcome rows."@en .

:faqQ11 a schema:Question ;
    schema:name "How are guardrails represented and audited?"@en ;
    schema:position 11 ;
    schema:acceptedAnswer :faqA11 .

:faqA11 a schema:Answer ;
    schema:text "As entities linked to every decision they constrain. One query lists the human-approval rules against the decisions that triggered them and whether a person approved; another joins withdrawn consent to the native decision table and finds no contact on a withdrawn channel. Auditing is shown here; enforcement is a separate property that this page does not test."@en .

:faqQ12 a schema:Question ;
    schema:name "Where is Databricks' answer stronger?"@en ;
    schema:position 12 ;
    schema:acceptedAnswer :faqA12 .

:faqA12 a schema:Answer ;
    schema:text "On none of the thirteen axes does the evidence put it ahead, and the page says where that evidence is thin. The article describes probabilistic and agentic identity matching with a partner graph, activation across destinations and three measurement methods, but shows none of them, and this page tests none of them on either side. They are where Databricks capabilities would feed the Virtuoso paths rather than compete with them: matches from any engine can be asserted as owl:sameAs and reasoned over, and Databricks tables attached over ODBC gain that reasoning without a copy."@en .

:faqQ13 a schema:Question ;
    schema:name "How does Virtuoso reconcile identities, and what does Databricks data gain from it?"@en ;
    schema:position 13 ;
    schema:acceptedAnswer :faqA13 .

:faqA13 a schema:Answer ;
    schema:text "By reasoning over the graph. An explicit owl:sameAs merges two identities, and declaring a property such as an email hash an owl:InverseFunctionalProperty makes any two records that share its value the same person, with no matching pipeline or third-party engine. Query 3 shows it: Avery Quinn is known under four records through one explicit link and two implied by a shared hash. Databricks tables attached over ODBC and projected by an RDF View take part in the same reasoning, and for master lookups OpenLink hosts DBpedia and many enclaves of the LOD Cloud."@en .

:faqQ14 a schema:Question ;
    schema:name "What privacy care does attaching Databricks tables need?"@en ;
    schema:position 14 ;
    schema:acceptedAnswer :faqA14 .

:faqA14 a schema:Answer ;
    schema:text "Review every column before an RDF View exposes it. The generator mapped all ten columns of the attached Bakehouse table, including cardNumber, as predicates. Drop or pseudonymise payment and personal columns, apply consent to the view, and grant SELECT narrowly, because the view inherits SQL privileges and publishes whatever it maps."@en .

:faqQ15 a schema:Question ;
    schema:name "How do I run the SPASQL and SQL queries on demo.openlinksw.com?"@en ;
    schema:position 15 ;
    schema:acceptedAnswer :faqA15 .

:faqA15 a schema:Answer ;
    schema:text "They need a signed-in session, which the anonymous SPARQL endpoint does not give. Authenticate with the account demo or vdb, where the user id and the password are identical, then run them in the SPASQL Query Builder or any SQL route. The instance is protected by attribute-based access control (ABAC) ACLs, and every table in the CustomerKG estate grants SELECT to those two accounts, to PUBLIC and to the SPARQL account."@en .

:faqQ16 a schema:Question ;
    schema:name "How does agent-rdf-memory relate to the article's control layer?"@en ;
    schema:position 16 ;
    schema:acceptedAnswer :faqA16 .

:faqA16 a schema:Answer ;
    schema:text "The article's control layer holds the intent, guardrails and approval points that agents must respect. The harness applies the same idea to the coding agent that built this page: standing preferences are schema:HowToStep entities retrieved by ontology-routed queries, and each memory document is a named graph in the Virtuoso store that also serves the CustomerKG view."@en .


# ═══════════════════════════════════════════════════════════════════════════
# Glossary
# ═══════════════════════════════════════════════════════════════════════════

:glossarySet a schema:DefinedTermSet ;
    schema:name "Core Technical Glossary"@en ;
    schema:hasDefinedTerm <http://dbpedia.org/resource/Customer_data_platform>, :termAgenticCdp, :termInfinityCampaigns, :termSameAs, :termInverseFunctional, :termHoldout, :termGuardrail, <http://dbpedia.org/resource/Marketing_mix_modeling>, <http://dbpedia.org/resource/Attribution_(marketing)>, :termRdfView, :termIriClass, :termSpasql, :termAttachedTable, <http://dbpedia.org/resource/Open_Database_Connectivity>, <http://dbpedia.org/resource/Named_graph>, <http://dbpedia.org/resource/Inference>, <http://dbpedia.org/resource/Linked_data>, <http://dbpedia.org/resource/Knowledge_graph>, <http://dbpedia.org/resource/SPARQL>, <http://dbpedia.org/resource/SQL>, <http://dbpedia.org/resource/Data_governance>, :termPredictionEconomy, :termIncrementalProfit, :agenticMarketing, :identityResolution, :incrementality, :profileAgents, :campaignAgents .

<http://dbpedia.org/resource/Customer_data_platform> a schema:DefinedTerm ;
    schema:name "Customer data platform"@en ;
    schema:description "Software that unifies customer data from many sources into persistent profiles and activates audiences. The article's agentic CDP adds agents that decide and act."@en ;
    schema:inDefinedTermSet :glossarySet .

:termAgenticCdp a schema:DefinedTerm ;
    schema:name "Agentic CDP"@en ;
    schema:description "A customer data platform that combines identity resolution, audience building and activation with AI agents that analyse signals, decide on next-best actions and act across channels."@en ;
    schema:inDefinedTermSet :glossarySet .

:termInfinityCampaigns a schema:DefinedTerm ;
    schema:name "Infinity Campaigns"@en ;
    schema:description "Databricks' name for always-on engagement loops that replace the cycle of building, launching and rebuilding one-off campaigns."@en ;
    schema:inDefinedTermSet :glossarySet .

:termSameAs a schema:DefinedTerm ;
    schema:name "owl:sameAs"@en ;
    schema:description "An OWL property asserting that two IRIs denote the same thing. Under reasoning, queries about one see the facts held about the other."@en ;
    schema:inDefinedTermSet :glossarySet .

:termInverseFunctional a schema:DefinedTerm ;
    schema:name "Inverse functional property"@en ;
    schema:description "A property whose value identifies its subject uniquely, such as a hashed email address. If two records share the value, a reasoner concludes they describe the same entity."@en ;
    schema:inDefinedTermSet :glossarySet .

:termHoldout a schema:DefinedTerm ;
    schema:name "Holdout"@en ;
    schema:description "A randomised group deliberately left alone, so the difference between its outcomes and the treated group's outcomes measures what the action changed."@en ;
    schema:inDefinedTermSet :glossarySet .

:termGuardrail a schema:DefinedTerm ;
    schema:name "Guardrail"@en ;
    schema:description "A rule an agent must respect, such as a discount cap, a contact-frequency limit, a consent requirement or a human-approval threshold."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Marketing_mix_modeling> a schema:DefinedTerm ;
    schema:name "Marketing mix modeling"@en ;
    schema:description "Statistical estimation of how marketing inputs contribute to outcomes in aggregate; one of three measurement methods the article names."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Attribution_(marketing)> a schema:DefinedTerm ;
    schema:name "Attribution"@en ;
    schema:description "Assigning credit for an outcome to marketing touchpoints; one of the three measurement methods the article names."@en ;
    schema:inDefinedTermSet :glossarySet .

:termRdfView a schema:DefinedTerm ;
    schema:name "RDF View"@en ;
    schema:description "A declarative mapping from relational tables to RDF triples computed at query time. In Virtuoso it is declared with ALTER QUAD STORAGE and is also called a quad map."@en ;
    schema:inDefinedTermSet :glossarySet .

:termIriClass a schema:DefinedTerm ;
    schema:name "IRI class"@en ;
    schema:description "A Virtuoso template that builds a dereferenceable IRI from a key column, such as http://demo.openlinksw.com/CustomerKG/customer/%d#this."@en ;
    schema:inDefinedTermSet :glossarySet .

:termSpasql a schema:DefinedTerm ;
    schema:name "SPASQL"@en ;
    schema:description "A SPARQL query nested inside a SQL SELECT, so a graph result set joins native relational tables in one statement."@en ;
    schema:inDefinedTermSet :glossarySet .

:termAttachedTable a schema:DefinedTerm ;
    schema:name "Attached table"@en ;
    schema:description "A table that lives in a remote database and is registered in Virtuoso over ODBC or JDBC, so it can be queried and mapped as if it were local."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Open_Database_Connectivity> a schema:DefinedTerm ;
    schema:name "ODBC"@en ;
    schema:description "A standard C API for connecting applications to databases through drivers. Virtuoso uses it to attach remote tables such as a Databricks SQL warehouse."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Named_graph> a schema:DefinedTerm ;
    schema:name "Named graph"@en ;
    schema:description "An RDF graph identified by an IRI. The CustomerKG view lives in http://demo.openlinksw.com/CustomerKG# and its TBox in http://demo.openlinksw.com/schemas/CustomerKG/."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Inference> a schema:DefinedTerm ;
    schema:name "Entailment"@en ;
    schema:description "Deriving facts not stored explicitly from a declared ontology, such as a consent record being a ControlElement because ConsentRecord is a subclass of it."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Linked_data> a schema:DefinedTerm ;
    schema:name "Linked data"@en ;
    schema:description "Publishing structured data so that its identifiers are HTTP IRIs that resolve to descriptions and link to other data."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Knowledge_graph> a schema:DefinedTerm ;
    schema:name "Knowledge graph"@en ;
    schema:description "A graph of entities and relationships with meaning attached. Here it is computed from relational tables rather than stored separately."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/SPARQL> a schema:DefinedTerm ;
    schema:name "SPARQL"@en ;
    schema:description "The W3C query language for RDF, used here against the RDF View."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/SQL> a schema:DefinedTerm ;
    schema:name "SQL"@en ;
    schema:description "The relational query language. An RDF View compiles SPARQL into it."@en ;
    schema:inDefinedTermSet :glossarySet .

<http://dbpedia.org/resource/Data_governance> a schema:DefinedTerm ;
    schema:name "Data governance"@en ;
    schema:description "The policies and controls over who may use which data and how. In the article Unity Catalog provides it; in Virtuoso the view inherits SQL grants."@en ;
    schema:inDefinedTermSet :glossarySet .

:termPredictionEconomy a schema:DefinedTerm ;
    schema:name "Prediction economy"@en ;
    schema:description "The article's name, via Jake LaDuke, for a model that rewards predicting what a customer needs, acting on it, proving the outcome and using it as the accelerant for what comes next, replacing an impression currency of reach and frequency."@en ;
    schema:inDefinedTermSet :glossarySet .

:termIncrementalProfit a schema:DefinedTerm ;
    schema:name "Incremental profit"@en ;
    schema:description "The profit the treated customers added beyond what the holdout's behaviour predicts, after media costs and discounts. Negative when an incentive only subsidises orders that would have happened anyway."@en ;
    schema:inDefinedTermSet :glossarySet .


# ═══════════════════════════════════════════════════════════════════════════
# HowTo
# ═══════════════════════════════════════════════════════════════════════════

:howto a schema:HowTo ;
    schema:name "Reproduce the CustomerKG demonstration, then extend it to Databricks tables"@en ;
    schema:description "Eleven steps from an empty database to a live RDF View with resolvable entity IRIs that answers the fourteen queries on this page, and on to an ODBC-attached Databricks table."@en ;
    schema:step :step1, :step2, :step3, :step4, :step5, :step6, :step7, :step8, :step9, :step10, :step11 .

:step1 a schema:HowToStep ;
    schema:name "Create the tables"@en ;
    schema:position 1 ;
    schema:text "Run the twelve CREATE TABLE statements under a qualifier and owner you control, parents before children. The demonstration used CustomerKG.kidehen."@en .

:step2 a schema:HowToStep ;
    schema:name "Load the rows"@en ;
    schema:position 2 ;
    schema:text "Insert the 228 rows. Virtuoso has no multi-row VALUES, so send one INSERT per row, parents before children."@en .

:step3 a schema:HowToStep ;
    schema:name "Create the IRI classes"@en ;
    schema:position 3 ;
    schema:text "Run the eleven SPARQL CREATE IRI CLASS statements. On a busy server each can take tens of seconds; confirm completion by querying the virtrdf schema graph rather than reissuing."@en .

:step4 a schema:HowToStep ;
    schema:name "Load the ontology and build the rule set"@en ;
    schema:position 4 ;
    schema:text "Load customerkg-ontology-claude_sonnet_5_5-1.ttl into http://demo.openlinksw.com/schemas/CustomerKG/ with DB.DBA.TTLP, then create the rule set with DB.DBA.rdfs_rule_set('urn:customerkg:inference', 'http://demo.openlinksw.com/schemas/CustomerKG/')."@en .

:step5 a schema:HowToStep ;
    schema:name "Declare the RDF View"@en ;
    schema:position 5 ;
    schema:text "Send the single ALTER QUAD STORAGE statement. Keep every mapping for the graph in one statement, because a second statement for the same graph replaces the first, and guard nullable foreign keys with IS NOT NULL."@en .

:step6 a schema:HowToStep ;
    schema:name "Grant access"@en ;
    schema:position 6 ;
    schema:text "GRANT SELECT on each table to the SPARQL account and PUBLIC, plus any accounts that run SPASQL. The view inherits these privileges."@en .

:step7 a schema:HowToStep ;
    schema:name "Install the rewrite rules"@en ;
    schema:position 7 ;
    schema:text "Call RDFVIEW_GENERATE_DATA_RULES with the IRI path segment CustomerKG and include_ontology_rules set to 1, and run the returned script, which defines the two virtual directories. Then request an entity IRI: it should answer with a 303 to a describe page."@en .

:step8 a schema:HowToStep ;
    schema:name "Build the identity graph"@en ;
    schema:position 8 ;
    schema:text "Load the identity vocabulary into http://demo.openlinksw.com/schemas/CustomerKG/identity/, build the rule set urn:customerkg:identity-inference from it, then derive the customer facts from the view with SPARQL INSERT ... WHERE and add the records from other systems. Query it with DEFINE input:same-as yes and the rule set pragma, signed in."@en .

:step9 a schema:HowToStep ;
    schema:name "Check the count"@en ;
    schema:position 9 ;
    schema:text "SELECT COUNT(*) over the named graph should return 1,160. If it is higher, look for NULL foreign keys missing their guard."@en .

:step10 a schema:HowToStep ;
    schema:name "Run the queries"@en ;
    schema:position 10 ;
    schema:text "Run Queries 1 to 14. Expect 20, 4, 7, 4, 12, 43, 5, 20, 3, 2, 3, 1, 11 and 4 rows. Queries 1 to 7 run from the demo SPARQL endpoint, except Query 3, which needs the signed-in route; Queries 8 to 12 and Query 3 need a signed-in route such as the SPASQL Query Builder or isql, authenticating as demo or vdb with an identical user id and password; Queries 13 and 14 run from the URIBurner SPARQL endpoint and read the view on demo through SERVICE."@en .

:step11 a schema:HowToStep ;
    schema:name "Extend to Databricks over ODBC"@en ;
    schema:position 11 ;
    schema:text "Create the ODBC data source with the workspace host, HTTP path and a token held in a credential store, attach the tables, then call RDFVIEW_FROM_TABLES, RDFVIEW_ONTOLOGY_FROM_TABLES and RDFVIEW_GENERATE_DATA_RULES for them. Before executing the output, remove or pseudonymise personal and payment columns."@en .
