Federate, Subscribe, Join: Lakehouse Federation Read Against VOICE

Notes on a Databricks post that joins an on-premises portfolio with a FactSet share in one query, set beside VOICE, with the scenario rebuilt as invented data, an RDF View and thirteen live queries on demo.openlinksw.com.

14dimensions compared
Source: Unlocking Investment Insights Without Migrating Your Data: Lakehouse Federation, Meghana Padmanabhan, Isaac Gritz and Max Nienu, Databricks Blog, October 27, 2026.
Executive SummaryBy Kingsley Uyi Idehen · 2026-10-28

Synopsis

Notes taken about a Databricks post of October 27, 2026 that joins an on-premises SQL Server portfolio with FactSet data from the Databricks Marketplace in one SQL query, then feeds a dashboard and a Genie space from it. The notes set the post beside VOICE, the Virtuoso Open Interoperable Context Engine.

The scenario is rebuilt on demo.openlinksw.com as invented data in nine tables, an ontology, an RDF View of 926 triples and thirteen queries in SQL, SPASQL and SPARQL. The post's own risk and action rules, taken from its repository, give the same 25 holdings and the same totals in SQL and through the view. The demo was generated as part of constructing the article.

Fourteen dimensions are compared side by side, each with the tier of evidence it rests on. VOICE serves knowledge workers and developers alike, where the post's example sits locked up in code. Eight demos already published on the OpenLink and URIBurner weblogs cover the parts the simulation only stands in for.

View this analysis as a KG entity
THE POST

What the Post Says

A wealth manager keeps holdings in an on-premises SQL Server. The post federates that database, subscribes to FactSet data from the Databricks Marketplace, joins both in one SQL query that persists nothing, and feeds a dashboard and a Genie space from the same query. Its closing claim is that the pattern, which it states as federate the sensitive first-party data, subscribe to third-party data and analyze without moving or copying anything, applies to any industry.

The data never moves

Federation queries the on-premises database directly with standard SQL; the post says nothing is copied or synced and every query runs against the source in real time.

The pattern generalizes beyond investing

The post names healthcare records joined with drug-interaction data, manufacturing telemetry joined with supply-chain data, and in-store point-of-sale joined with demographic data.

FactSet arrives as a native catalog with no ingestion pipeline

Symbology (sym_v1), fundamentals (ff_v3) and estimates (fe_v4) appear as a shared catalog after a Marketplace subscription and can be browsed beside the federated catalog.

One SQL join across both catalogs drives everything downstream

A plain SQL query joins the federated holdings to the Marketplace tables; the post says there is no pipeline, materialized view or gold layer, and every refresh re-federates the source.

Genie answers the same questions in natural language

A Genie space receives the join chain and data-quality filters in its instructions and translates natural language into the queries the dashboard runs. VOICE offers the same through any AI agent environment that supports SKILL.md-based skills.

THE READING

Reading Each Step Through VOICE

For each step of the post, the VOICE reading names the artifact that does the same job. A reading states how the job can be done in VOICE; the live proof section says which parts ran.

1

Steps 1a and 1b in VOICE: attach the source and publish it as an RDF View

Lakehouse Federation creates a connection and a foreign catalog over the on-premises database. In VOICE the database is attached through ODBC or JDBC and published as an RDF View, so its rows are read in place and each row gets a hyperlink-based super key that resolves. The simulation here uses tables on demo as the stand-in; a real SQL Server attached to the same server is shown by the published demos in the two-weblog section. Reading rows over ODBC is the same transfer any client of that database makes.

2

Step 1c in VOICE: bring in the third-party share

The post subscribes to FactSet through the Marketplace and gets a shared catalog. In VOICE the third-party tables are attached or loaded the same way and published in the same view, so their rows carry hyperlink-based super keys as well. The commercial side of a licensed dataset, the request, approval and entitlement, is a Marketplace feature this collection does not exercise.

3

Step 2a in VOICE: one join, expressed three ways

The post joins four FactSet tables to the holdings with key lookups from ticker to security id to company id. The simulation reproduces that SQL (Query 2) and answers the same question through the RDF View with SPASQL (Query 5) and SPARQL (Queries 6 and 7), where the key lookups have become links that are declared once in the view.

4

Step 3a in VOICE: dashboard tiles are queries

The AI/BI dashboard runs the combined query inside each widget. The two summary tiles, portfolio health and risk distribution, are reproduced as Queries 3 and 4. Dashboards generated from SPASQL over RDF Views are already published on the two weblogs.

5

Step 3b in VOICE: natural language and the instructions behind it

Genie receives the join chain and data-quality filters as instructions. In VOICE those instructions are Turtle in agent-rdf-memory, as HowTo steps in a named graph that an agent reads with SPARQL (Query 11) together with a record of what it decided last time (Query 12). Natural-language access works from any AI agent environment that supports SKILL.md-based packaging of skills: the skills in the repository tell the agent what to run and the OPAL tools carry the calls. The OPAL agent demo on the OpenLink weblog is linked, and no natural-language session over PortfolioKG was run for this…

6

The complete picture in VOICE: nothing persisted

The post's closing claim is that no row is copied and no intermediate object is stored. An RDF View is a mapping: the 926 triples it projects from 154 rows on demo are computed from the tables at query time, and no triple of them is stored.

HEAD TO HEAD

Databricks Lakehouse Federation and VOICE, Side by Side

14 dimensions, each with the tier of evidence it rests on: demonstrated on this page, taken from the post, or stated by OpenLink. Governance, and where the join executes, were left out because neither could be exercised on the demo data.

Dimension Databricks Lakehouse FederationLakehouse platform VOICEOpen interoperable context engine
Reaching on-premises data without copying itboth addressHow a query reaches the first-party database in place.

Evidence: Demonstrated on this page and in published demos; the Databricks side is taken from the post.

Connection and foreign catalogA federated connection to the on-premises database and a foreign catalog make its tables appear as Unity Catalog objects; queries run against the source in real time. ODBC or JDBC and an RDF ViewThe database is attached through ODBC or JDBC and published as an RDF View; the simulation reads first-party tables the same way (Queries 1 to 5), and a real attached SQL Server is shown by the SQL Server demo linked below.
Receiving a licensed third-party datasetoverlapHow a third-party dataset such as FactSet becomes available to the query.

Evidence: Taken from the post; the VOICE side was not exercised.

Marketplace subscriptionA request and approval flow in the Databricks Marketplace delivers FactSet as a shared catalog with no ingestion, and the dataset provider itself is listed there. Attach or load, then publishThird-party tables are attached or loaded and published in the same view, as the six marketplace-style tables are here. A catalog of listed providers with a request and approval flow is not part of what this collection ran.
Joining the two sidesboth addressHow a holding is carried to the fundamentals and estimates of its company.

Evidence: Demonstrated on this page: Queries 2, 5, 6 and 7.

Key chain in the queryThe query joins holdings to symbology on ticker, symbology to a security map, and the map to fundamentals and, separately, to estimates; the repository's query has five joins and two window-ranked subqueries. Links declared onceThe view declares holding to security, security to company, and company to fundamentals and estimates as links. A query follows them as a path (Queries 6 and 7), and every node on the path is a hyperlink-based super key that resolves.
Languages for the one queryboth addressWhich standard query languages can ask the combined question.

Evidence: Demonstrated on this page: SQL (Query 2), SPASQL (Query 5), SPARQL (Queries 6 to 9).

SQLStandard SQL on the federated and shared catalogs; the post presents one SQL statement. SQL, SPASQL and SPARQLThe same answer is returned as SQL on the tables, as SQL over two SPARQL tables, and as SPARQL over the view.
Persisting nothing in betweenboth addressWhether answering stores a derived table, view or copy.

Evidence: Demonstrated on this page for VOICE; taken from the post for Databricks.

No materialized objectsThe post builds no pipeline, materialized view or gold layer, and each widget re-federates on refresh. A mapping, not a storeThe RDF View projects 926 triples from 154 rows at query time and stores none; answering Queries 5 to 7 creates no object.
Computing risk and recommendationsboth addressWhether the post's measures can be reproduced exactly.

Evidence: Demonstrated on this page: Queries 2 to 5 equal an independent calculation.

CASE expressions in SQLCurrent and projected earnings, growth, margin, debt to equity, a risk class and an action are computed with CASE expressions in the query. The same rules on the viewThe same rules, taken from the post's own repository, give 25 analysed holdings, 78,044.50 current and 83,270.00 projected annual earnings and 4 high-risk positions in SQL and in SPASQL, matching an independent Python calculation.
A dashboard fed by the live queryboth addressHow the summary tiles read the data.

Evidence: Demonstrated on this page (Queries 3 and 4) and in published demos.

AI/BI dashboardEach widget's dataset inlines the combined query, so a refresh re-runs it. Queries and generated pagesThe two summary tiles are queries on the live tables (Queries 3 and 4), and dashboards generated from SPASQL over RDF Views are published on the OpenLink weblog.
Asking in natural languageboth addressHow a person without SQL gets an answer.

Evidence: Taken from the post for Databricks; for VOICE, stated by OpenLink and visible in the SKILL.md packages in the ai-agent-skills repository. No natural-language session over PortfolioKG was run for this collection.

Genie spaceA Genie space is wired to the same tables and receives the join chain and filters in its instructions, so questions are asked inside Databricks. Skills in any agent environmentQuestions are asked in natural language in any AI agent environment that supports SKILL.md-based packaging of skills. The skills teach the agent which SQL, SPARQL and SPASQL to run, the OPAL tools carry the calls over MCP, and any analysis the post names can be requested against the same linked data.
Where the instructions and past decisions liveunaddressedHow the join chain, filters and earlier results are kept for the next question.

Evidence: Demonstrated on this page: Queries 11 and 12.

In the Genie spaceThe post says the Genie space receives the join chain and filters in its instructions; it does not describe keeping a record of past answers. Turtle in a named graphThe same instructions are six HowTo steps in a memory graph, and a run record sits beside them; an agent reads both with SPARQL, and the graph can be edited as text.
Hyperlink-based super keys that resolveunaddressedWhether a row, a company or a term can be cited and followed outside the system.

Evidence: Demonstrated on this page: five sampled hyperlink-based super keys resolve, and Queries 6 to 9 return clickable ones.

Catalog object namesTables and columns are named inside Unity Catalog; the post does not describe keys that resolve outside the workspace. Hyperlink-based super keysEach holding, company and ontology term is a hyperlink-based super key that answers with a 303 to a description scoped to its graph, so a result can be cited from another document or another server (Query 13).
Reasoning over the combined dataunaddressedWhether the classes declared for the data answer questions without listing them.

Evidence: Demonstrated on this page: Query 10.

Not describedThe post describes joins and aggregates, not entailment. Entailment from the ontologyQuery 10 asks for marketplace records and receives securities, companies, financials and estimates through subclass entailment, with no list of types in the query.
Who the approach servesboth addressWhich people can read, ask and change what the demo is built from.

Evidence: Taken from the post and its repository for Databricks; demonstrated on this page for VOICE (Queries 2 to 12 for developers, the memory instructions and natural-language skills for knowledge workers).

Developer-centric, in codeThe walk-through, the join, the risk and action rules and the Genie instructions are SQL, notebooks and job scripts in a repository, so the knowledge sits locked up in code; business users meet it through the dashboard and Genie. Knowledge workers and developersThe same view serves both. Developers read and extend the ontology, the view and the queries in SQL, SPASQL and SPARQL; knowledge workers ask in natural language through skills, and the rules they rely on are HowTo steps in Turtle that anyone can read (Queries 11 and 12).
Building the demo with the articleboth addressWhere the demo comes from and whether a reader can run it from the article.

Evidence: Demonstrated on this page: the simulation, ontology, view, rewrite rules and queries were generated while the article was being constructed.

Demo in a separate repositoryThe post links a repository with a sample-holdings generator, notebooks, job scripts and dashboard definitions, built alongside the post and run inside a Databricks workspace. Live demo generated with the articleVOICE generated the live demo as part of constructing this article: the invented data, ontology, RDF View, rewrite rules and thirteen queries were produced with skills, run on demo.openlinksw.com, and each claim on the page links to the query that checks it (Live proof).
Where it can runboth addressThe choices open to the owner of sensitive data.

Evidence: Taken from the post for Databricks; deployment options stated by OpenLink for VOICE.

Databricks workspaceThe post runs everything in a Databricks workspace on a cloud, with the on-premises database reached by federation. On premises, containers, cloud imagesOpenLink states that Virtuoso runs on premises, in containers and as machine images and pay-as-you-go offers on AWS, Azure and Google Cloud, so the engine can sit beside the sensitive database as well as in a cloud.
Databricks Lakehouse FederationLakehouse platform

Evidence: Demonstrated on this page and in published demos; the Databricks side is taken from the post.

Connection and foreign catalogA federated connection to the on-premises database and a foreign catalog make its tables appear as Unity Catalog objects; queries run against the source in real time.

Evidence: Taken from the post; the VOICE side was not exercised.

Marketplace subscriptionA request and approval flow in the Databricks Marketplace delivers FactSet as a shared catalog with no ingestion, and the dataset provider itself is listed there.

Evidence: Demonstrated on this page: Queries 2, 5, 6 and 7.

Key chain in the queryThe query joins holdings to symbology on ticker, symbology to a security map, and the map to fundamentals and, separately, to estimates; the repository's query has five joins and two window-ranked subqueries.

Evidence: Demonstrated on this page: SQL (Query 2), SPASQL (Query 5), SPARQL (Queries 6 to 9).

SQLStandard SQL on the federated and shared catalogs; the post presents one SQL statement.

Evidence: Demonstrated on this page for VOICE; taken from the post for Databricks.

No materialized objectsThe post builds no pipeline, materialized view or gold layer, and each widget re-federates on refresh.

Evidence: Demonstrated on this page: Queries 2 to 5 equal an independent calculation.

CASE expressions in SQLCurrent and projected earnings, growth, margin, debt to equity, a risk class and an action are computed with CASE expressions in the query.

Evidence: Demonstrated on this page (Queries 3 and 4) and in published demos.

AI/BI dashboardEach widget's dataset inlines the combined query, so a refresh re-runs it.

Evidence: Taken from the post for Databricks; for VOICE, stated by OpenLink and visible in the SKILL.md packages in the ai-agent-skills repository. No natural-language session over PortfolioKG was run for this collection.

Genie spaceA Genie space is wired to the same tables and receives the join chain and filters in its instructions, so questions are asked inside Databricks.

Evidence: Demonstrated on this page: Queries 11 and 12.

In the Genie spaceThe post says the Genie space receives the join chain and filters in its instructions; it does not describe keeping a record of past answers.

Evidence: Demonstrated on this page: five sampled hyperlink-based super keys resolve, and Queries 6 to 9 return clickable ones.

Catalog object namesTables and columns are named inside Unity Catalog; the post does not describe keys that resolve outside the workspace.

Evidence: Demonstrated on this page: Query 10.

Not describedThe post describes joins and aggregates, not entailment.

Evidence: Taken from the post and its repository for Databricks; demonstrated on this page for VOICE (Queries 2 to 12 for developers, the memory instructions and natural-language skills for knowledge workers).

Developer-centric, in codeThe walk-through, the join, the risk and action rules and the Genie instructions are SQL, notebooks and job scripts in a repository, so the knowledge sits locked up in code; business users meet it through the dashboard and Genie.

Evidence: Demonstrated on this page: the simulation, ontology, view, rewrite rules and queries were generated while the article was being constructed.

Demo in a separate repositoryThe post links a repository with a sample-holdings generator, notebooks, job scripts and dashboard definitions, built alongside the post and run inside a Databricks workspace.
Where it can runboth address

Evidence: Taken from the post for Databricks; deployment options stated by OpenLink for VOICE.

Databricks workspaceThe post runs everything in a Databricks workspace on a cloud, with the on-premises database reached by federation.
VOICEOpen interoperable context engine

Evidence: Demonstrated on this page and in published demos; the Databricks side is taken from the post.

ODBC or JDBC and an RDF ViewThe database is attached through ODBC or JDBC and published as an RDF View; the simulation reads first-party tables the same way (Queries 1 to 5), and a real attached SQL Server is shown by the SQL Server demo linked below.

Evidence: Taken from the post; the VOICE side was not exercised.

Attach or load, then publishThird-party tables are attached or loaded and published in the same view, as the six marketplace-style tables are here. A catalog of listed providers with a request and approval flow is not part of what this collection ran.

Evidence: Demonstrated on this page: Queries 2, 5, 6 and 7.

Links declared onceThe view declares holding to security, security to company, and company to fundamentals and estimates as links. A query follows them as a path (Queries 6 and 7), and every node on the path is a hyperlink-based super key that resolves.

Evidence: Demonstrated on this page: SQL (Query 2), SPASQL (Query 5), SPARQL (Queries 6 to 9).

SQL, SPASQL and SPARQLThe same answer is returned as SQL on the tables, as SQL over two SPARQL tables, and as SPARQL over the view.

Evidence: Demonstrated on this page for VOICE; taken from the post for Databricks.

A mapping, not a storeThe RDF View projects 926 triples from 154 rows at query time and stores none; answering Queries 5 to 7 creates no object.

Evidence: Demonstrated on this page: Queries 2 to 5 equal an independent calculation.

The same rules on the viewThe same rules, taken from the post's own repository, give 25 analysed holdings, 78,044.50 current and 83,270.00 projected annual earnings and 4 high-risk positions in SQL and in SPASQL, matching an independent Python calculation.

Evidence: Demonstrated on this page (Queries 3 and 4) and in published demos.

Queries and generated pagesThe two summary tiles are queries on the live tables (Queries 3 and 4), and dashboards generated from SPASQL over RDF Views are published on the OpenLink weblog.

Evidence: Taken from the post for Databricks; for VOICE, stated by OpenLink and visible in the SKILL.md packages in the ai-agent-skills repository. No natural-language session over PortfolioKG was run for this collection.

Skills in any agent environmentQuestions are asked in natural language in any AI agent environment that supports SKILL.md-based packaging of skills. The skills teach the agent which SQL, SPARQL and SPASQL to run, the OPAL tools carry the calls over MCP, and any analysis the post names can be requested against the same linked data.

Evidence: Demonstrated on this page: Queries 11 and 12.

Turtle in a named graphThe same instructions are six HowTo steps in a memory graph, and a run record sits beside them; an agent reads both with SPARQL, and the graph can be edited as text.

Evidence: Demonstrated on this page: five sampled hyperlink-based super keys resolve, and Queries 6 to 9 return clickable ones.

Hyperlink-based super keysEach holding, company and ontology term is a hyperlink-based super key that answers with a 303 to a description scoped to its graph, so a result can be cited from another document or another server (Query 13).

Evidence: Demonstrated on this page: Query 10.

Entailment from the ontologyQuery 10 asks for marketplace records and receives securities, companies, financials and estimates through subclass entailment, with no list of types in the query.

Evidence: Taken from the post and its repository for Databricks; demonstrated on this page for VOICE (Queries 2 to 12 for developers, the memory instructions and natural-language skills for knowledge workers).

Knowledge workers and developersThe same view serves both. Developers read and extend the ontology, the view and the queries in SQL, SPASQL and SPARQL; knowledge workers ask in natural language through skills, and the rules they rely on are HowTo steps in Turtle that anyone can read (Queries 11 and 12).

Evidence: Demonstrated on this page: the simulation, ontology, view, rewrite rules and queries were generated while the article was being constructed.

Live demo generated with the articleVOICE generated the live demo as part of constructing this article: the invented data, ontology, RDF View, rewrite rules and thirteen queries were produced with skills, run on demo.openlinksw.com, and each claim on the page links to the query that checks it (Live proof).
Where it can runboth address

Evidence: Taken from the post for Databricks; deployment options stated by OpenLink for VOICE.

On premises, containers, cloud imagesOpenLink states that Virtuoso runs on premises, in containers and as machine images and pay-as-you-go offers on AWS, Azure and Google Cloud, so the engine can sit beside the sensitive database as well as in a cloud.
LIVE PROOF

Live Proof: PortfolioKG on demo.openlinksw.com

The post's scenario was rebuilt as nine SQL tables of invented data: three first-party tables standing for the on-premises database and six tables shaped like the FactSet symbology, fundamentals and estimates the post joins. The tables were given an ontology, projected as an RDF View, made resolvable with rewrite rules and queried with SQL, SPASQL and SPARQL. The risk and action rules are the ones in the post's own no-ETL query. No row is Databricks or FactSet data. VOICE generated this demo as part of constructing the article.

5 of 5 sampled

A company, a holding, a security, an ontology class and the ontology itself each answered with a 303 to a graph-scoped description page that returned 200.

9 tables, 154 rows

Three first-party tables and six marketplace-style tables on demo, all synthetic.

926

Hand-counted from the mapping and the rows, and equal to the count returned by the endpoint. None is stored.

29 properties and 7 classes used, none undeclared

The ontology declares 9 classes and 29 properties; the two classes not used are deliberate super-classes.

7

One per entity type: client, portfolio, holding, security, company, financials, estimate.

13

Four SQL, one SPASQL and eight SPARQL, each executed on 2026-10-28 and its result checked against an independent calculation of the same rules.

Server 1

demo.openlinksw.com

Where the data lives. Everything here was executed as the kidehen account; all rows are invented. Each group starts closed.

All of it in one script

1 card

The copy-paste batch for your own Virtuoso, in the order a fresh database needs it.

One script: tables, rows, IRI classes, ontology, RDF View, grants, rewrite rules, memory graph

SQL

The consolidated copy-paste script for running the whole PortfolioKG simulation on your own Virtuoso. Its purpose is execution, not exposition: the same statements as the cards below 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.

-- portfoliokg-lakehouse-federation.sql
-- Consolidated batch for the PortfolioKG estate used by the Databricks Lakehouse Federation meshup.
-- Run:  isql <host>:1111 <user> <password> < portfoliokg-lakehouse-federation.sql
--       or paste into Virtuoso Conductor > Database > Interactive SQL.
-- Order: tables, rows, IRI classes, ontology, the RDF View (quad map), grants, URL rewrite rules, memory graph.
-- Every statement below was executed on demo.openlinksw.com as user kidehen; the data is synthetic and illustrative.
-- Part 1 (tables, rows, grants) is standard SQL. Parts 2 and 3 (CREATE IRI CLASS and ALTER QUAD STORAGE) are
-- Virtuoso-specific and need the SPARQL keyword in front when sent through isql.

-- ── Part 1a: tables (first-party rows, then the marketplace-style share) ──
CREATE TABLE PortfolioKG.kidehen.client (client_id INTEGER NOT NULL PRIMARY KEY, client_name VARCHAR(48) NOT NULL, segment VARCHAR(16) NOT NULL);
CREATE TABLE PortfolioKG.kidehen.portfolio (portfolio_id INTEGER NOT NULL PRIMARY KEY, client_id INTEGER NOT NULL, portfolio_name VARCHAR(48) NOT NULL, base_currency VARCHAR(3) NOT NULL, FOREIGN KEY (client_id) REFERENCES PortfolioKG.kidehen.client (client_id));
CREATE TABLE PortfolioKG.kidehen.equity_holdings (holding_id INTEGER NOT NULL PRIMARY KEY, portfolio_id INTEGER NOT NULL, symbol VARCHAR(12) NOT NULL, instrument_type VARCHAR(12) NOT NULL, number_of_shares INTEGER NOT NULL, avg_cost DECIMAL(10,2) NOT NULL, acquired_date DATE NOT NULL, FOREIGN KEY (portfolio_id) REFERENCES PortfolioKG.kidehen.portfolio (portfolio_id));
CREATE TABLE PortfolioKG.kidehen.fs_company (fsym_company_id VARCHAR(12) NOT NULL PRIMARY KEY, company_name VARCHAR(64) NOT NULL, sector VARCHAR(24) NOT NULL);
CREATE TABLE PortfolioKG.kidehen.fs_sym_ticker_region (fsym_id VARCHAR(12) NOT NULL PRIMARY KEY, ticker_region VARCHAR(12) NOT NULL);
CREATE TABLE PortfolioKG.kidehen.fs_ff_sec_map (fsym_id VARCHAR(12) NOT NULL PRIMARY KEY, fsym_company_id VARCHAR(12) NOT NULL, FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_sym_ticker_region (fsym_id), FOREIGN KEY (fsym_company_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));
CREATE TABLE PortfolioKG.kidehen.fs_fe_sec_map (fsym_id VARCHAR(12) NOT NULL PRIMARY KEY, fsym_company_id VARCHAR(12) NOT NULL, FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_sym_ticker_region (fsym_id), FOREIGN KEY (fsym_company_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));
CREATE TABLE PortfolioKG.kidehen.fs_ff_basic_af (fsym_id VARCHAR(12) NOT NULL, fiscal_year INTEGER NOT NULL, fiscal_date DATE NOT NULL, ff_sales INTEGER NOT NULL, ff_net_income INTEGER NOT NULL, ff_eps_basic DECIMAL(8,2), ff_debt_st INTEGER NOT NULL, ff_debt_lt INTEGER NOT NULL, ff_com_eq INTEGER NOT NULL, PRIMARY KEY (fsym_id, fiscal_year), FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));
CREATE TABLE PortfolioKG.kidehen.fs_fe_basic_conh_af (fsym_id VARCHAR(12) NOT NULL, fiscal_year INTEGER NOT NULL, fe_item VARCHAR(8) NOT NULL, fe_fp_end DATE NOT NULL, fe_mean DECIMAL(8,2), fe_high DECIMAL(8,2), fe_low DECIMAL(8,2), fe_num_est INTEGER NOT NULL, cons_end_date DATE, PRIMARY KEY (fsym_id, fiscal_year), FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));

-- ── Part 1b: rows ──
INSERT INTO PortfolioKG.kidehen.client VALUES (1, 'Calder Family Trust', 'Trust');
INSERT INTO PortfolioKG.kidehen.client VALUES (2, 'Wren Foundation', 'Foundation');
INSERT INTO PortfolioKG.kidehen.client VALUES (3, 'Okafor Household', 'Individual');
INSERT INTO PortfolioKG.kidehen.client VALUES (4, 'Lindqvist Retirement Fund', 'Pension');
INSERT INTO PortfolioKG.kidehen.portfolio VALUES (1, 1, 'Calder Growth', 'USD');
INSERT INTO PortfolioKG.kidehen.portfolio VALUES (2, 1, 'Calder Income', 'USD');
INSERT INTO PortfolioKG.kidehen.portfolio VALUES (3, 2, 'Wren Endowment', 'USD');
INSERT INTO PortfolioKG.kidehen.portfolio VALUES (4, 3, 'Okafor Core', 'USD');
INSERT INTO PortfolioKG.kidehen.portfolio VALUES (5, 4, 'Lindqvist Balanced', 'USD');
INSERT INTO PortfolioKG.kidehen.portfolio VALUES (6, 4, 'Lindqvist Reserve', 'USD');
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (1, 1, 'ALDM-US', 'Equity', 1200, 31.4, stringdate('2023-03-14'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (2, 1, 'HBLS-US', 'Equity', 2500, 18.75, stringdate('2022-11-02'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (3, 1, 'DNMP-US', 'Equity', 800, 41.2, stringdate('2024-01-22'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (4, 1, 'KSTA-US', 'Equity', 600, 39.8, stringdate('2023-08-09'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (5, 2, 'BWUT-US', 'Equity', 3000, 19.1, stringdate('2021-06-30'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (6, 2, 'FHVB-US', 'Equity', 2200, 24.65, stringdate('2022-02-17'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (7, 2, 'JRGF-US', 'Equity', 1800, 22.3, stringdate('2023-05-11'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (8, 3, 'ALDM-US', 'Equity', 4000, 28.9, stringdate('2022-09-05'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (9, 3, 'DNMP-US', 'Equity', 2100, 36.75, stringdate('2023-10-19'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (10, 3, 'LKSH-US', 'Equity', 1500, 20.1, stringdate('2024-04-03'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (11, 3, 'GRWE-US', 'Equity', 2600, 21.85, stringdate('2021-12-14'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (12, 4, 'CLWY-US', 'Equity', 900, 24.5, stringdate('2022-07-26'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (13, 4, 'EGLG-US', 'Equity', 700, 27.15, stringdate('2023-02-08'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (14, 4, 'FHVB-US', 'Equity', 1100, 23.9, stringdate('2024-02-29'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (15, 4, 'ZZZX-US', 'Equity', 500, 12.0, stringdate('2020-05-18'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (16, 5, 'BWUT-US', 'Equity', 2400, 18.4, stringdate('2021-03-22'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (17, 5, 'IRVM-US', 'Equity', 1300, 26.6, stringdate('2022-10-31'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (18, 5, 'JRGF-US', 'Equity', 900, 21.75, stringdate('2023-12-12'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (19, 5, 'KSTA-US', 'Equity', 450, 41.05, stringdate('2024-05-20'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (20, 6, 'HBLS-US', 'Equity', 1600, 19.3, stringdate('2023-06-06'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (21, 6, 'EGLG-US', 'Equity', 1000, 26.8, stringdate('2022-04-12'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (22, 6, 'CLWY-US', 'Equity', 1400, 23.1, stringdate('2021-09-27'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (23, 6, 'GRWE-US', 'Equity', 1900, 22.6, stringdate('2023-01-30'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (24, 6, 'LKSH-US', 'Equity', 800, 19.55, stringdate('2024-06-17'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (25, 2, 'IRVM-US', 'Equity', 1000, 25.9, stringdate('2023-07-24'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (26, 3, 'MRDT-US', 'Equity', 700, 15.2, stringdate('2024-08-05'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (27, 3, 'NVTC-US', 'Equity', 300, 33.1, stringdate('2024-09-09'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (28, 5, 'TRSY-US', 'Bond', 100000, 0.99, stringdate('2024-03-01'));
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1001-E', 'Aldermoor Semiconductors', 'Technology');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1002-E', 'Brightwater Utilities', 'Utilities');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1003-E', 'Calloway Retail Group', 'Consumer');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1004-E', 'Dunmore Pharma', 'Healthcare');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1005-E', 'Eastgate Logistics', 'Industrials');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1006-E', 'Fairhaven Bancorp', 'Financials');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1007-E', 'Granitewood Energy', 'Energy');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1008-E', 'Harborline Software', 'Technology');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1009-E', 'Ironvale Materials', 'Materials');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1010-E', 'Juniper Ridge Foods', 'Consumer');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1011-E', 'Kestrel Aerospace', 'Industrials');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1012-E', 'Larkspur Health Systems', 'Healthcare');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1013-E', 'Meridian Telecom', 'Communication');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1014-E', 'Novatec Instruments', 'Technology');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1001-R', 'ALDM-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1002-R', 'BWUT-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1003-R', 'CLWY-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1004-R', 'DNMP-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1005-R', 'EGLG-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1006-R', 'FHVB-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1007-R', 'GRWE-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1008-R', 'HBLS-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1009-R', 'IRVM-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1010-R', 'JRGF-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1011-R', 'KSTA-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1012-R', 'LKSH-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1013-R', 'MRDT-US');
INSERT INTO PortfolioKG.kidehen.fs_sym_ticker_region VALUES ('S1014-R', 'NVTC-US');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1001-R', 'C1001-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1002-R', 'C1002-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1003-R', 'C1003-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1004-R', 'C1004-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1005-R', 'C1005-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1006-R', 'C1006-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1007-R', 'C1007-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1008-R', 'C1008-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1009-R', 'C1009-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1010-R', 'C1010-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1011-R', 'C1011-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1012-R', 'C1012-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1013-R', 'C1013-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1014-R', 'C1014-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1001-R', 'C1001-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1002-R', 'C1002-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1003-R', 'C1003-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1004-R', 'C1004-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1005-R', 'C1005-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1006-R', 'C1006-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1007-R', 'C1007-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1008-R', 'C1008-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1009-R', 'C1009-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1010-R', 'C1010-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1011-R', 'C1011-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1012-R', 'C1012-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1013-R', 'C1013-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1014-R', 'C1014-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1001-E', 2023, stringdate('2023-12-31'), 3600, 500, 1.22, 100, 700, 2600);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1001-E', 2024, stringdate('2024-12-31'), 4200, 640, 1.56, 150, 700, 2900);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1001-E', 2025, stringdate('2025-12-31'), 4900, 820, 2.00, 200, 700, 3100);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1002-E', 2023, stringdate('2023-12-31'), 6000, 500, 1.67, 900, 7000, 3300);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1002-E', 2024, stringdate('2024-12-31'), 6100, 520, 1.73, 900, 7400, 3300);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1002-E', 2025, stringdate('2025-12-31'), 6300, 540, 1.80, 1000, 7600, 3300);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1003-E', 2023, stringdate('2023-12-31'), 10100, 360, 2.00, 300, 2200, 2500);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1003-E', 2024, stringdate('2024-12-31'), 9800, 310, 1.72, 300, 2300, 2400);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1003-E', 2025, stringdate('2025-12-31'), 9500, 210, 1.17, 350, 2250, 2400);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1004-E', 2023, stringdate('2023-12-31'), 3000, 640, 2.46, 50, 500, 4800);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1004-E', 2024, stringdate('2024-12-31'), 3300, 700, 2.69, 50, 550, 5000);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1004-E', 2025, stringdate('2025-12-31'), 3700, 790, 3.04, 60, 540, 5200);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1005-E', 2023, stringdate('2023-12-31'), 5000, 360, 2.40, 200, 1800, 2400);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1005-E', 2024, stringdate('2024-12-31'), 5200, 380, 2.53, 200, 1900, 2450);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1005-E', 2025, stringdate('2025-12-31'), 5600, 420, 2.80, 200, 1900, 2500);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1006-E', 2023, stringdate('2023-12-31'), 2300, 490, 2.23, 100, 1200, 4100);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1006-E', 2024, stringdate('2024-12-31'), 2400, 520, 2.36, 100, 1250, 4200);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1006-E', 2025, stringdate('2025-12-31'), 2550, 560, 2.55, 100, 1300, 4300);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1007-E', 2023, stringdate('2023-12-31'), 8100, 1100, 3.24, 300, 3000, 4000);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1007-E', 2024, stringdate('2024-12-31'), 7700, 900, 2.65, 300, 3100, 4100);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1007-E', 2025, stringdate('2025-12-31'), 6900, 610, 1.79, 300, 3000, 4100);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1008-E', 2023, stringdate('2023-12-31'), 1500, 140, 1.17, 20, 130, 1500);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1008-E', 2024, stringdate('2024-12-31'), 1800, 190, 1.58, 20, 130, 1600);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1008-E', 2025, stringdate('2025-12-31'), 2300, 320, 2.67, 20, 130, 1700);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1009-E', 2023, stringdate('2023-12-31'), 3900, 150, 1.67, 300, 2000, 1200);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1009-E', 2024, stringdate('2024-12-31'), 3900, 60, 0.67, 350, 2050, 1150);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1009-E', 2025, stringdate('2025-12-31'), 3800, -40, -0.44, 400, 2000, 1100);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1010-E', 2023, stringdate('2023-12-31'), 5200, 390, 1.86, 200, 1700, 2500);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1010-E', 2024, stringdate('2024-12-31'), 5400, 410, 1.95, 200, 1700, 2550);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1010-E', 2025, stringdate('2025-12-31'), 5600, 450, 2.14, 200, 1700, 2600);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1011-E', 2023, stringdate('2023-12-31'), 8200, 640, 2.78, 500, 3500, 2800);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1011-E', 2024, stringdate('2024-12-31'), 8800, 700, 3.04, 500, 3600, 2850);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1011-E', 2025, stringdate('2025-12-31'), 9600, 880, 3.83, 500, 3600, 2900);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1012-E', 2023, stringdate('2023-12-31'), 3900, 310, 1.63, 100, 1300, 2200);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1012-E', 2024, stringdate('2024-12-31'), 4100, 330, 1.74, 100, 1350, 2250);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1012-E', 2025, stringdate('2025-12-31'), 4400, 360, 1.89, 100, 1400, 2300);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1013-E', 2023, stringdate('2023-12-31'), 6500, 780, 1.56, 500, 4600, 6000);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1013-E', 2024, stringdate('2024-12-31'), 6600, 800, 1.60, 500, 4700, 6050);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1013-E', 2025, stringdate('2025-12-31'), 6700, 790, 1.58, 500, 4700, 6100);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1014-E', 2023, stringdate('2023-12-31'), 1300, 5400, 77.14, 30, 260, 850);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1014-E', 2024, stringdate('2024-12-31'), 1500, 5320, 76.00, 30, 270, 880);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1014-E', 2025, stringdate('2025-12-31'), 1650, 5250, 75.00, 30, 270, 900);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1001-E', 2022, stringdate('2022-12-31'), 3000, 400, 0.98, 100, 700, 2300);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1001-E', 2026, 'EPS', stringdate('2026-12-31'), 2.50, 2.75, 2.25, 11, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1001-E', 2027, 'EPS', stringdate('2027-12-31'), 3.05, 3.40, 2.70, 9, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1002-E', 2026, 'EPS', stringdate('2026-12-31'), 1.85, 1.95, 1.70, 8, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1003-E', 2026, 'EPS', stringdate('2026-12-31'), 1.05, 1.25, 0.80, 14, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1004-E', 2026, 'EPS', stringdate('2026-12-31'), 3.40, 3.65, 3.10, 9, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1004-E', 2027, 'EPS', stringdate('2027-12-31'), 3.75, 4.10, 3.40, 7, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1005-E', 2026, 'EPS', stringdate('2026-12-31'), 2.95, 3.20, 2.70, 7, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1006-E', 2026, 'EPS', stringdate('2026-12-31'), 2.70, 2.85, 2.55, 12, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1007-E', 2026, 'EPS', stringdate('2026-12-31'), 1.45, 1.70, 1.20, 10, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1008-E', 2026, 'EPS', stringdate('2026-12-31'), 3.10, 3.50, 2.80, 6, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1008-E', 2027, 'EPS', stringdate('2027-12-31'), 3.60, 4.10, 3.10, 5, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1009-E', 2026, 'EPS', stringdate('2026-12-31'), -0.20, 0.10, -0.50, 5, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1010-E', 2026, 'EPS', stringdate('2026-12-31'), 2.25, 2.40, 2.10, 9, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1011-E', 2026, 'EPS', stringdate('2026-12-31'), 4.30, 4.70, 3.90, 13, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1011-E', 2027, 'EPS', stringdate('2027-12-31'), 4.75, 5.30, 4.20, 10, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1012-E', 2026, 'EPS', stringdate('2026-12-31'), 2.05, 2.30, 1.85, 3, NULL);
INSERT INTO PortfolioKG.kidehen.fs_fe_basic_conh_af VALUES ('C1013-E', 2026, 'EPS', stringdate('2026-12-31'), 1.55, 1.70, 1.40, 2, stringdate('2026-06-30'));

-- ── Part 2: IRI classes (one per entity; each can take minutes on a busy server) ──
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri> "http://demo.openlinksw.com/PortfolioKG/client/%d#this" (in client_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri> "http://demo.openlinksw.com/PortfolioKG/portfolio/%d#this" (in portfolio_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri> "http://demo.openlinksw.com/PortfolioKG/holding/%d#this" (in holding_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri> "http://demo.openlinksw.com/PortfolioKG/security/%s#this" (in entity_id varchar not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri> "http://demo.openlinksw.com/PortfolioKG/company/%s#this" (in company_id varchar not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri> "http://demo.openlinksw.com/PortfolioKG/financials/%s/%d#this" (in entity_id varchar not null, in fiscal_year integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri> "http://demo.openlinksw.com/PortfolioKG/estimate/%s/%d#this" (in entity_id varchar not null, in fiscal_year integer not null);

-- ── Part 2b: the ontology (TBox) and the inference rule set derived from it ──
DB.DBA.TTLP('@prefix pf:     <http://demo.openlinksw.com/schemas/PortfolioKG/> .
@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#> .
<http://demo.openlinksw.com/schemas/PortfolioKG/> a owl:Ontology ; rdfs:label "PortfolioKG vocabulary"@en ; rdfs:comment "Terms for a synthetic wealth-management estate: holdings kept on premises and a marketplace-style share of symbology, fundamentals and consensus estimates. Every record is invented."@en .
pf:OnPremRecord a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "on-premises record"@en ; rdfs:comment "A row that lives in the first-party database; the stand-in for the foreign catalog."@en ; rdfs:seeAlso <http://dbpedia.org/resource/Federated_database_system> .
pf:MarketplaceRecord a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "marketplace record"@en ; rdfs:comment "A row that arrives in a third-party share; the stand-in for the shared catalog."@en ; rdfs:seeAlso <http://dbpedia.org/resource/Federated_database_system> .
pf:Client a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "client"@en ; rdfs:comment "A person or institution whose portfolios are managed."@en ; rdfs:subClassOf pf:OnPremRecord , schema:Organization ; rdfs:seeAlso schema:Organization .
pf:Portfolio a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "portfolio"@en ; rdfs:comment "A named collection of holdings belonging to one client."@en ; rdfs:subClassOf pf:OnPremRecord ; skos:closeMatch schema:Collection ; rdfs:seeAlso <http://dbpedia.org/resource/Portfolio_(finance)> .
pf:Holding a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "holding"@en ; rdfs:comment "A position: a number of shares or units of one symbol in one portfolio."@en ; rdfs:subClassOf pf:OnPremRecord ; rdfs:seeAlso schema:InvestmentOrDeposit , <http://dbpedia.org/resource/Stock> .
pf:Security a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "security"@en ; rdfs:comment "A listed instrument known to symbology; the hyperlink that carries a ticker to its company."@en ; rdfs:subClassOf pf:MarketplaceRecord ; skos:closeMatch schema:InvestmentOrDeposit ; rdfs:seeAlso <http://dbpedia.org/resource/Security_(finance)> .
pf:Company a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "company"@en ; rdfs:comment "A listed company, the level at which fundamentals and estimates are kept."@en ; rdfs:subClassOf pf:MarketplaceRecord , schema:Organization ; skos:closeMatch schema:Corporation .
pf:AnnualFinancials a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "annual financials"@en ; rdfs:comment "One fiscal year of reported fundamentals for a company."@en ; rdfs:subClassOf pf:MarketplaceRecord ; skos:closeMatch schema:Observation ; rdfs:seeAlso <http://dbpedia.org/resource/Financial_statement> .
pf:EarningsEstimate a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "earnings estimate"@en ; rdfs:comment "Analyst consensus earnings per share for one fiscal period of a company."@en ; rdfs:subClassOf pf:MarketplaceRecord ; skos:closeMatch schema:Observation ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:segment a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "segment"@en ; rdfs:comment "Trust, Foundation, Individual or Pension."@en ; rdfs:domain pf:Client ; rdfs:range xsd:string ; rdfs:seeAlso schema:category .
pf:ofClient a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "of client"@en ; rdfs:comment "The client a portfolio belongs to."@en ; rdfs:domain pf:Portfolio ; rdfs:range pf:Client ; rdfs:seeAlso schema:customer .
pf:baseCurrency a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "base currency"@en ; rdfs:comment "Reporting currency of a portfolio."@en ; rdfs:domain pf:Portfolio ; rdfs:range xsd:string ; rdfs:seeAlso schema:currency .
pf:inPortfolio a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "in portfolio"@en ; rdfs:comment "The portfolio a holding sits in."@en ; rdfs:domain pf:Holding ; rdfs:range pf:Portfolio ; rdfs:seeAlso schema:isPartOf .
pf:symbol a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "symbol as held"@en ; rdfs:comment "The ticker-and-region symbol recorded in the first-party system."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:string ; rdfs:seeAlso schema:tickerSymbol .
pf:instrumentType a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "instrument type"@en ; rdfs:comment "Equity or Bond."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:string ; rdfs:seeAlso schema:additionalType .
pf:shares a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "shares"@en ; rdfs:comment "Number of shares or units held."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Stock> .
pf:avgCost a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "average cost"@en ; rdfs:comment "Average cost per unit in the base currency."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:decimal ; rdfs:seeAlso schema:price .
pf:acquiredDate a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "acquired date"@en ; rdfs:comment "Date the position was opened."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:date ; rdfs:seeAlso schema:startDate .
pf:ofSecurity a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "of security"@en ; rdfs:comment "The security a holding is a position in, resolved through symbology."@en ; rdfs:domain pf:Holding ; rdfs:range pf:Security ; rdfs:seeAlso <http://dbpedia.org/resource/Security_(finance)> .
pf:tickerRegion a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "ticker and region"@en ; rdfs:comment "Symbol of a security in symbology."@en ; rdfs:domain pf:Security ; rdfs:range xsd:string ; rdfs:seeAlso schema:tickerSymbol .
pf:ofCompany a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "of company"@en ; rdfs:comment "The company that issued a security."@en ; rdfs:domain pf:Security ; rdfs:range pf:Company ; rdfs:seeAlso <http://dbpedia.org/resource/Security_(finance)> .
pf:sector a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "sector"@en ; rdfs:comment "Industry sector of a company."@en ; rdfs:domain pf:Company ; rdfs:range xsd:string ; rdfs:seeAlso schema:industry .
pf:forCompany a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "for company"@en ; rdfs:comment "The company a fundamentals or estimate record describes."@en ; rdfs:range pf:Company ; rdfs:seeAlso schema:about .
pf:fiscalYear a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "fiscal year"@en ; rdfs:comment "Fiscal year of a financials or estimate record."@en ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Fiscal_year> .
pf:fiscalDate a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "fiscal date"@en ; rdfs:comment "Closing date of the fiscal period."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:date ; rdfs:seeAlso <http://dbpedia.org/resource/Fiscal_year> .
pf:sales a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "sales"@en ; rdfs:comment "Reported sales in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Revenue> .
pf:netIncome a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "net income"@en ; rdfs:comment "Reported net income in millions of dollars; negative for a loss."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Net_income> .
pf:epsBasic a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "basic EPS"@en ; rdfs:comment "Reported basic earnings per share."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:debtShortTerm a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "short-term debt"@en ; rdfs:comment "Short-term debt in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Debt-to-equity_ratio> .
pf:debtLongTerm a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "long-term debt"@en ; rdfs:comment "Long-term debt in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Debt-to-equity_ratio> .
pf:commonEquity a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "common equity"@en ; rdfs:comment "Common equity in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Debt-to-equity_ratio> .
pf:estimateItem a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "estimate item"@en ; rdfs:comment "The estimated measure; EPS here."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:string ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:periodEnd a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "period end"@en ; rdfs:comment "Closing date of the estimated fiscal period."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:date ; rdfs:seeAlso <http://dbpedia.org/resource/Fiscal_year> .
pf:epsMean a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "mean EPS estimate"@en ; rdfs:comment "Analyst consensus mean earnings per share."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:epsHigh a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "high EPS estimate"@en ; rdfs:comment "Highest analyst estimate."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:epsLow a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "low EPS estimate"@en ; rdfs:comment "Lowest analyst estimate."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:analystCount a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "analyst count"@en ; rdfs:comment "Number of analysts behind the consensus."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Financial_analyst> .
pf:consensusEnded a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "consensus ended"@en ; rdfs:comment "Date a consensus stopped being current; absent while current."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:date ; rdfs:seeAlso schema:endDate .', 'http://demo.openlinksw.com/schemas/PortfolioKG/', 'http://demo.openlinksw.com/schemas/PortfolioKG/');
DB.DBA.rdfs_rule_set('urn:portfoliokg:inference', 'http://demo.openlinksw.com/schemas/PortfolioKG/');

-- ── Part 3: the RDF View. One statement per target graph: a second ALTER QUAD STORAGE for the same graph REPLACES the first. Nullable columns carry an IS NOT NULL guard. ──
SPARQL
ALTER QUAD STORAGE virtrdf:DefaultQuadStorage
  FROM PortfolioKG.kidehen.client AS c
  FROM PortfolioKG.kidehen.portfolio AS p
  FROM PortfolioKG.kidehen.equity_holdings AS h
  FROM PortfolioKG.kidehen.fs_sym_ticker_region AS s
  FROM PortfolioKG.kidehen.fs_ff_sec_map AS fm
  FROM PortfolioKG.kidehen.fs_company AS co
  FROM PortfolioKG.kidehen.fs_ff_basic_af AS f
  FROM PortfolioKG.kidehen.fs_fe_basic_conh_af AS e
{
  GRAPH <http://demo.openlinksw.com/PortfolioKG#>
  {
    <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(c.client_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Client> as virtrdf:PortfolioKG-client-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(c.client_id) <http://schema.org/name> c.client_name as virtrdf:PortfolioKG-client-name .
    <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(c.client_id) <http://demo.openlinksw.com/schemas/PortfolioKG/segment> c.segment as virtrdf:PortfolioKG-client-segment .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Portfolio> as virtrdf:PortfolioKG-pf-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://schema.org/name> p.portfolio_name as virtrdf:PortfolioKG-pf-name .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://demo.openlinksw.com/schemas/PortfolioKG/baseCurrency> p.base_currency as virtrdf:PortfolioKG-pf-ccy .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://demo.openlinksw.com/schemas/PortfolioKG/ofClient> <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(p.client_id) as virtrdf:PortfolioKG-pf-client .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Holding> as virtrdf:PortfolioKG-h-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/inPortfolio> <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(h.portfolio_id) as virtrdf:PortfolioKG-h-pf .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/symbol> h.symbol as virtrdf:PortfolioKG-h-symbol .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/instrumentType> h.instrument_type as virtrdf:PortfolioKG-h-type2 .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/shares> h.number_of_shares as virtrdf:PortfolioKG-h-shares .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/avgCost> h.avg_cost as virtrdf:PortfolioKG-h-cost .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/acquiredDate> h.acquired_date as virtrdf:PortfolioKG-h-acq .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/ofSecurity> <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) where (^{h.}^.symbol = ^{s.}^.ticker_region) as virtrdf:PortfolioKG-h-sec .
    <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Security> as virtrdf:PortfolioKG-s-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) <http://demo.openlinksw.com/schemas/PortfolioKG/tickerRegion> s.ticker_region as virtrdf:PortfolioKG-s-ticker .
    <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) <http://demo.openlinksw.com/schemas/PortfolioKG/ofCompany> <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(fm.fsym_company_id) where (^{s.}^.fsym_id = ^{fm.}^.fsym_id) as virtrdf:PortfolioKG-s-co .
    <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(co.fsym_company_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Company> as virtrdf:PortfolioKG-co-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(co.fsym_company_id) <http://schema.org/name> co.company_name as virtrdf:PortfolioKG-co-name .
    <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(co.fsym_company_id) <http://demo.openlinksw.com/schemas/PortfolioKG/sector> co.sector as virtrdf:PortfolioKG-co-sector .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/AnnualFinancials> as virtrdf:PortfolioKG-f-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/forCompany> <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(f.fsym_id) as virtrdf:PortfolioKG-f-co .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/fiscalYear> f.fiscal_year as virtrdf:PortfolioKG-f-year .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/fiscalDate> f.fiscal_date as virtrdf:PortfolioKG-f-date .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/sales> f.ff_sales as virtrdf:PortfolioKG-f-sales .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/netIncome> f.ff_net_income as virtrdf:PortfolioKG-f-ni .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsBasic> f.ff_eps_basic where (^{f.}^.ff_eps_basic is not null) as virtrdf:PortfolioKG-f-eps .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/debtShortTerm> f.ff_debt_st as virtrdf:PortfolioKG-f-dst .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/debtLongTerm> f.ff_debt_lt as virtrdf:PortfolioKG-f-dlt .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/commonEquity> f.ff_com_eq as virtrdf:PortfolioKG-f-eq .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/EarningsEstimate> as virtrdf:PortfolioKG-e-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/forCompany> <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(e.fsym_id) as virtrdf:PortfolioKG-e-co .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/fiscalYear> e.fiscal_year as virtrdf:PortfolioKG-e-year .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/estimateItem> e.fe_item as virtrdf:PortfolioKG-e-item .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/periodEnd> e.fe_fp_end as virtrdf:PortfolioKG-e-end .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsMean> e.fe_mean where (^{e.}^.fe_mean is not null) as virtrdf:PortfolioKG-e-mean .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsHigh> e.fe_high where (^{e.}^.fe_high is not null) as virtrdf:PortfolioKG-e-high .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsLow> e.fe_low where (^{e.}^.fe_low is not null) as virtrdf:PortfolioKG-e-low .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/analystCount> e.fe_num_est as virtrdf:PortfolioKG-e-count .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/consensusEnded> e.cons_end_date where (^{e.}^.cons_end_date is not null) as virtrdf:PortfolioKG-e-ended .
  }
};

-- ── Part 4: access. The view inherits SQL table privileges. SPARQL serves the anonymous endpoint; demo and vdb serve SPASQL and the Query Builder: on another server, drop those two lines. ──
GRANT SELECT ON PortfolioKG.kidehen.client TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.client TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.client TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.client TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.portfolio TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.portfolio TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.portfolio TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.portfolio TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.equity_holdings TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.equity_holdings TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.equity_holdings TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.equity_holdings TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.fs_company TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.fs_company TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.fs_company TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.fs_company TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.fs_sym_ticker_region TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.fs_sym_ticker_region TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.fs_sym_ticker_region TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.fs_sym_ticker_region TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_sec_map TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_sec_map TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_sec_map TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_sec_map TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_sec_map TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_sec_map TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_sec_map TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_sec_map TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_basic_af TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_basic_af TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_basic_af TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.fs_ff_basic_af TO "vdb";
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_basic_conh_af TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_basic_conh_af TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_basic_conh_af TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.fs_fe_basic_conh_af TO "vdb";

-- ── Part 5: URL rewrite rules, so every entity IRI resolves (303 to a description page; RDF requests routed to DESCRIBE). Generated by the OpenLink RDFVIEW_GENERATE_DATA_RULES function with iri_path_segment=PortfolioKG; the describe rules were edited to pass uri= and graph=, and the generic stat and objects rules were left out. ──
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'portfoliokg_rule2',
1,
'(/[^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^%U%%23this%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/PortfolioKG%%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 (
'portfoliokg_rule1',
1,
'([^#]*)',
vector('path'),
1,
'/describe/?uri=http%%3A%%2F%%2F^{URIQADefaultHost}^%U%%23this&graph=http%%3A%%2F%%2F^{URIQADefaultHost}^%%2FPortfolioKG%%23',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_RULELIST ( 'portfoliokg_rule_list1', 1, vector ( 'portfoliokg_rule1', 'portfoliokg_rule2'));
DB.DBA.VHOST_REMOVE (lpath=>'/PortfolioKG');
DB.DBA.VHOST_DEFINE (lpath=>'/PortfolioKG', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'portfoliokg_rule_list1')
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'portfoliokg_owl_rule2',
1,
'(/[^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^%U%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/schemas/PortfolioKG%%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 (
'portfoliokg_owl_rule1',
1,
'([^#]*)',
vector('path'),
1,
'/describe/?uri=http%%3A%%2F%%2F^{URIQADefaultHost}^%U&graph=http%%3A%%2F%%2F^{URIQADefaultHost}^%%2Fschemas%%2FPortfolioKG%%2F',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_RULELIST ( 'portfoliokg_owl_rule_list1', 1, vector ( 'portfoliokg_owl_rule1', 'portfoliokg_owl_rule2'));
DB.DBA.VHOST_REMOVE (lpath=>'/schemas/PortfolioKG');
DB.DBA.VHOST_DEFINE (lpath=>'/schemas/PortfolioKG', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'portfoliokg_owl_rule_list1')
);

-- ── Part 6: the memory graph, standing instructions and a run record in Turtle, loaded as its own named graph. ──
DB.DBA.TTLP('@prefix pfm:    <http://demo.openlinksw.com/PortfolioKGMemory#> .
@prefix schema: <http://schema.org/> .
@prefix xsd:    <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
pfm:analysisHowTo a schema:HowTo ;
    schema:name "How to answer a question about the client portfolios"@en ;
    schema:description "Standing instructions for an agent that answers portfolio questions over the PortfolioKG view. They carry the join chain and data-quality filters that the Databricks post gives its Genie space."@en ;
    schema:step pfm:step1, pfm:step2, pfm:step3, pfm:step4, pfm:step5, pfm:step6 .
pfm:step1 a schema:HowToStep ; schema:position 1 ; schema:name "Query in place, persist nothing"@en ;
    schema:text "Answer from the RDF View over the source tables. Create no view, table or copy for an answer; record only what was asked and what came back."@en .
pfm:step2 a schema:HowToStep ; schema:position 2 ; schema:name "Follow the join chain as links"@en ;
    schema:text "A holding points to a security (pf:ofSecurity), a security to its company (pf:ofCompany), and fundamentals and estimates point to the same company (pf:forCompany). In SQL the same chain is equity_holdings, fs_sym_ticker_region, fs_ff_sec_map, fs_ff_basic_af and, for estimates, fs_fe_sec_map and fs_fe_basic_conh_af."@en .
pfm:step3 a schema:HowToStep ; schema:position 3 ; schema:name "Apply the data-quality filters"@en ;
    schema:text "Equities only. Fundamentals: period closing 2023-01-01 or later, basic EPS present and below 50 in absolute value, sales above zero, latest row per company. Estimates: item EPS, no consensus end date, period ending today or later, absolute mean below 50, earliest period."@en .
pfm:step4 a schema:HowToStep ; schema:position 4 ; schema:name "Compute the measures"@en ;
    schema:text "Current earnings are shares times basic EPS; projected earnings are shares times mean EPS estimate; growth is (estimate minus EPS) divided by EPS; margin is net income divided by sales; debt to equity is short plus long-term debt divided by common equity. Keep holdings without an estimate, with the projection left empty."@en .
pfm:step5 a schema:HowToStep ; schema:position 5 ; schema:name "Classify risk and action"@en ;
    schema:text "Risk: unprofitable or debt to equity above 2.5 is high; growth below minus 15 percent or margin below 3 percent is medium; margin above 15 percent with debt to equity below 1.5 is low and strong; otherwise low. Action: a loss or growth below minus 15 percent is sell; above 20 percent strong buy; above 5 percent buy; above minus 10 percent hold and stable; otherwise hold and monitor."@en .
pfm:step6 a schema:HowToStep ; schema:position 6 ; schema:name "Write the run back to memory"@en ;
    schema:text "After an answer, add a schema:Action to this graph naming the question, the date and the counts returned, so the next session can read what was already decided."@en .
pfm:run1 a schema:Action ;
    schema:name "Holdings review, 2026-10-28"@en ;
    schema:startTime "2026-10-28T12:00:00Z"^^xsd:dateTime ;
    schema:instrument pfm:analysisHowTo ;
    schema:description "Reviewed 28 holdings: 3 left out by the filters (a bond, a symbol absent from symbology and a company whose reported EPS fails the plausibility test), 25 analysed, 4 of them high risk, 2 flagged sell for a loss and 2 for declining earnings."@en .', 'http://demo.openlinksw.com/PortfolioKGMemory#', 'http://demo.openlinksw.com/PortfolioKG/memory#');

SQL data definition

3 cards

The nine tables that stand for the on-premises database and the FactSet share, and a sample of their 154 rows.

Tables standing for the on-premises database

SQL

Three first-party tables: client, portfolio and equity_holdings. In production these would be attached from SQL Server through ODBC or JDBC rather than created.

CREATE TABLE PortfolioKG.kidehen.client (client_id INTEGER NOT NULL PRIMARY KEY, client_name VARCHAR(48) NOT NULL, segment VARCHAR(16) NOT NULL);
CREATE TABLE PortfolioKG.kidehen.portfolio (portfolio_id INTEGER NOT NULL PRIMARY KEY, client_id INTEGER NOT NULL, portfolio_name VARCHAR(48) NOT NULL, base_currency VARCHAR(3) NOT NULL, FOREIGN KEY (client_id) REFERENCES PortfolioKG.kidehen.client (client_id));
CREATE TABLE PortfolioKG.kidehen.equity_holdings (holding_id INTEGER NOT NULL PRIMARY KEY, portfolio_id INTEGER NOT NULL, symbol VARCHAR(12) NOT NULL, instrument_type VARCHAR(12) NOT NULL, number_of_shares INTEGER NOT NULL, avg_cost DECIMAL(10,2) NOT NULL, acquired_date DATE NOT NULL, FOREIGN KEY (portfolio_id) REFERENCES PortfolioKG.kidehen.portfolio (portfolio_id));

Tables shaped like the FactSet symbology, fundamentals and estimates

SQL

Six marketplace-style tables: a company table, the symbology table, the two security maps, fundamentals and estimates, with the column names of the post's repository in lower case.

CREATE TABLE PortfolioKG.kidehen.fs_company (fsym_company_id VARCHAR(12) NOT NULL PRIMARY KEY, company_name VARCHAR(64) NOT NULL, sector VARCHAR(24) NOT NULL);
CREATE TABLE PortfolioKG.kidehen.fs_sym_ticker_region (fsym_id VARCHAR(12) NOT NULL PRIMARY KEY, ticker_region VARCHAR(12) NOT NULL);
CREATE TABLE PortfolioKG.kidehen.fs_ff_sec_map (fsym_id VARCHAR(12) NOT NULL PRIMARY KEY, fsym_company_id VARCHAR(12) NOT NULL, FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_sym_ticker_region (fsym_id), FOREIGN KEY (fsym_company_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));
CREATE TABLE PortfolioKG.kidehen.fs_fe_sec_map (fsym_id VARCHAR(12) NOT NULL PRIMARY KEY, fsym_company_id VARCHAR(12) NOT NULL, FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_sym_ticker_region (fsym_id), FOREIGN KEY (fsym_company_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));
CREATE TABLE PortfolioKG.kidehen.fs_ff_basic_af (fsym_id VARCHAR(12) NOT NULL, fiscal_year INTEGER NOT NULL, fiscal_date DATE NOT NULL, ff_sales INTEGER NOT NULL, ff_net_income INTEGER NOT NULL, ff_eps_basic DECIMAL(8,2), ff_debt_st INTEGER NOT NULL, ff_debt_lt INTEGER NOT NULL, ff_com_eq INTEGER NOT NULL, PRIMARY KEY (fsym_id, fiscal_year), FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));
CREATE TABLE PortfolioKG.kidehen.fs_fe_basic_conh_af (fsym_id VARCHAR(12) NOT NULL, fiscal_year INTEGER NOT NULL, fe_item VARCHAR(8) NOT NULL, fe_fp_end DATE NOT NULL, fe_mean DECIMAL(8,2), fe_high DECIMAL(8,2), fe_low DECIMAL(8,2), fe_num_est INTEGER NOT NULL, cons_end_date DATE, PRIMARY KEY (fsym_id, fiscal_year), FOREIGN KEY (fsym_id) REFERENCES PortfolioKG.kidehen.fs_company (fsym_company_id));

Sample rows (an excerpt of 154)

SQL

A few INSERT statements from each table; the full set is in the script. The data is invented: nothing here is Databricks or FactSet data.

INSERT INTO PortfolioKG.kidehen.client VALUES (1, 'Calder Family Trust', 'Trust');
INSERT INTO PortfolioKG.kidehen.client VALUES (2, 'Wren Foundation', 'Foundation');
INSERT INTO PortfolioKG.kidehen.client VALUES (3, 'Okafor Household', 'Individual');
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (1, 1, 'ALDM-US', 'Equity', 1200, 31.4, stringdate('2023-03-14'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (2, 1, 'HBLS-US', 'Equity', 2500, 18.75, stringdate('2022-11-02'));
INSERT INTO PortfolioKG.kidehen.equity_holdings VALUES (3, 1, 'DNMP-US', 'Equity', 800, 41.2, stringdate('2024-01-22'));
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1001-E', 'Aldermoor Semiconductors', 'Technology');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1002-E', 'Brightwater Utilities', 'Utilities');
INSERT INTO PortfolioKG.kidehen.fs_company VALUES ('C1003-E', 'Calloway Retail Group', 'Consumer');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1001-R', 'C1001-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1002-R', 'C1002-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_sec_map VALUES ('S1003-R', 'C1003-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1001-R', 'C1001-E');
INSERT INTO PortfolioKG.kidehen.fs_fe_sec_map VALUES ('S1002-R', 'C1002-E');
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1004-E', 2025, stringdate('2025-12-31'), 3700, 790, 3.04, 60, 540, 5200);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1005-E', 2023, stringdate('2023-12-31'), 5000, 360, 2.40, 200, 1800, 2400);
INSERT INTO PortfolioKG.kidehen.fs_ff_basic_af VALUES ('C1005-E', 2024, stringdate('2024-12-31'), 5200, 380, 2.53, 200, 1900, 2450);

RDF View and Linked Data deployment

6 cards

IRI classes, ontology, rule set, the view itself, grants and the rewrite rules that make every entity's hyperlink-based super key resolve.

IRI classes

SPARQL DDL

One CREATE IRI CLASS per entity type; each defines how an entity's hyperlink-based super key is built from key columns. Needs the SPARQL keyword in front when sent through isql.

SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri> "http://demo.openlinksw.com/PortfolioKG/client/%d#this" (in client_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri> "http://demo.openlinksw.com/PortfolioKG/portfolio/%d#this" (in portfolio_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri> "http://demo.openlinksw.com/PortfolioKG/holding/%d#this" (in holding_id integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri> "http://demo.openlinksw.com/PortfolioKG/security/%s#this" (in entity_id varchar not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri> "http://demo.openlinksw.com/PortfolioKG/company/%s#this" (in company_id varchar not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri> "http://demo.openlinksw.com/PortfolioKG/financials/%s/%d#this" (in entity_id varchar not null, in fiscal_year integer not null);
SPARQL CREATE IRI CLASS <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri> "http://demo.openlinksw.com/PortfolioKG/estimate/%s/%d#this" (in entity_id varchar not null, in fiscal_year integer not null);

The PortfolioKG ontology

Turtle

A Turtle ontology of 9 classes and 29 properties, loaded into its own graph. Every class and property has a label, comment and isDefinedBy and a verified cross-reference to schema.org or DBpedia; properties carry a domain and range and are characterized as functional (object properties also asymmetric); OnPremRecord and MarketplaceRecord are super-classes that give entailment (Query 10). All 29 properties and 7 classes the view uses are declared, and none is used undeclared.

@prefix pf:     <http://demo.openlinksw.com/schemas/PortfolioKG/> .
@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#> .
<http://demo.openlinksw.com/schemas/PortfolioKG/> a owl:Ontology ; rdfs:label "PortfolioKG vocabulary"@en ; rdfs:comment "Terms for a synthetic wealth-management estate: holdings kept on premises and a marketplace-style share of symbology, fundamentals and consensus estimates. Every record is invented."@en .
pf:OnPremRecord a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "on-premises record"@en ; rdfs:comment "A row that lives in the first-party database; the stand-in for the foreign catalog."@en ; rdfs:seeAlso <http://dbpedia.org/resource/Federated_database_system> .
pf:MarketplaceRecord a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "marketplace record"@en ; rdfs:comment "A row that arrives in a third-party share; the stand-in for the shared catalog."@en ; rdfs:seeAlso <http://dbpedia.org/resource/Federated_database_system> .
pf:Client a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "client"@en ; rdfs:comment "A person or institution whose portfolios are managed."@en ; rdfs:subClassOf pf:OnPremRecord , schema:Organization ; rdfs:seeAlso schema:Organization .
pf:Portfolio a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "portfolio"@en ; rdfs:comment "A named collection of holdings belonging to one client."@en ; rdfs:subClassOf pf:OnPremRecord ; skos:closeMatch schema:Collection ; rdfs:seeAlso <http://dbpedia.org/resource/Portfolio_(finance)> .
pf:Holding a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "holding"@en ; rdfs:comment "A position: a number of shares or units of one symbol in one portfolio."@en ; rdfs:subClassOf pf:OnPremRecord ; rdfs:seeAlso schema:InvestmentOrDeposit , <http://dbpedia.org/resource/Stock> .
pf:Security a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "security"@en ; rdfs:comment "A listed instrument known to symbology; the hyperlink that carries a ticker to its company."@en ; rdfs:subClassOf pf:MarketplaceRecord ; skos:closeMatch schema:InvestmentOrDeposit ; rdfs:seeAlso <http://dbpedia.org/resource/Security_(finance)> .
pf:Company a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "company"@en ; rdfs:comment "A listed company, the level at which fundamentals and estimates are kept."@en ; rdfs:subClassOf pf:MarketplaceRecord , schema:Organization ; skos:closeMatch schema:Corporation .
pf:AnnualFinancials a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "annual financials"@en ; rdfs:comment "One fiscal year of reported fundamentals for a company."@en ; rdfs:subClassOf pf:MarketplaceRecord ; skos:closeMatch schema:Observation ; rdfs:seeAlso <http://dbpedia.org/resource/Financial_statement> .
pf:EarningsEstimate a owl:Class ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "earnings estimate"@en ; rdfs:comment "Analyst consensus earnings per share for one fiscal period of a company."@en ; rdfs:subClassOf pf:MarketplaceRecord ; skos:closeMatch schema:Observation ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:segment a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "segment"@en ; rdfs:comment "Trust, Foundation, Individual or Pension."@en ; rdfs:domain pf:Client ; rdfs:range xsd:string ; rdfs:seeAlso schema:category .
pf:ofClient a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "of client"@en ; rdfs:comment "The client a portfolio belongs to."@en ; rdfs:domain pf:Portfolio ; rdfs:range pf:Client ; rdfs:seeAlso schema:customer .
pf:baseCurrency a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "base currency"@en ; rdfs:comment "Reporting currency of a portfolio."@en ; rdfs:domain pf:Portfolio ; rdfs:range xsd:string ; rdfs:seeAlso schema:currency .
pf:inPortfolio a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "in portfolio"@en ; rdfs:comment "The portfolio a holding sits in."@en ; rdfs:domain pf:Holding ; rdfs:range pf:Portfolio ; rdfs:seeAlso schema:isPartOf .
pf:symbol a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "symbol as held"@en ; rdfs:comment "The ticker-and-region symbol recorded in the first-party system."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:string ; rdfs:seeAlso schema:tickerSymbol .
pf:instrumentType a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "instrument type"@en ; rdfs:comment "Equity or Bond."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:string ; rdfs:seeAlso schema:additionalType .
pf:shares a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "shares"@en ; rdfs:comment "Number of shares or units held."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Stock> .
pf:avgCost a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "average cost"@en ; rdfs:comment "Average cost per unit in the base currency."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:decimal ; rdfs:seeAlso schema:price .
pf:acquiredDate a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "acquired date"@en ; rdfs:comment "Date the position was opened."@en ; rdfs:domain pf:Holding ; rdfs:range xsd:date ; rdfs:seeAlso schema:startDate .
pf:ofSecurity a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "of security"@en ; rdfs:comment "The security a holding is a position in, resolved through symbology."@en ; rdfs:domain pf:Holding ; rdfs:range pf:Security ; rdfs:seeAlso <http://dbpedia.org/resource/Security_(finance)> .
pf:tickerRegion a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "ticker and region"@en ; rdfs:comment "Symbol of a security in symbology."@en ; rdfs:domain pf:Security ; rdfs:range xsd:string ; rdfs:seeAlso schema:tickerSymbol .
pf:ofCompany a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "of company"@en ; rdfs:comment "The company that issued a security."@en ; rdfs:domain pf:Security ; rdfs:range pf:Company ; rdfs:seeAlso <http://dbpedia.org/resource/Security_(finance)> .
pf:sector a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "sector"@en ; rdfs:comment "Industry sector of a company."@en ; rdfs:domain pf:Company ; rdfs:range xsd:string ; rdfs:seeAlso schema:industry .
pf:forCompany a owl:ObjectProperty , owl:FunctionalProperty , owl:AsymmetricProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "for company"@en ; rdfs:comment "The company a fundamentals or estimate record describes."@en ; rdfs:range pf:Company ; rdfs:seeAlso schema:about .
pf:fiscalYear a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "fiscal year"@en ; rdfs:comment "Fiscal year of a financials or estimate record."@en ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Fiscal_year> .
pf:fiscalDate a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "fiscal date"@en ; rdfs:comment "Closing date of the fiscal period."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:date ; rdfs:seeAlso <http://dbpedia.org/resource/Fiscal_year> .
pf:sales a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "sales"@en ; rdfs:comment "Reported sales in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Revenue> .
pf:netIncome a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "net income"@en ; rdfs:comment "Reported net income in millions of dollars; negative for a loss."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Net_income> .
pf:epsBasic a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "basic EPS"@en ; rdfs:comment "Reported basic earnings per share."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:debtShortTerm a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "short-term debt"@en ; rdfs:comment "Short-term debt in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Debt-to-equity_ratio> .
pf:debtLongTerm a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "long-term debt"@en ; rdfs:comment "Long-term debt in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Debt-to-equity_ratio> .
pf:commonEquity a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "common equity"@en ; rdfs:comment "Common equity in millions of dollars."@en ; rdfs:domain pf:AnnualFinancials ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Debt-to-equity_ratio> .
pf:estimateItem a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "estimate item"@en ; rdfs:comment "The estimated measure; EPS here."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:string ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:periodEnd a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "period end"@en ; rdfs:comment "Closing date of the estimated fiscal period."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:date ; rdfs:seeAlso <http://dbpedia.org/resource/Fiscal_year> .
pf:epsMean a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "mean EPS estimate"@en ; rdfs:comment "Analyst consensus mean earnings per share."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:epsHigh a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "high EPS estimate"@en ; rdfs:comment "Highest analyst estimate."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:epsLow a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "low EPS estimate"@en ; rdfs:comment "Lowest analyst estimate."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:decimal ; rdfs:seeAlso <http://dbpedia.org/resource/Earnings_per_share> .
pf:analystCount a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "analyst count"@en ; rdfs:comment "Number of analysts behind the consensus."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:integer ; rdfs:seeAlso <http://dbpedia.org/resource/Financial_analyst> .
pf:consensusEnded a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:isDefinedBy <http://demo.openlinksw.com/schemas/PortfolioKG/> ; rdfs:label "consensus ended"@en ; rdfs:comment "Date a consensus stopped being current; absent while current."@en ; rdfs:domain pf:EarningsEstimate ; rdfs:range xsd:date ; rdfs:seeAlso schema:endDate .

Loading the ontology and deriving the inference rule set

SQL

The two procedure calls that put the ontology in http://demo.openlinksw.com/schemas/PortfolioKG/ and derive the rule set urn:portfoliokg:inference used by Query 10.

DB.DBA.TTLP('<the ontology Turtle above>', 'http://demo.openlinksw.com/schemas/PortfolioKG/', 'http://demo.openlinksw.com/schemas/PortfolioKG/');
DB.DBA.rdfs_rule_set('urn:portfoliokg:inference', 'http://demo.openlinksw.com/schemas/PortfolioKG/');

The RDF View (quad map)

SPARQL DDL

One ALTER QUAD STORAGE for the graph http://demo.openlinksw.com/PortfolioKG#: 41 mappings over six tables. The link from a holding to its security is a join on symbol and ticker; nullable columns carry an IS NOT NULL guard. It projects 926 triples from 154 rows; a hand count from the mapping and the rows gives the same figure.

SPARQL
ALTER QUAD STORAGE virtrdf:DefaultQuadStorage
  FROM PortfolioKG.kidehen.client AS c
  FROM PortfolioKG.kidehen.portfolio AS p
  FROM PortfolioKG.kidehen.equity_holdings AS h
  FROM PortfolioKG.kidehen.fs_sym_ticker_region AS s
  FROM PortfolioKG.kidehen.fs_ff_sec_map AS fm
  FROM PortfolioKG.kidehen.fs_company AS co
  FROM PortfolioKG.kidehen.fs_ff_basic_af AS f
  FROM PortfolioKG.kidehen.fs_fe_basic_conh_af AS e
{
  GRAPH <http://demo.openlinksw.com/PortfolioKG#>
  {
    <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(c.client_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Client> as virtrdf:PortfolioKG-client-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(c.client_id) <http://schema.org/name> c.client_name as virtrdf:PortfolioKG-client-name .
    <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(c.client_id) <http://demo.openlinksw.com/schemas/PortfolioKG/segment> c.segment as virtrdf:PortfolioKG-client-segment .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Portfolio> as virtrdf:PortfolioKG-pf-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://schema.org/name> p.portfolio_name as virtrdf:PortfolioKG-pf-name .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://demo.openlinksw.com/schemas/PortfolioKG/baseCurrency> p.base_currency as virtrdf:PortfolioKG-pf-ccy .
    <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(p.portfolio_id) <http://demo.openlinksw.com/schemas/PortfolioKG/ofClient> <http://demo.openlinksw.com/schemas/PortfolioKG/client_iri>(p.client_id) as virtrdf:PortfolioKG-pf-client .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Holding> as virtrdf:PortfolioKG-h-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/inPortfolio> <http://demo.openlinksw.com/schemas/PortfolioKG/portfolio_iri>(h.portfolio_id) as virtrdf:PortfolioKG-h-pf .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/symbol> h.symbol as virtrdf:PortfolioKG-h-symbol .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/instrumentType> h.instrument_type as virtrdf:PortfolioKG-h-type2 .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/shares> h.number_of_shares as virtrdf:PortfolioKG-h-shares .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/avgCost> h.avg_cost as virtrdf:PortfolioKG-h-cost .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/acquiredDate> h.acquired_date as virtrdf:PortfolioKG-h-acq .
    <http://demo.openlinksw.com/schemas/PortfolioKG/holding_iri>(h.holding_id) <http://demo.openlinksw.com/schemas/PortfolioKG/ofSecurity> <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) where (^{h.}^.symbol = ^{s.}^.ticker_region) as virtrdf:PortfolioKG-h-sec .
    <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Security> as virtrdf:PortfolioKG-s-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) <http://demo.openlinksw.com/schemas/PortfolioKG/tickerRegion> s.ticker_region as virtrdf:PortfolioKG-s-ticker .
    <http://demo.openlinksw.com/schemas/PortfolioKG/security_iri>(s.fsym_id) <http://demo.openlinksw.com/schemas/PortfolioKG/ofCompany> <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(fm.fsym_company_id) where (^{s.}^.fsym_id = ^{fm.}^.fsym_id) as virtrdf:PortfolioKG-s-co .
    <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(co.fsym_company_id) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/Company> as virtrdf:PortfolioKG-co-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(co.fsym_company_id) <http://schema.org/name> co.company_name as virtrdf:PortfolioKG-co-name .
    <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(co.fsym_company_id) <http://demo.openlinksw.com/schemas/PortfolioKG/sector> co.sector as virtrdf:PortfolioKG-co-sector .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/AnnualFinancials> as virtrdf:PortfolioKG-f-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/forCompany> <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(f.fsym_id) as virtrdf:PortfolioKG-f-co .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/fiscalYear> f.fiscal_year as virtrdf:PortfolioKG-f-year .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/fiscalDate> f.fiscal_date as virtrdf:PortfolioKG-f-date .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/sales> f.ff_sales as virtrdf:PortfolioKG-f-sales .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/netIncome> f.ff_net_income as virtrdf:PortfolioKG-f-ni .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsBasic> f.ff_eps_basic where (^{f.}^.ff_eps_basic is not null) as virtrdf:PortfolioKG-f-eps .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/debtShortTerm> f.ff_debt_st as virtrdf:PortfolioKG-f-dst .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/debtLongTerm> f.ff_debt_lt as virtrdf:PortfolioKG-f-dlt .
    <http://demo.openlinksw.com/schemas/PortfolioKG/financials_iri>(f.fsym_id, f.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/commonEquity> f.ff_com_eq as virtrdf:PortfolioKG-f-eq .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://demo.openlinksw.com/schemas/PortfolioKG/EarningsEstimate> as virtrdf:PortfolioKG-e-type .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/forCompany> <http://demo.openlinksw.com/schemas/PortfolioKG/company_iri>(e.fsym_id) as virtrdf:PortfolioKG-e-co .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/fiscalYear> e.fiscal_year as virtrdf:PortfolioKG-e-year .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/estimateItem> e.fe_item as virtrdf:PortfolioKG-e-item .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/periodEnd> e.fe_fp_end as virtrdf:PortfolioKG-e-end .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsMean> e.fe_mean where (^{e.}^.fe_mean is not null) as virtrdf:PortfolioKG-e-mean .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsHigh> e.fe_high where (^{e.}^.fe_high is not null) as virtrdf:PortfolioKG-e-high .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/epsLow> e.fe_low where (^{e.}^.fe_low is not null) as virtrdf:PortfolioKG-e-low .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/analystCount> e.fe_num_est as virtrdf:PortfolioKG-e-count .
    <http://demo.openlinksw.com/schemas/PortfolioKG/estimate_iri>(e.fsym_id, e.fiscal_year) <http://demo.openlinksw.com/schemas/PortfolioKG/consensusEnded> e.cons_end_date where (^{e.}^.cons_end_date is not null) as virtrdf:PortfolioKG-e-ended .
  }
}

Grants

SQL

SELECT on each of the nine tables to SPARQL, PUBLIC, demo and vdb: 36 statements, of which the first four are shown. The view inherits table privileges.

GRANT SELECT ON PortfolioKG.kidehen.client TO "SPARQL";
GRANT SELECT ON PortfolioKG.kidehen.client TO PUBLIC;
GRANT SELECT ON PortfolioKG.kidehen.client TO "demo";
GRANT SELECT ON PortfolioKG.kidehen.client TO "vdb";

URL rewrite rules

SQL

Generated by the OpenLink RDFVIEW_GENERATE_DATA_RULES function with iri_path_segment PortfolioKG; the describe rules were edited to pass uri and graph, and the generic stat and objects rules were left out. Five sampled hyperlink-based super keys answer with a 303 to a graph-scoped description page that returns 200.

DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'portfoliokg_rule2',
1,
'(/[^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^%U%%23this%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/PortfolioKG%%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 (
'portfoliokg_rule1',
1,
'([^#]*)',
vector('path'),
1,
'/describe/?uri=http%%3A%%2F%%2F^{URIQADefaultHost}^%U%%23this&graph=http%%3A%%2F%%2F^{URIQADefaultHost}^%%2FPortfolioKG%%23',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_RULELIST ( 'portfoliokg_rule_list1', 1, vector ( 'portfoliokg_rule1', 'portfoliokg_rule2'));
DB.DBA.VHOST_REMOVE (lpath=>'/PortfolioKG');
DB.DBA.VHOST_DEFINE (lpath=>'/PortfolioKG', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'portfoliokg_rule_list1')
);
DB.DBA.URLREWRITE_CREATE_REGEX_RULE (
'portfoliokg_owl_rule2',
1,
'(/[^#]*)',
vector('path'),
1,
'/sparql?query=DESCRIBE+%%3Chttp%%3A//^{URIQADefaultHost}^%U%%3E+FROM+%%3Chttp%%3A//^{URIQADefaultHost}^/schemas/PortfolioKG%%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 (
'portfoliokg_owl_rule1',
1,
'([^#]*)',
vector('path'),
1,
'/describe/?uri=http%%3A%%2F%%2F^{URIQADefaultHost}^%U&graph=http%%3A%%2F%%2F^{URIQADefaultHost}^%%2Fschemas%%2FPortfolioKG%%2F',
vector('path'),
null,
null,
2,
303
);
DB.DBA.URLREWRITE_CREATE_RULELIST ( 'portfoliokg_owl_rule_list1', 1, vector ( 'portfoliokg_owl_rule1', 'portfoliokg_owl_rule2'));
DB.DBA.VHOST_REMOVE (lpath=>'/schemas/PortfolioKG');
DB.DBA.VHOST_DEFINE (lpath=>'/schemas/PortfolioKG', ppath=>'/', vsp_user=>'dba', is_dav=>0,
is_brws=>0, opts=>vector ('url_rewrite', 'portfoliokg_owl_rule_list1')
);

Memory: the counterpart of the Genie instructions

1 card

Turtle in a named graph, read back by Queries 11 and 12.

The memory graph

Turtle

Turtle loaded into http://demo.openlinksw.com/PortfolioKG/memory#: a HowTo of six steps carrying the join chain, filters, measures and rules, and a run record. These are the counterpart of the instructions the post gives its Genie space.

@prefix pfm:    <http://demo.openlinksw.com/PortfolioKGMemory#> .
@prefix schema: <http://schema.org/> .
@prefix xsd:    <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
pfm:analysisHowTo a schema:HowTo ;
    schema:name "How to answer a question about the client portfolios"@en ;
    schema:description "Standing instructions for an agent that answers portfolio questions over the PortfolioKG view. They carry the join chain and data-quality filters that the Databricks post gives its Genie space."@en ;
    schema:step pfm:step1, pfm:step2, pfm:step3, pfm:step4, pfm:step5, pfm:step6 .
pfm:step1 a schema:HowToStep ; schema:position 1 ; schema:name "Query in place, persist nothing"@en ;
    schema:text "Answer from the RDF View over the source tables. Create no view, table or copy for an answer; record only what was asked and what came back."@en .
pfm:step2 a schema:HowToStep ; schema:position 2 ; schema:name "Follow the join chain as links"@en ;
    schema:text "A holding points to a security (pf:ofSecurity), a security to its company (pf:ofCompany), and fundamentals and estimates point to the same company (pf:forCompany). In SQL the same chain is equity_holdings, fs_sym_ticker_region, fs_ff_sec_map, fs_ff_basic_af and, for estimates, fs_fe_sec_map and fs_fe_basic_conh_af."@en .
pfm:step3 a schema:HowToStep ; schema:position 3 ; schema:name "Apply the data-quality filters"@en ;
    schema:text "Equities only. Fundamentals: period closing 2023-01-01 or later, basic EPS present and below 50 in absolute value, sales above zero, latest row per company. Estimates: item EPS, no consensus end date, period ending today or later, absolute mean below 50, earliest period."@en .
pfm:step4 a schema:HowToStep ; schema:position 4 ; schema:name "Compute the measures"@en ;
    schema:text "Current earnings are shares times basic EPS; projected earnings are shares times mean EPS estimate; growth is (estimate minus EPS) divided by EPS; margin is net income divided by sales; debt to equity is short plus long-term debt divided by common equity. Keep holdings without an estimate, with the projection left empty."@en .
pfm:step5 a schema:HowToStep ; schema:position 5 ; schema:name "Classify risk and action"@en ;
    schema:text "Risk: unprofitable or debt to equity above 2.5 is high; growth below minus 15 percent or margin below 3 percent is medium; margin above 15 percent with debt to equity below 1.5 is low and strong; otherwise low. Action: a loss or growth below minus 15 percent is sell; above 20 percent strong buy; above 5 percent buy; above minus 10 percent hold and stable; otherwise hold and monitor."@en .
pfm:step6 a schema:HowToStep ; schema:position 6 ; schema:name "Write the run back to memory"@en ;
    schema:text "After an answer, add a schema:Action to this graph naming the question, the date and the counts returned, so the next session can read what was already decided."@en .
pfm:run1 a schema:Action ;
    schema:name "Holdings review, 2026-10-28"@en ;
    schema:startTime "2026-10-28T12:00:00Z"^^xsd:dateTime ;
    schema:instrument pfm:analysisHowTo ;
    schema:description "Reviewed 28 holdings: 3 left out by the filters (a bond, a symbol absent from symbology and a company whose reported EPS fails the plausibility test), 25 analysed, 4 of them high risk, 2 flagged sell for a loss and 2 for declining earnings."@en .

SQL over the tables (signed in)

4 cards

Standard SQL on the native tables, the post's query and its two dashboard tiles. Sign-in notice on every card.

Query 1: A plain SQL read of the first-party table

SQL

Reads the ten largest positions straight from the first-party table. A bond tops the list; the join in Query 2 leaves it out because it is not an equity.

SELECT TOP 10 holding_id, portfolio_id, symbol, instrument_type, number_of_shares
FROM PortfolioKG.kidehen.equity_holdings
ORDER BY number_of_shares DESC
Result on 2026-10-28: 10 rows, first 6 shown
holding_idportfolio_idsymbolinstrument_typenumber_of_shares
285TRSY-USBond100000
83ALDM-USEquity4000
52BWUT-USEquity3000
113GRWE-USEquity2600
21HBLS-USEquity2500
165BWUT-USEquity2400

Query 2: The Databricks query, as one SQL statement across both sides

SQL

The post's no-ETL query as one SQL statement: equity holdings, symbology, the fundamentals security map, the latest plausible fundamentals row and the current EPS consensus through a left join, with the post's risk and action rules. Returns 25 rows on 2026-10-28; the top row is Aldermoor Semiconductors with 8,000 current and 10,000 projected earnings, a strong buy.

SELECT h.symbol, h.number_of_shares AS shares_held, f.ff_eps_basic AS current_eps, e.fe_mean AS forward_eps, e.fe_num_est AS analysts,
  h.number_of_shares * f.ff_eps_basic AS my_current_annual_earnings,
  h.number_of_shares * e.fe_mean AS my_projected_annual_earnings,
  (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic * 100 AS projected_eps_growth_pct,
  f.ff_net_income * 100.0 / f.ff_sales AS profit_margin_pct,
  (f.ff_debt_st + f.ff_debt_lt) * 1.0 / f.ff_com_eq AS debt_to_equity,
  CASE WHEN f.ff_net_income < 0 THEN 'High Risk - Unprofitable'
       WHEN (f.ff_debt_st + f.ff_debt_lt) * 1.0 / f.ff_com_eq > 2.5 THEN 'High Risk - Excessive Debt'
       WHEN (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic < -0.15 THEN 'Medium Risk - Declining Earnings'
       WHEN f.ff_net_income * 100.0 / f.ff_sales < 3 THEN 'Medium Risk - Low Margins'
       WHEN f.ff_net_income * 100.0 / f.ff_sales > 15 AND (f.ff_debt_st + f.ff_debt_lt) * 1.0 / f.ff_com_eq < 1.5 THEN 'Low Risk - Strong Fundamentals'
       ELSE 'Low Risk' END AS risk_assessment,
  CASE WHEN f.ff_net_income < 0 THEN 'SELL - Company Losing Money'
       WHEN (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic < -0.15 THEN 'SELL - Earnings Declining'
       WHEN (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic > 0.20 THEN 'STRONG BUY - High Growth Expected'
       WHEN (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic > 0.05 THEN 'BUY - Positive Growth'
       WHEN (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic > -0.10 THEN 'HOLD - Stable'
       ELSE 'HOLD - Monitor' END AS action_recommendation
FROM PortfolioKG.kidehen.equity_holdings h
JOIN PortfolioKG.kidehen.fs_sym_ticker_region s ON upper(h.symbol) = s.ticker_region
JOIN PortfolioKG.kidehen.fs_ff_sec_map fm ON fm.fsym_id = s.fsym_id
JOIN PortfolioKG.kidehen.fs_ff_basic_af f ON f.fsym_id = fm.fsym_company_id
  AND f.fiscal_year = (SELECT max(f2.fiscal_year) FROM PortfolioKG.kidehen.fs_ff_basic_af f2
       WHERE f2.fsym_id = f.fsym_id AND f2.fiscal_date >= stringdate('2023-01-01') AND f2.ff_eps_basic IS NOT NULL AND abs(f2.ff_eps_basic) < 50 AND f2.ff_sales > 0)
LEFT JOIN PortfolioKG.kidehen.fs_fe_sec_map em ON em.fsym_id = s.fsym_id
LEFT JOIN PortfolioKG.kidehen.fs_fe_basic_conh_af e ON e.fsym_id = em.fsym_company_id AND e.fe_item = 'EPS' AND e.cons_end_date IS NULL
  AND e.fe_fp_end >= curdate() AND abs(e.fe_mean) < 50
  AND e.fiscal_year = (SELECT min(e2.fiscal_year) FROM PortfolioKG.kidehen.fs_fe_basic_conh_af e2
       WHERE e2.fsym_id = e.fsym_id AND e2.fe_item = 'EPS' AND e2.cons_end_date IS NULL AND e2.fe_fp_end >= curdate() AND abs(e2.fe_mean) < 50)
WHERE h.instrument_type = 'Equity'
ORDER BY my_current_annual_earnings DESC
Result on 2026-10-28: 25 rows, first 6 shown
symbolshares_heldcurrent_epsforward_epsanalystsmy_current_annual_earningsmy_projected_annual_earningsprojected_eps_growth_pctprofit_margin_pctdebt_to_equityrisk_assessmentaction_recommendation
ALDM-US400022.5118000100002516.734693877551020.290322580645161Low Risk - Strong FundamentalsSTRONG BUY - High Growth Expected
HBLS-US25002.673.166675775016.10486891385767813.913043478260870.088235294117647Low RiskBUY - Positive Growth
DNMP-US21003.043.496384714011.84210526315789521.3513513513513510.115384615384615Low Risk - Strong FundamentalsBUY - Positive Growth
FHVB-US22002.552.712561059405.88235294117647121.960784313725490.325581395348837Low Risk - Strong FundamentalsBUY - Positive Growth
BWUT-US30001.81.858540055502.7777777777777788.5714285714285712.606060606060606High Risk - Excessive DebtHOLD - Stable
GRWE-US26001.791.451046543770-18.9944134078212298.8405797101449280.804878048780488Medium Risk - Declining EarningsSELL - Earnings Declining

Query 3: Portfolio health: the dashboard's summary tile

SQL

The dashboard's portfolio-health tile: 25 holdings, 78,044.50 current and 83,270.00 projected annual earnings, 4 high-risk positions, 2 unprofitable and 2 with growth above 20 percent. The projection sums only holdings that have a current consensus.

SELECT count(*) AS total_holdings, sum(cur) AS total_current_earnings, sum(proj) AS total_projected_earnings, sum(CASE WHEN ni < 0 OR de > 2.5 THEN 1 ELSE 0 END) AS high_risk_stocks, sum(CASE WHEN ni < 0 THEN 1 ELSE 0 END) AS unprofitable, sum(CASE WHEN growth_pct > 20 THEN 1 ELSE 0 END) AS high_growth FROM (
SELECT h.symbol AS symbol, h.number_of_shares * f.ff_eps_basic AS cur, h.number_of_shares * e.fe_mean AS proj,
  (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic * 100 AS growth_pct, f.ff_net_income AS ni,
  f.ff_net_income * 100.0 / f.ff_sales AS margin_pct, (f.ff_debt_st + f.ff_debt_lt) * 1.0 / f.ff_com_eq AS de
FROM PortfolioKG.kidehen.equity_holdings h
JOIN PortfolioKG.kidehen.fs_sym_ticker_region s ON upper(h.symbol) = s.ticker_region
JOIN PortfolioKG.kidehen.fs_ff_sec_map fm ON fm.fsym_id = s.fsym_id
JOIN PortfolioKG.kidehen.fs_ff_basic_af f ON f.fsym_id = fm.fsym_company_id
  AND f.fiscal_year = (SELECT max(f2.fiscal_year) FROM PortfolioKG.kidehen.fs_ff_basic_af f2
       WHERE f2.fsym_id = f.fsym_id AND f2.fiscal_date >= stringdate('2023-01-01') AND f2.ff_eps_basic IS NOT NULL AND abs(f2.ff_eps_basic) < 50 AND f2.ff_sales > 0)
LEFT JOIN PortfolioKG.kidehen.fs_fe_sec_map em ON em.fsym_id = s.fsym_id
LEFT JOIN PortfolioKG.kidehen.fs_fe_basic_conh_af e ON e.fsym_id = em.fsym_company_id AND e.fe_item = 'EPS' AND e.cons_end_date IS NULL
  AND e.fe_fp_end >= curdate() AND abs(e.fe_mean) < 50
  AND e.fiscal_year = (SELECT min(e2.fiscal_year) FROM PortfolioKG.kidehen.fs_fe_basic_conh_af e2
       WHERE e2.fsym_id = e.fsym_id AND e2.fe_item = 'EPS' AND e2.cons_end_date IS NULL AND e2.fe_fp_end >= curdate() AND abs(e2.fe_mean) < 50)
WHERE h.instrument_type = 'Equity'
) AS per_holding
Result on 2026-10-28: 1 row
total_holdingstotal_current_earningstotal_projected_earningshigh_risk_stocksunprofitablehigh_growth
2578044.583270422

Query 4: Risk distribution: the dashboard's second tile

SQL

The dashboard's risk-distribution tile: 4 high-risk, 5 medium-risk and 16 low-risk positions, with the earnings each class carries.

SELECT risk_category, count(*) AS num_stocks, sum(cur) AS total_earnings FROM ( SELECT CASE WHEN ni < 0 OR de > 2.5 THEN 'High Risk' WHEN growth_pct < -15 OR margin_pct < 3 THEN 'Medium Risk' ELSE 'Low Risk' END AS risk_category, cur FROM (
SELECT h.symbol AS symbol, h.number_of_shares * f.ff_eps_basic AS cur, h.number_of_shares * e.fe_mean AS proj,
  (e.fe_mean - f.ff_eps_basic) / f.ff_eps_basic * 100 AS growth_pct, f.ff_net_income AS ni,
  f.ff_net_income * 100.0 / f.ff_sales AS margin_pct, (f.ff_debt_st + f.ff_debt_lt) * 1.0 / f.ff_com_eq AS de
FROM PortfolioKG.kidehen.equity_holdings h
JOIN PortfolioKG.kidehen.fs_sym_ticker_region s ON upper(h.symbol) = s.ticker_region
JOIN PortfolioKG.kidehen.fs_ff_sec_map fm ON fm.fsym_id = s.fsym_id
JOIN PortfolioKG.kidehen.fs_ff_basic_af f ON f.fsym_id = fm.fsym_company_id
  AND f.fiscal_year = (SELECT max(f2.fiscal_year) FROM PortfolioKG.kidehen.fs_ff_basic_af f2
       WHERE f2.fsym_id = f.fsym_id AND f2.fiscal_date >= stringdate('2023-01-01') AND f2.ff_eps_basic IS NOT NULL AND abs(f2.ff_eps_basic) < 50 AND f2.ff_sales > 0)
LEFT JOIN PortfolioKG.kidehen.fs_fe_sec_map em ON em.fsym_id = s.fsym_id
LEFT JOIN PortfolioKG.kidehen.fs_fe_basic_conh_af e ON e.fsym_id = em.fsym_company_id AND e.fe_item = 'EPS' AND e.cons_end_date IS NULL
  AND e.fe_fp_end >= curdate() AND abs(e.fe_mean) < 50
  AND e.fiscal_year = (SELECT min(e2.fiscal_year) FROM PortfolioKG.kidehen.fs_fe_basic_conh_af e2
       WHERE e2.fsym_id = e.fsym_id AND e2.fe_item = 'EPS' AND e2.cons_end_date IS NULL AND e2.fe_fp_end >= curdate() AND abs(e2.fe_mean) < 50)
WHERE h.instrument_type = 'Equity'
) AS per_holding ) AS r GROUP BY risk_category ORDER BY risk_category
Result on 2026-10-28: 3 rows
risk_categorynum_stockstotal_earnings
High Risk48708
Low Risk1657484.5
Medium Risk511852

SPASQL over the RDF View (signed in)

1 card

SQL on the outside, two SPARQL results inside, joined by the company's hyperlink-based super key. Sign-in notice on the card.

Query 5: The same answer through the RDF View: SQL over two SPARQL tables

SPASQL

The same per-holding answer through the RDF View. Two SPARQL results, holdings with fundamentals and the current consensus, are joined by the company's hyperlink-based super key in SQL, and the post's rules are applied in the outer statement. Returns the same 25 holdings as Query 2 with identical figures.

SELECT h.symbol, h.name, h.shares * h.eps AS current_earnings, h.shares * e.fwd AS projected_earnings,
  CASE WHEN h.ni < 0 THEN 'High Risk - Unprofitable'
       WHEN (h.dst + h.dlt) * 1.0 / h.eq > 2.5 THEN 'High Risk - Excessive Debt'
       WHEN e.fwd IS NOT NULL AND (e.fwd - h.eps) / h.eps < -0.15 THEN 'Medium Risk - Declining Earnings'
       WHEN h.ni * 100.0 / h.sales < 3 THEN 'Medium Risk - Low Margins'
       WHEN h.ni * 100.0 / h.sales > 15 AND (h.dst + h.dlt) * 1.0 / h.eq < 1.5 THEN 'Low Risk - Strong Fundamentals'
       ELSE 'Low Risk' END AS risk_assessment,
  CASE WHEN h.ni < 0 THEN 'SELL - Company Losing Money'
       WHEN e.fwd IS NOT NULL AND (e.fwd - h.eps) / h.eps < -0.15 THEN 'SELL - Earnings Declining'
       WHEN e.fwd IS NOT NULL AND (e.fwd - h.eps) / h.eps > 0.20 THEN 'STRONG BUY - High Growth Expected'
       WHEN e.fwd IS NOT NULL AND (e.fwd - h.eps) / h.eps > 0.05 THEN 'BUY - Positive Growth'
       WHEN e.fwd IS NOT NULL AND (e.fwd - h.eps) / h.eps > -0.10 THEN 'HOLD - Stable'
       ELSE 'HOLD - Monitor' END AS action_recommendation
FROM (SPARQL
  PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
  PREFIX schema: <http://schema.org/>
  SELECT ?holding ?symbol ?company ?name ?shares ?eps ?ni ?sales ?dst ?dlt ?eq
  FROM <http://demo.openlinksw.com/PortfolioKG#>
  WHERE {
    ?holding a pf:Holding ; pf:symbol ?symbol ; pf:instrumentType "Equity" ; pf:shares ?shares ; pf:ofSecurity ?security .
    ?security pf:ofCompany ?company .
    ?company schema:name ?name .
    ?fin pf:forCompany ?company ; pf:fiscalYear 2025 ; pf:sales ?sales ; pf:netIncome ?ni ;
         pf:epsBasic ?eps ; pf:debtShortTerm ?dst ; pf:debtLongTerm ?dlt ; pf:commonEquity ?eq .
    FILTER(ABS(?eps) < 50 && ?sales > 0)
  }) AS h
LEFT JOIN (SPARQL
  PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
  SELECT ?company ?fwd
  FROM <http://demo.openlinksw.com/PortfolioKG#>
  WHERE {
    ?est pf:forCompany ?company ; pf:estimateItem "EPS" ; pf:fiscalYear 2026 ; pf:epsMean ?fwd .
    OPTIONAL { ?est pf:consensusEnded ?ended }
    FILTER(!BOUND(?ended))
  }) AS e ON h.company = e.company
ORDER BY current_earnings DESC
Result on 2026-10-28: 25 rows, first 6 shown
symbolnamecurrent_earningsprojected_earningsrisk_assessmentaction_recommendation
ALDM-USAldermoor Semiconductors800010000Low Risk - Strong FundamentalsSTRONG BUY - High Growth Expected
HBLS-USHarborline Software66757750Low RiskBUY - Positive Growth
DNMP-USDunmore Pharma63847140Low Risk - Strong FundamentalsBUY - Positive Growth
FHVB-USFairhaven Bancorp56105940Low Risk - Strong FundamentalsBUY - Positive Growth
BWUT-USBrightwater Utilities54005550High Risk - Excessive DebtHOLD - Stable
GRWE-USGranitewood Energy46543770Medium Risk - Declining EarningsSELL - Earnings Declining

SPARQL over the RDF View (anonymous endpoint)

7 cards

No sign-in needed. Run links request plain HTML so that every hyperlink-based super key in a result links to its own description page, scoped to the view's graph. Each query takes a few seconds to compile on this server.

Query 6: What is in my book: holdings reach their companies by links

SPARQL

Follows the links from each equity holding to its security, company and latest fundamentals: 25 rows, each with the hyperlink-based super keys of the holding and the company.

PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
PREFIX schema: <http://schema.org/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT ?holding ?symbol ?company ?name ?shares ?eps
FROM <http://demo.openlinksw.com/PortfolioKG#>
WHERE {
  ?holding a pf:Holding ; pf:symbol ?symbol ; pf:instrumentType "Equity" ; pf:shares ?shares ; pf:ofSecurity ?security .
  ?security pf:ofCompany ?company .
  ?company schema:name ?name .
  ?fin pf:forCompany ?company ; pf:fiscalYear 2025 ; pf:epsBasic ?eps .
  FILTER(ABS(?eps) < 50)
}
Result on 2026-10-28: 25 rows, first 6 shown
holdingsymbolcompanynameshareseps
PortfolioKG/holding/1#thisALDM-USPortfolioKG/company/C1001-E#thisAldermoor Semiconductors12002
PortfolioKG/holding/8#thisALDM-USPortfolioKG/company/C1001-E#thisAldermoor Semiconductors40002
PortfolioKG/holding/5#thisBWUT-USPortfolioKG/company/C1002-E#thisBrightwater Utilities30001.8
PortfolioKG/holding/16#thisBWUT-USPortfolioKG/company/C1002-E#thisBrightwater Utilities24001.8
PortfolioKG/holding/12#thisCLWY-USPortfolioKG/company/C1003-E#thisCalloway Retail Group9001.17
PortfolioKG/holding/22#thisCLWY-USPortfolioKG/company/C1003-E#thisCalloway Retail Group14001.17

Query 7: Current analyst consensus for the companies I hold

SPARQL

Follows the links from each holding to its company's current EPS consensus for 2026: 24 rows, one holding fewer than Query 6 because Meridian Telecom's consensus has ended.

PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
PREFIX schema: <http://schema.org/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT ?holding ?symbol ?company ?name ?estimate ?mean ?analysts
FROM <http://demo.openlinksw.com/PortfolioKG#>
WHERE {
  ?holding a pf:Holding ; pf:symbol ?symbol ; pf:ofSecurity ?security .
  ?security pf:ofCompany ?company .
  ?company schema:name ?name .
  ?estimate pf:forCompany ?company ; pf:estimateItem "EPS" ; pf:fiscalYear 2026 ; pf:epsMean ?mean ; pf:analystCount ?analysts .
  OPTIONAL { ?estimate pf:consensusEnded ?ended }
  FILTER(!BOUND(?ended))
}
Result on 2026-10-28: 24 rows, first 6 shown

Query 8: Exposure by sector

SPARQL

Counts holdings and shares by sector across the link from holding to security to company: 9 sectors.

PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
PREFIX schema: <http://schema.org/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT ?sector (COUNT(DISTINCT ?holding) AS ?holdings) (SUM(?shares) AS ?shares_held)
FROM <http://demo.openlinksw.com/PortfolioKG#>
WHERE {
  ?holding pf:shares ?shares ; pf:ofSecurity ?security .
  ?security pf:ofCompany ?company .
  ?company pf:sector ?sector .
}
GROUP BY ?sector
Result on 2026-10-28: 9 rows, first 6 shown
sectorholdingsshares_held
Consumer45000
Energy24500
Communication1700
Financials23300
Materials22300
Healthcare45200

Query 9: Holdings the join leaves out

SPARQL

Lists the holdings that never reach a security: a symbol absent from symbology (ZZZX-US) and a bond (TRSY-US). Both super keys are returned.

PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
SELECT ?holding ?symbol ?type ?shares
FROM <http://demo.openlinksw.com/PortfolioKG#>
WHERE {
  ?holding a pf:Holding ; pf:symbol ?symbol ; pf:instrumentType ?type ; pf:shares ?shares .
  OPTIONAL { ?holding pf:ofSecurity ?security }
  FILTER(!BOUND(?security))
}
Result on 2026-10-28: 2 rows
holdingsymboltypeshares
PortfolioKG/holding/15#thisZZZX-USEquity500
PortfolioKG/holding/28#thisTRSY-USBond100000

Query 10: Marketplace records, found by entailment

SPARQL

Asks for marketplace records and receives six classes by entailment: Security, Company, AnnualFinancials, EarningsEstimate, the declared MarketplaceRecord and, because Company is also a schema.org Organization, Organization.

DEFINE input:inference "urn:portfoliokg:inference"
PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
PREFIX schema: <http://schema.org/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT DISTINCT ?class
FROM <http://demo.openlinksw.com/PortfolioKG#>
WHERE { ?record a pf:MarketplaceRecord ; a ?class }
Result on 2026-10-28: 6 rows

Query 11: The standing instructions, read from memory

SPARQL

Reads the six HowTo steps in the memory graph, which carry the join chain, filters, measures and rules the post gives to its Genie space.

PREFIX schema: <http://schema.org/>
SELECT ?step ?position ?name ?text
FROM <http://demo.openlinksw.com/PortfolioKG/memory#>
WHERE { ?step a schema:HowToStep ; schema:position ?position ; schema:name ?name ; schema:text ?text }
Result on 2026-10-28: 6 rows
steppositionnametext
PortfolioKGMemory#step33Apply the data-quality filtersEquities only. Fundamentals: period closing 2023-01-01 or later, basic EPS present and below 50 in absolute value, sales above zero, latest row per company. Estimates: item EPS, no consensus end date, period ending today or later, absolute mean below 50, earliest period.
PortfolioKGMemory#step11Query in place, persist nothingAnswer from the RDF View over the source tables. Create no view, table or copy for an answer; record only what was asked and what came back.
PortfolioKGMemory#step22Follow the join chain as linksA holding points to a security (pf:ofSecurity), a security to its company (pf:ofCompany), and fundamentals and estimates point to the same company (pf:forCompany). In SQL the same chain is equity_holdings, fs_sym_ticker_region, fs_ff_sec_map, fs_ff_basic_af and, for estimates, fs_fe_sec_map and fs_fe_basic_conh_af.
PortfolioKGMemory#step44Compute the measuresCurrent earnings are shares times basic EPS; projected earnings are shares times mean EPS estimate; growth is (estimate minus EPS) divided by EPS; margin is net income divided by sales; debt to equity is short plus long-term debt divided by common equity. Keep holdings without an estimate, with the projection left empty.
PortfolioKGMemory#step55Classify risk and actionRisk: unprofitable or debt to equity above 2.5 is high; growth below minus 15 percent or margin below 3 percent is medium; margin above 15 percent with debt to equity below 1.5 is low and strong; otherwise low. Action: a loss or growth below minus 15 percent is sell; above 20 percent strong buy; above 5 percent buy; above minus 10 percent hold and stable; otherwise hold and monitor.
PortfolioKGMemory#step66Write the run back to memoryAfter an answer, add a schema:Action to this graph naming the question, the date and the counts returned, so the next session can read what was already decided.

Query 12: What was decided last time, read from memory

SPARQL

Reads the run record in the memory graph: 28 holdings reviewed, 3 left out by the filters, 25 analysed.

PREFIX schema: <http://schema.org/>
SELECT ?run ?name ?when ?note
FROM <http://demo.openlinksw.com/PortfolioKG/memory#>
WHERE { ?run a schema:Action ; schema:name ?name ; schema:startTime ?when ; schema:description ?note }
Result on 2026-10-28: 1 row
runnamewhennote
PortfolioKGMemory#run1Holdings review, 2026-10-282026-10-28T12:00:00ZReviewed 28 holdings: 3 left out by the filters (a bond, a symbol absent from symbology and a company whose reported EPS fails the plausibility test), 25 analysed, 4 of them high risk, 2 flagged sell for a loss and 2 for declining earnings.
Server 2

URIBurner

A different Virtuoso instance reading the view on demo.

SPARQL-FED across servers

1 card

One SERVICE call from URIBurner to the endpoint on demo. If the first run reports a transient error, run it again.

Query 13: From URIBurner: the PortfolioKG companies on demo through SERVICE

SPARQL

Run on URIBurner: a SERVICE call to demo reads the 14 PortfolioKG companies with their hyperlink-based super keys, names and sectors. It shows the view answering a query from a different server.

PREFIX pf: <http://demo.openlinksw.com/schemas/PortfolioKG/>
PREFIX schema: <http://schema.org/>
SELECT ?company ?name ?sector
WHERE {
  SERVICE <https://demo.openlinksw.com/sparql> {
    SELECT ?company ?name ?sector
    FROM <http://demo.openlinksw.com/PortfolioKG#>
    WHERE { ?company a pf:Company ; schema:name ?name ; pf:sector ?sector }
  }
}
LIMIT 20
Result on 2026-10-28: 14 rows, first 6 shown
companynamesector
PortfolioKG/company/C1001-E#thisAldermoor SemiconductorsTechnology
PortfolioKG/company/C1002-E#thisBrightwater UtilitiesUtilities
PortfolioKG/company/C1003-E#thisCalloway Retail GroupConsumer
PortfolioKG/company/C1004-E#thisDunmore PharmaHealthcare
PortfolioKG/company/C1005-E#thisEastgate LogisticsIndustrials
PortfolioKG/company/C1006-E#thisFairhaven BancorpFinancials
ALREADY LIVE

Demos Already Published on the Two Weblogs

Eight pages on the URIBurner and OpenLink weblogs already run live against demo.openlinksw.com. They cover the parts of the post that the PortfolioKG simulation stands in for: a real attached SQL Server, dashboards from RDF Views, other attached databases, an OPAL agent, Databricks tables as a source, and a query federated across two instances.

1

WWW weblog: Customer Sales Performance Dashboard, SPASQL

A SQL Server database attached to demo, published as an RDF View, and queried with SPASQL and SPARQL into a dashboard. It is the closest published counterpart to the on-premises SQL Server of the post.

Open the weblog post

2

WWW weblog: Northwind Sales Performance Dashboard

An interactive sales dashboard whose data comes from SPARQL over an RDF View on demo.

Open the weblog post

3

WWW weblog: Virtuoso Multi-Model Data Management Breakdown

RDF Views of SQL Server, Oracle, Informix and CSV data on demo, side by side.

Open the weblog post

4

WWW weblog: PostgreSQL knowledge graph and Linked Data principles

A PostgreSQL database attached over JDBC, published as an RDF View, with its Linked Data identifiers walked through.

Open the weblog post

5

WWW weblog: OPAL agent, Spike Lee filmography

An OPAL agent answering from DBpedia, as a demonstration of natural-language access through the OPAL modules.

Open the weblog post

6

UB weblog: Databricks to a virtual knowledge graph with Virtuoso

Databricks tables mapped with R2RML and published as a virtual knowledge graph on demo.

Open the weblog post

7

UB weblog: Agentic Marketing, Databricks, Virtuoso and Both Together

An earlier meshup with the same server: a CustomerKG simulation, Databricks tables attached over ODBC, SPASQL, and federated queries run from URIBurner.

Open the weblog post

8

UB weblog: Scale AI into Production, Neo4j meshed with Virtuoso

A ScaleKG simulation on demo and a query run on URIBurner that joins its own graph to the view on demo.

Open the weblog post

THE THESIS

Hyperlinks Replace the Key Chain

The thesis this collection tests: the key chain a federated join needs, ticker to security to company, can be replaced by hyperlink-based super keys declared once in a view, and a view built that way can be queried in three languages, read by agents, and reasoned over, while the data stays in its source. A Semantic Web built this way does not depend on one vendor's catalog.

1

A join key is a hyperlink-based super key.

In the post a ticker becomes a security id, then a company id, and each hop is a join in the query. In the RDF View each entity has a hyperlink-based super key that resolves, the hops are declared once as links, and a query follows them.

2

The query is a path.

From a holding to its company to that company's fundamentals and estimates is one path through the graph, and the same path answers the dashboard, the agent and the analyst.

3

Instructions are data.

The join chain and data-quality filters that Genie receives in its instructions are, in VOICE, HowTo steps in Turtle that any agent can read and a person can edit.

4

Names outlive the copy.

Every company, holding and ontology term is a hyperlink-based super key that resolves on the Web, so a result row can be cited and followed from another document or another server.

5

A virtual view still reasons.

Classes declared in the ontology give the view entailments: asking for marketplace records returns securities, companies, financials and estimates without listing them.

6

Knowledge is readable, not locked in code.

The post's example lives in SQL, notebooks and job scripts. In VOICE the ontology, the view and the instructions are Turtle that knowledge workers and developers can both read, and either can ask questions of it, in natural language or in a query language.

7

The article builds its own demo.

VOICE generates the live demo as part of constructing the article: the data, the ontology, the view, the rewrite rules and the queries come from skills, run on the public server, and the page links each claim to the query that checks it.

How-To

How-To Guide

1

Create the tables

Run Part 1 of the batch script: three first-party tables, then the six marketplace-style tables and their 154 rows. The statements are standard SQL.

2

Create the IRI classes

Run Part 2: one CREATE IRI CLASS per entity type. Each can take minutes on a busy server.

3

Load the ontology and derive the rule set

Run Part 2b: load the Turtle into the schemas graph and derive the inference rule set from it.

4

Create the RDF View

Run Part 3: one ALTER QUAD STORAGE for the graph http://demo.openlinksw.com/PortfolioKG#. A second statement for the same graph would replace the first.

5

Grant read access

Run Part 4: SELECT on each table to SPARQL, PUBLIC, demo and vdb, so the anonymous endpoint and the signed-in Query Builder can read the view.

6

Add the rewrite rules

Run Part 5: the generated rules and the two virtual hosts, with the describe rules edited to pass uri and graph.

7

Load the memory graph

Run Part 6 to load the instructions and the run record into their own named graph.

8

Sign in and run the SQL and SPASQL queries

Sign in as demo or vdb, where the user id and password are identical, and run Queries 1 to 5 in the Query Builder; the instance is protected by ABAC ACLs.

9

Run the SPARQL queries and check coverage

Run Queries 6 to 12 on the anonymous endpoint, then compare the predicates and classes the view uses with those the ontology declares.

FAQ

Frequently Asked Questions

A wealth manager federates an on-premises SQL Server holdings database, subscribes to FactSet symbology, fundamentals and estimates from the Databricks Marketplace, joins them in one SQL query that persists nothing, and uses that query for an AI/BI dashboard and a Genie space.

The name this collection uses for Virtuoso with the OPAL modules and agent-rdf-memory installed, together with the repository skills that generate ontologies, RDF Views and rewrite rules.

PortfolioKG: nine SQL tables of invented data (154 rows), an ontology, an RDF View that projects 926 triples, rewrite rules that make its hyperlink-based super keys resolve, a memory graph, and thirteen queries. Nothing existing on the server was changed.

No. Every client, company, ticker and figure is invented, and neither Databricks nor FactSet supplied any row. The table shapes follow the symbology, fundamentals and estimates tables the post joins.

From the no-ETL query in the repository the post links. The same thresholds, filters and ranking rules are used in Queries 2 to 5, and their results equal an independent calculation of the same rules.

The view declares the links once: holding to security, security to company, company to fundamentals and estimates. Queries 6 and 7 follow them as paths, and every node on the path is a hyperlink-based super key that resolves.

Three first-party tables on demo stand in for it in the simulation. A real SQL Server attached to the same server, published as an RDF View and queried with SPASQL, is shown by the SQL Server demo on the OpenLink weblog.

Six HowTo steps in a memory graph carry the join chain, filters, measures and rules, and a run record sits beside them. Queries 11 and 12 read both.

Sign in as demo or vdb, where the user id and the password are identical; the instance is protected by ABAC ACLs. The cards give the credentials and a Query Builder link with them preset. SPARQL queries run on the anonymous endpoint with no sign-in.

The post describes a Marketplace with listed providers, a request and approval flow and a native shared catalog. VOICE can attach or load the same tables, but a catalog of listed providers was not part of what ran.

Eight pages on the two OpenLink weblogs that already run live on demo: an attached SQL Server, dashboards from RDF Views, other attached databases, an OPAL agent, Databricks tables as a source, and a query federated across two instances.

Knowledge workers and developers. Developers read and extend the ontology, the view and the queries; knowledge workers ask in natural language through skills, and the rules they rely on are HowTo steps in Turtle, where the post's example is a developer-centric walk-through with its knowledge locked up in code.

VOICE generated it as part of constructing this article: the invented data, ontology, RDF View, rewrite rules and thirteen queries were produced with skills and run on demo.openlinksw.com, and each claim on the page links to the query that checks it.

The five comparison terms (Position, ofDimension, ofSubject, stance and hasRelation) are the registry's canonical terms and are reused, not minted. The one term minted here, the evidence tier behind a comparison row, is defined with the registry terms it builds on in the meshup ontology file, and the PortfolioKG terms are defined in their own ontology file, which is also what the view's hyperlink-based super keys resolve against on demo.

Asking for marketplace records returned securities, companies, financials and estimates, because the ontology declares them as subclasses; Company also returned as a schema.org Organization.

Neither could be exercised on the demo data, and the post does not state where the join executes. A row that can be neither demonstrated nor rated objectively is left out rather than rated.

Glossary

Glossary of Terms

Debt to equity

Short-term plus long-term debt divided by common equity, used in the post's risk classes.

Entailment

A query-time inference, such as treating members of a subclass as members of its superclass.

Earnings per share

Net income divided by shares outstanding; the post multiplies it by shares held to get a holding's earnings.

Lakehouse Federation

The Databricks feature that queries an external database in place through a connection and a foreign catalog.

Foreign catalog

A Unity Catalog object that makes the tables of an external database appear as native catalog tables.

Genie

The Databricks natural-language space that turns questions into queries using the instructions it is given.

IRI class

A rule that builds an entity's hyperlink-based super key from key columns, such as company/C1001-E#this from a company id.

Linked Data

Publishing data so that each thing has a name that resolves to a description and links to other names.

Marketplace share

A third-party dataset delivered to a workspace as a shared catalog after a subscription.

MCP

Model Context Protocol, through which agents call tools such as OPAL's SPARQL tool.

agent-rdf-memory

A Turtle-based operational memory of preferences, howtos, sessions and entities that agents read and write.

Named graph

A set of triples with its own hyperlink-based super key, used to scope queries and access.

OPAL

OpenLink's agent module set for Virtuoso, exposing tools over MCP and an Agent2Agent card.

Quad map

The statement that declares which table columns become which triples of an RDF View.

RDF View

A mapping that publishes relational rows as RDF with hyperlink-based super keys and queries them in place.

URL rewrite rule

A server rule that answers an entity's hyperlink-based super key with a redirect to a graph-scoped description page.

SPARQL

The W3C query language for RDF graphs.

SPASQL

SQL with SPARQL inside a derived table, so one statement can join SPARQL results with SQL tables.

Symbology

Tables that map a ticker to the internal identifiers of a data provider.

VOICE

Virtuoso Open Interoperable Context Engine: Virtuoso with the OPAL modules and agent-rdf-memory installed.

Knowledge Graph Explorer 274 nodes · 688 links

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

Federate, Subscribe, Join: Lakehouse Federation Read Against VOICE

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

Query this knowledge graph on URIBurner. The editor opens on the canonical SAMPLE entity-type summary (DAV named graph). Pick a recipe, edit freely, then run live or copy.

Query editor

▶ Run live on URIBurner SELECT: text/x-html+tr | DESCRIBE/CONSTRUCT: text/x-html-nice-turtle