From 93efb8fb736fe8758bbcf801401ba5971cde4703 Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Fri, 8 May 2026 13:46:42 -0400 Subject: [PATCH 1/7] First draft adding support for ontologies to the core spec. --- core-spec/spec.md | 247 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 232 insertions(+), 15 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index 37982512..17659274 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -13,10 +13,12 @@ 1. [Enumerations](#enumerations) 2. [Semantic Model](#semantic-model) 3. [Datasets](#datasets) -4. [Relationships](#relationships) +4. [Join paths](#join-paths) 5. [Fields](#fields) 6. [Metrics](#metrics) -7. [Examples](#examples) +7. [Concepts](#concepts) +8. [Mappings](#mappings) +9. [Complete Example](#complete-example) --- @@ -37,6 +39,15 @@ Supported SQL and expression language dialects for metrics and field definitions | `DATABRICKS` | Databricks SQL | | `MAQL` | GoodData MAQL (Metric Analysis and Query Language) | +### Multiplicities + +The allowable multiplicities of relationships defined in the [Concepts](#concepts) section. + +| Multiplicity | Description | +|---------|-------------| +| `ManyToOne` | The last role of a relationship is uniquely determined by the other roles | +| `OneToOne` | The relationship is ManyToMany in both directions (only for binary relationships) | + ### Vendors Supported vendors for custom extensions and integrations. @@ -52,7 +63,7 @@ Supported vendors for custom extensions and integrations. ## Semantic Model -The top-level container that represents a complete semantic model, including datasets, relationships, and metrics. +The top-level container that represents a complete semantic model, including datasets, join paths, and metrics. ### Schema @@ -62,7 +73,7 @@ The top-level container that represents a complete semantic model, including dat | `description` | string | No | Human-readable description | | `ai_context` | string/object | No | Additional context for AI tools (e.g., custom instructions) | | `datasets` | array | Yes | Collection of logical datasets (fact and dimension tables) | -| `relationships` | array | No | Defines how logical datasets are connected | +| `join_paths` | array | No | Defines how logical datasets are connected | | `metrics` | array | No | Quantifiable measures defined as aggregate expessions on fields from logical datsets | | `custom_extensions` | array | No | Vendor-specific attributes for extensibility | @@ -75,7 +86,7 @@ semantic_model: ai_context: instructions: "Use this model for sales analysis and customer insights" datasets: [] - relationships: [] + join_paths: [] metrics: [] custom_extensions: - vendor_name: DBT @@ -143,17 +154,17 @@ datasets: --- -## Relationships +## Join paths -Relationships define how logical datasets are connected through foreign key constraints. They support both simple and composite keys. +Join paths define how logical datasets are connected through foreign key constraints. They support both simple and composite keys. ### Schema | Field | Type | Required | Description | |-------|------|----------|-------------| -| `name` | string | Yes | Unique identifier for the relationship | -| `from` | string | Yes | The logical dataset on the many side of the relationship | -| `to` | string | Yes | The logical dataset on the one side of the relationship | +| `name` | string | Yes | Unique identifier for the join path | +| `from` | string | Yes | The logical dataset on the many side of the join path | +| `to` | string | Yes | The logical dataset on the one side of the join path | | `from_columns` | array | Yes | Array of column names in the "from" dataset (foreign key columns) | | `to_columns` | array | Yes | Array of column names in the "to" dataset (primary or unique key columns) | | `ai_context` | string/object | No | Additional context for AI tools | @@ -163,12 +174,12 @@ Relationships define how logical datasets are connected through foreign key cons - The order of columns in `from_columns` must correspond to the order in `to_columns` - Both arrays must have the same number of columns -- For simple relationships, use a single column: `[column1]` -- For composite relationships, use multiple columns: `[column1, column2]` +- For simple join paths, use a single column: `[column1]` +- For composite join paths, use multiple columns: `[column1, column2]` ### Examples -**Simple Relationship:** +**Simple Join Path:** ```yaml - name: orders_to_customers @@ -178,7 +189,7 @@ Relationships define how logical datasets are connected through foreign key cons to_columns: [id] ``` -**Composite Relationship:** +**Composite Join Path:** ```yaml # order_lines.product_id = products.id AND order_lines.variant_id = products.variant_id @@ -412,6 +423,206 @@ custom_extensions: --- +## Concepts + +Concepts represent the types of things that have meaning in a business setting, e.g., person, company, or +salary. Each concept is either an entity type or a value type. Every ontology starts with a value-type +concept for each basic data type, like `Integer`, `Decimal`, and `String`, and an entity-type concept +called `Any`. Every other concept in the ontology extends one of these starter concepts. + +### Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Unique identifier for this concept | +| `description` | string | No | Human-readable description | +| `relationships` | object | No | Relationships where this concept plays the first role | +| `extends` | list | No | Names of this concept's supertypes | +| `derived_by` | list | No | Expressions for deriving this concept's population | +| `identify_by` | list | No | Names of relationships that collectively identify this concept | +| `requires` | list | No | Expressions that constrain this concept's population | + +### Extends + +Every concept that a user declares extends one or more concepts in the ontology. The new concept +is a sutype of each concept that it extends, and the extended concepts are its supertypes. For instance,`SocialSecurityNr` extends `Integer`, `Person` extends `Any`, and `Employee` extends `Person`. Any +concept that directly or indirectly extends a value type like `Integer` or `String` is a value type. +A concept is an entity type if it is not a value type. Any concept with an empty extends list defaults +to being a subtype of `Any` and so is an entity type. + +### Relationships + +Relationships relate objects of one or more concepts and declare how to verbalize links among those objects. +For instance, a relationship named `earns` links persons to salaries. Each link pairs some `Person` with +some `Salary` and verbalizes as, “Person earns Salary annually.” Relationships have set (as opposed to bag) +semantics, and links do not contain nulls. + +#### Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Part of the identifier for this relationship | +| `description` | string | No | Human-readable description | +| `multiplicity` | enum | No | Multiplicity constraint | +| `roles` | list | No | List of additional roles in this relationship | +| `derived_by` | list | No | Expressions that derive links of this relationship | +| `requires` | list | No | Expressions that constrain this relationship's population | +| `verbalization` | list | Yes | Patterns describing how to verbalize links | + +Each relationship is uniquely identified by a prepending its declared name with that of the containing +concept. For instance, a relationship named `earns`, declared within the `Person` concept is identified +by the string `Person.earns`. This convention naturally supports expressions that navigate over the links +of relationships using the “dot-join” operator in a manner that is familiar to object-oriented programming +languages. + +#### Multiplicities + +In a relationship that comprises more than one role, the last role might be functionally dermined +by the other roles. This is declared by providing a ManyToOne multiplicity on that relationship. +For relationships of ternary and higher arity, the multiplicity applies to the n-th role, meaning +the object that plays the n-th role is functionally determined by the tuple of objects that play +the first n-1 roles. A relationship like `Item.total_sales_in`, which records the total sales amount +of an item at a given store, will have a ManyToOne multiplicity to indicate that for any pair +of `Item` and `Store` at most one `Amount` will be recorded as the total sales amount for that +`Item` and that `Store`. + +In the special case of a binary relationship, one might declare a OneToOne multiplicity, which +indicates the relationship is ManyToOne in both directions. For instance, the `Person.ssn` +relationship will have a OneToOne multiplicity because each person is assigned at most one social +security number and each social security number is assigned to at most one person. + +In the absence of any multiplicity, we make no assumptions of functional dependencies among +any of the roles. + +#### Roles + +Objects play roles in the links of a relationship. If you think of a relationship as a narrow table, +then links are its rows and roles are its columns. Each role is played by some concept, which +indicates the type of objects that can play that role in the relationship's links. In the +`Person.earns` relationship, `Person` and `Salary` play the first and second roles respectively. + +By convention, the first role of any relationship will be played by the concept under which the +relationship is declared. Any additional roles are declared in the roles field as follows: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `player` | string | Yes | Name of the concept that plays this role | +| `name` | string | No | Optional role name | + +A unary relationship like `Person.files_married_joint` declares no additional roles, +while a ternary relationship like `Person.purchased_on` declares two additional roles +played by `Vehicle` and `Date` respectively. + +The role player often suffices to distinguish the role within a given relationship. +However, the same concept can play multiple roles, such as in the ternary relationship +`Store.ships_to_in_days` that connects pairs of `Store` objects to the number of days +required to ship inventory from one store to another. When this happens, the user must +declare a distinguising name for any additional role whose player does not suffice to +distinguish it from other roles in the same relationship. + +### Identifying relationships + +Many conceptual models distinguish one or more relationships that should be used when +referecing entity-type objects in expressions and queries. For instance, the relationship +`Person.ssn` can be used to reference a person by their social security number; while +the pair of relationships `License.acct` and `License.seat_nr` can be used to reference +a license by the account and seat number. These relationships are always binary, and +their first roles are always played by the concept the relationship is used to reference. +When a set of identifying relationships is known for a concept, this knowledge can be +expressed using the identify_by list. + +### Derivation expressions + +Concepts and relationships may be derived using expressions. A derived relationship is one +whose links are derived from those of other relationships. For instance: + +```yaml +- name: Person + relationships: + parent_of: + roles: + - player: Person + name: "child" + verbalizes: [ "{Person} is a parent of {Person:child}", "{Person:child} is a child of {Person}" ] + ancestor_of: + roles: + - player: Person + name: "descendant" + derived_by: + - "Person.parent_of(descendant)" + "Person.ancestor_of.parent_of(descendant)" + taxed_at: + roles: + - player: TaxRate + derived_by: + - "10.0 WHERE ( Person.files_single AND Person.earns <= 11925 )" + - "10.0 WHERE ( Person.files_married_joint AND Person.earns <= 23850 )" + - ... +``` + +derives a link of `Person.taxed_at` for each object of the first role player (Person) +using expressions that return a TaxRate based on the person's filing status and how +much they earn. If, for some person, none of the expressions can be evaluated, then +the relationship will have no link involving that person. + +Expressions here are interpreted as rules for assembling the links of the relationship +in the same way that a SQL query is interpreted as a rule for assembling the rows of +a new table. Each expression must therefore reference each role of the relationship, +either explicitly or implicitly. When an expression is relational, then it must explicitly +reference each role. On the other handm if an expression returns a value (like 10.0 in the +two examples here) then that value will implicitly play the last role, and the expression +must reference each of the other roles explicitly. + +A derived concept is one whose population is derived from those of its supertype concepts +using one or more expressions. For instance: + +```yaml +- name: Employee + extends: [Person] + derived_by: [ "EXISTS ( Person.earns )" ] +``` + +declares that the population of Employee is derived from the population of Person by +classifying each Person who earns some salary as a Employee. + +### Requires + +The requires list contains expressions that give additional semantics to a concept or relationship +by declaring conditions that must hold over their populations. When applied to a concept, each +expression must reference the concept itself or one of its supertypes, as in: + +```yaml +- name: SocialSecurityNr + extends: [Integer] + requires: [ "0 < SocialSecurityNr", "SocialSecurityNr <= 999999999" ] +``` + +When applied to a relationship, each expression must reference one or more roles of the +relationship. For instance, in: + +```yaml +- name: Item + extends: [Integer] + relationships: + offers_in: + roles: + - player: Store + verbalizations: [ "{Item} is offered for sale in {Store}", "{Store} offers sale of {Item}" ] + total_sales_in: + roles: + - player: Store + - player: Amount + verbalizations: [ "{Item} sold for cumulative {Amount} in {Store}" ] + requires: + - "Amount > 0.0" + - "Item.offers_in(Store)" +``` + +the first expression requires any value that plays the `Amount` role to be positive while the second +requires any item that has sales in some store to be offered in that store. + +--- + ## Complete Example Here's a complete semantic model example showing all components working together: @@ -478,7 +689,7 @@ semantic_model: expression: email description: Customer email - relationships: + join_paths: - name: orders_to_customers from: orders to: customers @@ -551,6 +762,12 @@ ai_context: ## Version History +- **0.1.2** (2026-05-08): Support for both logical and conceptual modeling layers + - Core conceptual model structure + - Support for concepts, relationships, and logical -> conceptual schema mappings + - Renamed relationships in the logical (semantic) layer to join paths to avoid + conflict with relationships in the conceptual layer + - **0.1.1** (2025-12-11): Initial release - Core semantic model structure - Support for datasets, relationships, fields, and metrics From 717279877a3597fe90257a96464eaa9fef1dce01 Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Mon, 11 May 2026 17:55:31 -0400 Subject: [PATCH 2/7] Checkpointing --- core-spec/spec.md | 418 +++++++++++++++++++++++++++++++++------------- 1 file changed, 302 insertions(+), 116 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index 17659274..7d995683 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -16,9 +16,8 @@ 4. [Join paths](#join-paths) 5. [Fields](#fields) 6. [Metrics](#metrics) -7. [Concepts](#concepts) -8. [Mappings](#mappings) -9. [Complete Example](#complete-example) +7. [Ontology](#ontology) +8. [Complete Example](#complete-example) --- @@ -41,7 +40,7 @@ Supported SQL and expression language dialects for metrics and field definitions ### Multiplicities -The allowable multiplicities of relationships defined in the [Concepts](#concepts) section. +The allowable multiplicities of relationships defined in the [Ontology](#ontology) section. | Multiplicity | Description | |---------|-------------| @@ -97,7 +96,7 @@ semantic_model: ## Datasets -Logical datasets represent business entities or concepts (fact and dimension tables). They contain fields and define the structure of the data. +Logical datasets represent business entities (fact and dimension tables). They contain fields and define the structure of the data. ### Schema @@ -423,39 +422,69 @@ custom_extensions: --- -## Concepts +## Ontology -Concepts represent the types of things that have meaning in a business setting, e.g., person, company, or -salary. Each concept is either an entity type or a value type. Every ontology starts with a value-type -concept for each basic data type, like `Integer`, `Decimal`, and `String`, and an entity-type concept -called `Any`. Every other concept in the ontology extends one of these starter concepts. +Enterprise data are commonly modeled at different levels. The logical level comprises datasets and +fields, while the conceptual level is an ontology that comprises concepts, relationships, and +business rules. This section describes how to declare an ontology and, when a model also +declares datasets and fields, logical to conceptual schema mappings. + +Ontologies and schema mappings are organized hierarchically. At the top level are the concepts, +under which relationships, business rules, and schema mappings are then defined. + +### Concepts + +Concepts represent the types of things that have meaning in a business setting, e.g., person, company, +or salary. Each concept is either an entity type or a value type. Each model implicitly includes a +value-type for each basic data type, like `Integer`, `Decimal`, and `String`, and an entity type +called `Any`. Every other concept in an ontology extends (is a subtype of) one of these concepts. ### Schema | Field | Type | Required | Description | |-------|------|----------|-------------| -| `name` | string | Yes | Unique identifier for this concept | +| `concept` | string | Yes | Unique name of a concept | | `description` | string | No | Human-readable description | -| `relationships` | object | No | Relationships where this concept plays the first role | +| `relationships` | list | No | Relationships where this concept plays the first role | | `extends` | list | No | Names of this concept's supertypes | | `derived_by` | list | No | Expressions for deriving this concept's population | -| `identify_by` | list | No | Names of relationships that collectively identify this concept | +| `identify_by` | list | No | Names of relationships to use to referencing objects in expressions and queries | | `requires` | list | No | Expressions that constrain this concept's population | +| `entity_mappings` | list | No | Mappings from fields to concept objects | +| `relationship_mappings` | list | No | Mappings from fields to relatioship links | ### Extends Every concept that a user declares extends one or more concepts in the ontology. The new concept -is a sutype of each concept that it extends, and the extended concepts are its supertypes. For instance,`SocialSecurityNr` extends `Integer`, `Person` extends `Any`, and `Employee` extends `Person`. Any -concept that directly or indirectly extends a value type like `Integer` or `String` is a value type. -A concept is an entity type if it is not a value type. Any concept with an empty extends list defaults -to being a subtype of `Any` and so is an entity type. +is a sutype of each concept that it extends, and the extended concepts are its supertypes. + +Any concept that directly or indirectly extends a value type like `Integer` or `String` is a value type. +Any concept that does not extend some value type is an entity type, and if a concept declares no extends +list, then it is assumed to extend the built-in entity type `Any`. + +So if `SocialSecurityNr` extends `Integer` and `Employee` extends `Person`, which declares +no extends list, then `SocialSecurityNr` is a value type and both `Person` and `Employee` +are entity types. ### Relationships -Relationships relate objects of one or more concepts and declare how to verbalize links among those objects. -For instance, a relationship named `earns` links persons to salaries. Each link pairs some `Person` with -some `Salary` and verbalizes as, “Person earns Salary annually.” Relationships have set (as opposed to bag) -semantics, and links do not contain nulls. +Relationships relate objects of one or more concepts and declare how to verbalize links among +those objects. For instance, the following relationships: + +```yaml +ontology: + - concept: Person + relationships: + - name: earns + roles: + - player: Salary + multiplicity: ManyToOne + verbalizes: [ "{Person} earns {Salary}" ] + ... +``` + +links `Person` and `Salary` objects and verbalizes each link as “Person earns Salary.” +Relationships have set (as opposed to bag) semantics, and links do not contain nulls. #### Schema @@ -470,116 +499,124 @@ semantics, and links do not contain nulls. | `verbalization` | list | Yes | Patterns describing how to verbalize links | Each relationship is uniquely identified by a prepending its declared name with that of the containing -concept. For instance, a relationship named `earns`, declared within the `Person` concept is identified -by the string `Person.earns`. This convention naturally supports expressions that navigate over the links -of relationships using the “dot-join” operator in a manner that is familiar to object-oriented programming -languages. - -#### Multiplicities - -In a relationship that comprises more than one role, the last role might be functionally dermined -by the other roles. This is declared by providing a ManyToOne multiplicity on that relationship. -For relationships of ternary and higher arity, the multiplicity applies to the n-th role, meaning -the object that plays the n-th role is functionally determined by the tuple of objects that play -the first n-1 roles. A relationship like `Item.total_sales_in`, which records the total sales amount -of an item at a given store, will have a ManyToOne multiplicity to indicate that for any pair -of `Item` and `Store` at most one `Amount` will be recorded as the total sales amount for that -`Item` and that `Store`. - -In the special case of a binary relationship, one might declare a OneToOne multiplicity, which -indicates the relationship is ManyToOne in both directions. For instance, the `Person.ssn` -relationship will have a OneToOne multiplicity because each person is assigned at most one social -security number and each social security number is assigned to at most one person. - -In the absence of any multiplicity, we make no assumptions of functional dependencies among -any of the roles. +concept. The relationship in the previous example is identified by the string `Person.earns`. +This convention naturally supports expressions that navigate over the links of relationships using +the “dot-join” operator in a manner that is familiar to object-oriented programming languages. #### Roles Objects play roles in the links of a relationship. If you think of a relationship as a narrow table, -then links are its rows and roles are its columns. Each role is played by some concept, which -indicates the type of objects that can play that role in the relationship's links. In the +then its links are like rows and its roles are like columns. Each role is played by a concept that +constraints the type of objects that can play the role in the relationship's links. In the `Person.earns` relationship, `Person` and `Salary` play the first and second roles respectively. By convention, the first role of any relationship will be played by the concept under which the -relationship is declared. Any additional roles are declared in the roles field as follows: +relationship is declared. Any additional roles are enumerated in the roles list using this schema: | Field | Type | Required | Description | |-------|------|----------|-------------| | `player` | string | Yes | Name of the concept that plays this role | | `name` | string | No | Optional role name | -A unary relationship like `Person.files_married_joint` declares no additional roles, -while a ternary relationship like `Person.purchased_on` declares two additional roles -played by `Vehicle` and `Date` respectively. +A ternary relationship like `Person.purchased_on` declares two additional roles played by +`Vehicle` and `Date` respectively, while a unary relationship like +`Person.files_married_joint` will have an empty roles list. -The role player often suffices to distinguish the role within a given relationship. +The role player often suffices to distinguish the role within its relationship. However, the same concept can play multiple roles, such as in the ternary relationship `Store.ships_to_in_days` that connects pairs of `Store` objects to the number of days required to ship inventory from one store to another. When this happens, the user must declare a distinguising name for any additional role whose player does not suffice to distinguish it from other roles in the same relationship. +#### Multiplicities + +In a relationship that comprises more than one role, objects that play the last role could +be functionally dermined by a tuple of objects that play the other roles. This knowledge is +declared using a `ManyToOne` multiplicity constraint. In the previous example, the constraint +declares that each person earns at most one salary. + +For relationships of ternary and higher arity, the multiplicity applies to the n-th role, meaning +the object that plays the n-th role is functionally determined by the tuple of objects that play +the first n-1 roles. A relationship like `Item.total_sales_in`, which records the total sales amount +of an item at a given store, is many-to-one because for any pair of `Item` and `Store` at most one +`Amount` represents the total sales for that `Item` and that `Store`. + +In the special case of a binary relationship, one might declare a `OneToOne` multiplicity, which +indicates the relationship is many-to-one in both directions. For instance, the `Person.ssn` +relationship is one-to-one because each person is assigned at most one social security number +and each social security number is assigned to at most one person. + +In the absence of any multiplicity, we make no assumptions of functional dependencies among +any of the roles. + ### Identifying relationships -Many conceptual models distinguish one or more relationships that should be used when -referecing entity-type objects in expressions and queries. For instance, the relationship -`Person.ssn` can be used to reference a person by their social security number; while -the pair of relationships `License.acct` and `License.seat_nr` can be used to reference -a license by the account and seat number. These relationships are always binary, and -their first roles are always played by the concept the relationship is used to reference. -When a set of identifying relationships is known for a concept, this knowledge can be -expressed using the identify_by list. +Many conceptual models distinguish one or more relationships to use when referecing entity-type +objects in expressions and queries. The `Person.ssn` relationship can be used to reference a +person by their social security number; while the pair of relationships `License.acct` and +`License.seat_nr` can be used to reference a license by its associated account and seat number. +These identifier relationships are always binary, and their first role is always played by the +concept the relationship is used to reference. ### Derivation expressions -Concepts and relationships may be derived using expressions. A derived relationship is one -whose links are derived from those of other relationships. For instance: +Concepts and relationships may be derived using expressions. Think of a derived relationship +as a conceptual view whose links are derived from those of other relationships. For instance: ```yaml -- name: Person - relationships: - parent_of: - roles: - - player: Person - name: "child" - verbalizes: [ "{Person} is a parent of {Person:child}", "{Person:child} is a child of {Person}" ] - ancestor_of: - roles: - - player: Person - name: "descendant" - derived_by: - - "Person.parent_of(descendant)" - "Person.ancestor_of.parent_of(descendant)" - taxed_at: - roles: - - player: TaxRate - derived_by: - - "10.0 WHERE ( Person.files_single AND Person.earns <= 11925 )" - - "10.0 WHERE ( Person.files_married_joint AND Person.earns <= 23850 )" - - ... +ontology: + - concept: Person + relationships: + - name: parent_of + roles: + - player: Person + name: "child" + verbalizes: [ "{Person} is a parent of {Person:child}", "{Person:child} is a child of {Person}" ] + - name: ancestor_of + roles: + - player: Person + name: "descendant" + derived_by: + - "Person.parent_of(descendant)" + "Person.ancestor_of.parent_of(descendant)" + taxed_at: + roles: + - player: TaxRate + derived_by: + - "10.0 WHERE ( Person.files_single AND Person.earns <= 11925 )" + - "10.0 WHERE ( Person.files_married_joint AND Person.earns <= 23850 )" + - ... ``` -derives a link of `Person.taxed_at` for each object of the first role player (Person) -using expressions that return a TaxRate based on the person's filing status and how -much they earn. If, for some person, none of the expressions can be evaluated, then -the relationship will have no link involving that person. - -Expressions here are interpreted as rules for assembling the links of the relationship -in the same way that a SQL query is interpreted as a rule for assembling the rows of -a new table. Each expression must therefore reference each role of the relationship, -either explicitly or implicitly. When an expression is relational, then it must explicitly -reference each role. On the other handm if an expression returns a value (like 10.0 in the -two examples here) then that value will implicitly play the last role, and the expression -must reference each of the other roles explicitly. +declares two derived relationships -- `ancestor_of` and `taxed_at`. Each link of `Person.ancestor_of` +relates a person to one of its descendants. The two expressions form the base and recursive cases for +this calculation. In the base case, a `Person` as an ancestor of some `descendant` if that `Person` +is the parent of that descendant. And in the recursive case, a `Person` is an ancestor of some +`descendant` if that `Person` is an ancestor of the parent of that `descendant`. Notice in this +example how role names are used to disambiguate the two `Person` roles in this relationship. + +Each link of `Person.taxed_at` links a `Person` object to a `TaxRate` that is derived using +expressions that determine the rate based on the person's filing status and how much they earn. +If, for some person, none of the expressions can be evaluated, then the relationship will have +no link involving that person. + +Expressions that derive a relationship are interpreted as rules for constructing the links of the +relationship in the same way that a SQL query is interpreted as a rule for constructing the rows +of a new table. Each expression must therefore reference each role of the relationship, either +explicitly or implicitly. If an expression evaluates to some object (like 10.0 in the two examples +here) then that object will implicitly play the last role, and the expression must reference each +of the other roles explicitly. If an expression does not evaluate to any object, then it must +explicitly reference each role. A derived concept is one whose population is derived from those of its supertype concepts using one or more expressions. For instance: ```yaml -- name: Employee - extends: [Person] - derived_by: [ "EXISTS ( Person.earns )" ] +ontology: + - concept: Employee + extends: [Person] + derived_by: [ "EXISTS ( Person.earns )" ] ``` declares that the population of Employee is derived from the population of Person by @@ -589,38 +626,187 @@ classifying each Person who earns some salary as a Employee. The requires list contains expressions that give additional semantics to a concept or relationship by declaring conditions that must hold over their populations. When applied to a concept, each -expression must reference the concept itself or one of its supertypes, as in: +expression must reference the concept, as in: ```yaml -- name: SocialSecurityNr - extends: [Integer] - requires: [ "0 < SocialSecurityNr", "SocialSecurityNr <= 999999999" ] +ontology: + - concept: SocialSecurityNr + extends: [Integer] + requires: [ "0 < SocialSecurityNr", "SocialSecurityNr <= 999999999" ] ``` When applied to a relationship, each expression must reference one or more roles of the relationship. For instance, in: ```yaml -- name: Item - extends: [Integer] - relationships: - offers_in: - roles: - - player: Store - verbalizations: [ "{Item} is offered for sale in {Store}", "{Store} offers sale of {Item}" ] - total_sales_in: - roles: - - player: Store - - player: Amount - verbalizations: [ "{Item} sold for cumulative {Amount} in {Store}" ] - requires: - - "Amount > 0.0" - - "Item.offers_in(Store)" +ontology: + - concept: Item + extends: [Integer] + relationships: + - name: offers_in + roles: + - player: Store + verbalizations: [ "{Item} is offered for sale in {Store}", "{Store} offers sale of {Item}" ] + - name: total_sales_in + roles: + - player: Store + - player: Amount + verbalizations: [ "{Item} sold for cumulative {Amount} in {Store}" ] + requires: + - "Amount > 0.0" + - "Item.offers_in(Store)" ``` the first expression requires any value that plays the `Amount` role to be positive while the second requires any item that has sales in some store to be offered in that store. +### Mappings + +Logical to conceptual schema mappings declare how field values map to conceptual objects and +relationship links among conceptual objects. The key idea is to map a SQL expression that +references one or more fields to some role that is played by a value-typed concept. + +#### Entity mappings + +Entity mappings declare how and under what conditions fields in the logical level map to objects of +some entity type in the population of the model by mapping to the value-typed roles of its identifying +relationsips: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `description` | string | No | Human-readable description | +| `identify_by` | list | Yes | Role bindings for each identifying relationship | + +For instance, this entity mapping: + +```yaml +ontology: + - concept: Person + relationships: + - name: nr + roles: + - player: SocialSecurityNr + multiplicity: OneToOne + verbalizes: [ "{Person} is identified by {SocialSecurityNr}" ] + identify_by: [ nr ] + entity_mappings: + - identify_by: + - role: Person.nr + expr: PERSONS.SSN + ... +``` + +uses one role binding that maps values from the `SSN` field of dataset `PERSONS` to objects +that play the `SocialSecurityNr` role in the `nr` relationship. Because each link in that +relationship associates a `SocialSecurityNr` object to some unique `Person` object, this +one role binding suffices to associate each distinct `SSN` value in the dataset to a distinct +`Person` object in the ontology. + +A more interesting example maps fields to instances of the `OrderLineItem` concept, whose identifier +involves two relationships: + +```yaml + - concept: OrderLineItem + relationships: + - name: nr + roles: [ concept: LineNr ] + multiplicity: ManyToOne + - name: order + roles: [ concept: CustOrder ] + multiplicity: ManyToOne + identify_by: + requires: [ "nr", "order" ] + entity_mappings: + - identify_by: + - role: OrderLineItem.nr + expr: LINEITEMS.L_LINENUMBER + - role: OrderLineItem.order + identify_by: + - role: CustOrder.nr + expr: LINEITEMS.L_ORDERKEY + +``` + +Notice how this entity mapping uses two role bindings -- one that maps the `L_LINENUMBER` +field to the `LineNr` role of the `nr` relationship, and one that uses a more complex +structure to map an object to the `CustOrder` role of the `order` relationship. Because +`LineNr` is a value-typed concept, we can directly map an expression involving fields to +that role just as in the previous example. But because `CustOrder` is an entity typed +concept, we cannot directly map field values to its objects but must instead use its +identifier, which here involves one relationship called `CustOrder.nr`. + +#### Relationship mappings + +Relationship mappings declare how and under what conditions fields refer to objects that +play roles in the links of relationships. Unlike entity mappings, which declare how to +construct objects from fields, relationship mappings declare how to look up objects using +fields and then form tuples that populate relationships in the model. + +Relationship mappings are organized hierarchically into trees with common tuple prefixes +to allow mapping to links of relationships of many different arities while minimizing +redundancy. Each node in the tree has this schema: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `concept` | string | No | Concept that plays the role mapped to by this node in the tree | +| `expr` | string | No | Maps fields to objects when the concept is a value type | +| `identify_by` | list | Yes | Maps fields to identifying relationships when the concept is an entity type | +| `relationship` | string | No | Relationship to populate using tuple mapped to by this level in the tree | +| `children` | list | List of relationship child mappings | + +```yaml +ontology: + - concept: Item + relationships: + - name: nr + roles: [ concept: SkuNr ] + multiplicity: OneToOne + verbalises: "{Item} is identified by {SkuNr}" + - name: active # A unary relationship + verbalizes: "{Item} is actively sold" + - name: active_in + roles: [ concept: Store ] + verbalises: "{Item} is actively sold in {Store}" + - name: returned_in + roles: [ concept: Store, concept: Amount ] + verbalizes: "{Item} returned in {Store} for {Amount}" + multiplicitly: ManyToOne + - name: sold_in + roles: [ concept: Store, concept: Amount ] + verbalizes: "{Item} sells in {Store} for {Amount}" + multiplicitly: ManyToOne + identify_by: [ nr ] + entity_mappings: + - identify_by: + role: Item.nr + expr: ITEMS.SKU + relationship_mappings: + - identify_by: + role: Item.nr + expr: METRICS.SKU + relationship: Item.active + children: + - concept: Store + identify_by: + role: Store.nr + expr: METRICS.STORE + relationship: Item.active_in + children: + - concept: Amount + expr: METRICS.SALES + relationship: Item.sold_in + - concept: Amount + expr: METRICS.RETURNS + relationship: Item.returned_in +``` + +This relationship mapping maps fields of the `METRICS` dataset to links of four different relationships. +The mapping has a tree structure in which paths from the root declare how to use fields to look up +objects that form tuples of various lengths and that at each level in the tree, the tuple of objects +formed at that level can be used to form links of the relationship that is named at that level. This +hierarchical structure simplifies the mapping by not declaring how to map the `SKU` field four times +and the `STORE` field three times. + --- ## Complete Example From 6f2d404bd77de8356361a39f763227fb2bc3a499 Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Mon, 11 May 2026 22:10:25 -0400 Subject: [PATCH 3/7] Checkpointing --- core-spec/spec.md | 245 +++++++++++++++++++++++++------------------- core-spec/spec.yaml | 110 +++++++++++++++++++- 2 files changed, 245 insertions(+), 110 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index 7d995683..6f1ba2c6 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -424,52 +424,65 @@ custom_extensions: ## Ontology -Enterprise data are commonly modeled at different levels. The logical level comprises datasets and -fields, while the conceptual level is an ontology that comprises concepts, relationships, and -business rules. This section describes how to declare an ontology and, when a model also -declares datasets and fields, logical to conceptual schema mappings. - -Ontologies and schema mappings are organized hierarchically. At the top level are the concepts, -under which relationships, business rules, and schema mappings are then defined. +Enterprise data are often modeled at both the logical level, in terms of datasets and fields, +and the conceptual level in the form of an ontology that comprises concepts, relationships, +and business rules. This section describes how to declare an ontology and schema mappings +from logical-level fields to concepts and relationships in the ontology. Ontologies are +organized hierarchically in top-level collection of tree structures, each root of which +is a concept, under which relationships and schema mappings are defined. ### Concepts Concepts represent the types of things that have meaning in a business setting, e.g., person, company, -or salary. Each concept is either an entity type or a value type. Each model implicitly includes a +or salary. Each concept is either an entity type or a value type. Ontologies implicitly include a value-type for each basic data type, like `Integer`, `Decimal`, and `String`, and an entity type -called `Any`. Every other concept in an ontology extends (is a subtype of) one of these concepts. +called `Any`. Every other concept ontology extends (is a subtype of) one of these concepts. -### Schema +Concepts conform to the following schema: | Field | Type | Required | Description | |-------|------|----------|-------------| -| `concept` | string | Yes | Unique name of a concept | +| `concept` | string | Yes | Unique name of this concept | | `description` | string | No | Human-readable description | | `relationships` | list | No | Relationships where this concept plays the first role | | `extends` | list | No | Names of this concept's supertypes | -| `derived_by` | list | No | Expressions for deriving this concept's population | -| `identify_by` | list | No | Names of relationships to use to referencing objects in expressions and queries | +| `derived_by` | list | No | Expressions that derive this concept's population | +| `identify_by` | list | No | Names of relationships that uniquely reference objects of this concept | | `requires` | list | No | Expressions that constrain this concept's population | -| `entity_mappings` | list | No | Mappings from fields to concept objects | -| `relationship_mappings` | list | No | Mappings from fields to relatioship links | +| `entity_mappings` | list | No | Mappings from field values to concept objects | +| `relationship_mappings` | list | No | Mappings from field values to relatioship links | ### Extends -Every concept that a user declares extends one or more concepts in the ontology. The new concept +Every user-declared concept extends one or more concepts in the ontology. The new concept is a sutype of each concept that it extends, and the extended concepts are its supertypes. Any concept that directly or indirectly extends a value type like `Integer` or `String` is a value type. Any concept that does not extend some value type is an entity type, and if a concept declares no extends -list, then it is assumed to extend the built-in entity type `Any`. - -So if `SocialSecurityNr` extends `Integer` and `Employee` extends `Person`, which declares -no extends list, then `SocialSecurityNr` is a value type and both `Person` and `Employee` -are entity types. +list, then it is assumed to extend the built-in entity type `Any`. If `SocialSecurityNr` extends `Integer` +and `Employee` extends `Person`, which declares no extends list, then `SocialSecurityNr` is a value type +and both `Person` and `Employee` are entity types. ### Relationships Relationships relate objects of one or more concepts and declare how to verbalize links among -those objects. For instance, the following relationships: +those objects. Relationships have set (as opposed to bag) semantics, and links do not contain +nulls. + +Each relationship that is declared under a concept conforms to the following schema: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Part of the identifier for this relationship | +| `description` | string | No | Human-readable description | +| `multiplicity` | enum | No | Multiplicity constraint | +| `roles` | list | No | List of additional roles in this relationship | +| `derived_by` | list | No | Expressions that derive links of this relationship | +| `requires` | list | No | Expressions that constrain this relationship's population | +| `verbalizes` | list | Yes | Patterns describing how to verbalize links | + +Each relationship is uniquely identified by a prepending its declared name with that of the containing +concept. For instance, in: ```yaml ontology: @@ -483,67 +496,86 @@ ontology: ... ``` -links `Person` and `Salary` objects and verbalizes each link as “Person earns Salary.” -Relationships have set (as opposed to bag) semantics, and links do not contain nulls. - -#### Schema - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `name` | string | Yes | Part of the identifier for this relationship | -| `description` | string | No | Human-readable description | -| `multiplicity` | enum | No | Multiplicity constraint | -| `roles` | list | No | List of additional roles in this relationship | -| `derived_by` | list | No | Expressions that derive links of this relationship | -| `requires` | list | No | Expressions that constrain this relationship's population | -| `verbalization` | list | Yes | Patterns describing how to verbalize links | - -Each relationship is uniquely identified by a prepending its declared name with that of the containing -concept. The relationship in the previous example is identified by the string `Person.earns`. -This convention naturally supports expressions that navigate over the links of relationships using -the “dot-join” operator in a manner that is familiar to object-oriented programming languages. +the relationship is identified by the string `Person.earns`. This convention naturally supports +expressions that navigate over the links of relationships using the “dot-join” operator in a +manner that is familiar to object-oriented programming languages. This relationship links +`Person` and `Salary` objects and verbalizes each link as “Person earns Salary.” #### Roles Objects play roles in the links of a relationship. If you think of a relationship as a narrow table, then its links are like rows and its roles are like columns. Each role is played by a concept that -constraints the type of objects that can play the role in the relationship's links. In the -`Person.earns` relationship, `Person` and `Salary` play the first and second roles respectively. +constrains the type of objects that can play that role in any link. In `Person.earns`, `Person` and +`Salary` play the first and second roles respectively. -By convention, the first role of any relationship will be played by the concept under which the -relationship is declared. Any additional roles are enumerated in the roles list using this schema: +By convention, the first role of any relationship is played by the concept under which the +relationship is declared. Any additional roles are enumerated in order in the roles list +using this schema: | Field | Type | Required | Description | |-------|------|----------|-------------| | `player` | string | Yes | Name of the concept that plays this role | | `name` | string | No | Optional role name | -A ternary relationship like `Person.purchased_on` declares two additional roles played by -`Vehicle` and `Date` respectively, while a unary relationship like -`Person.files_married_joint` will have an empty roles list. +For instance, in: + +```yaml +ontology: + - concept: Person + relationships: + - name: files_married_joint + verbalizes: [ "{Person} files married filing joint" ] + - name: purchased_on + roles: + - player: Vehicle + - player: Date + multiplicity: ManyToOne + verbalizes: [ "{Person} puchased {Vehicle} on {Date}" ] +``` + +the unary relationship `Person.files_married_joint` has an empty roles list, while the +ternary relationship `Person.purchased_on` declares two additional roles played by +`Vehicle` and `Date` respectively, -The role player often suffices to distinguish the role within its relationship. -However, the same concept can play multiple roles, such as in the ternary relationship -`Store.ships_to_in_days` that connects pairs of `Store` objects to the number of days -required to ship inventory from one store to another. When this happens, the user must -declare a distinguising name for any additional role whose player does not suffice to -distinguish it from other roles in the same relationship. +The role player often suffices to distinguish the role within its relationship, but when +the same concept plays more than one role, the user must declare a distinguising name for +any additional role whose player does not suffice to distinguish it from other roles in +the same relationship. For instance, in: -#### Multiplicities +```yaml +ontology: + - concept: Store + relationships: + - name: ships_to_in_days + roles: + - player: Store + name: destination + - player: NrDays + multiplicity: ManyToOne + verbalizes: [ "{Store} ships to {destination} in {NrDays}" ] +``` + +the role name `destination` distinguishes the second `Store`-playing role from the first in +this relationship. + +Expressions that are used to define derived_by rules and requires constraints will refer to +roles by name -- the name defaulting to the concept that plays the role unless an explicit +role name is provided. In any expression that involving links of the `Store.ships_to_in_days` +relationship can then use the variables `Store` and `destination` to refer to objecs that +play these two `Store`-playing roles without ambiguity. -In a relationship that comprises more than one role, objects that play the last role could -be functionally dermined by a tuple of objects that play the other roles. This knowledge is -declared using a `ManyToOne` multiplicity constraint. In the previous example, the constraint -declares that each person earns at most one salary. +#### Multiplicities -For relationships of ternary and higher arity, the multiplicity applies to the n-th role, meaning +If a relationship comprises more than one role, objects that play the last role could be functionally +dermined by a tuple of objects that play the other roles. This knowledge is declared using a `ManyToOne` +multiplicity constraint. In the examples above, the constraint declares that each person earns at most +one salary and that for each pair of stores, the former ships to the latter in at most one number of +days. For relationships of ternary and higher arity, the multiplicity applies to the n-th role, meaning the object that plays the n-th role is functionally determined by the tuple of objects that play -the first n-1 roles. A relationship like `Item.total_sales_in`, which records the total sales amount -of an item at a given store, is many-to-one because for any pair of `Item` and `Store` at most one -`Amount` represents the total sales for that `Item` and that `Store`. +the first n-1 roles. In the special case of a binary relationship, one might declare a `OneToOne` multiplicity, which -indicates the relationship is many-to-one in both directions. For instance, the `Person.ssn` +indicates the relationship is many-to-one in both directions. For instance, the `Person.nr` relationship is one-to-one because each person is assigned at most one social security number and each social security number is assigned to at most one person. @@ -552,8 +584,8 @@ any of the roles. ### Identifying relationships -Many conceptual models distinguish one or more relationships to use when referecing entity-type -objects in expressions and queries. The `Person.ssn` relationship can be used to reference a +Many conceptual models distinguish one or more relationships to use when referencing entity-type +objects in expressions and queries. The `Person.nr` relationship can be used to reference a person by their social security number; while the pair of relationships `License.acct` and `License.seat_nr` can be used to reference a license by its associated account and seat number. These identifier relationships are always binary, and their first role is always played by the @@ -561,8 +593,9 @@ concept the relationship is used to reference. ### Derivation expressions -Concepts and relationships may be derived using expressions. Think of a derived relationship -as a conceptual view whose links are derived from those of other relationships. For instance: +Concepts and relationships may be derived using expressions. Think of a derived concept or +relationship as a conceptual view whose objects or links are derived from those of other +concepts or relationships. For instance: ```yaml ontology: @@ -663,21 +696,19 @@ requires any item that has sales in some store to be offered in that store. ### Mappings Logical to conceptual schema mappings declare how field values map to conceptual objects and -relationship links among conceptual objects. The key idea is to map a SQL expression that -references one or more fields to some role that is played by a value-typed concept. +relationship links among conceptual objects. The key idea is to map a SQL expression that computes +a value from one or more fields to some role that is played by a value-typed concept. #### Entity mappings Entity mappings declare how and under what conditions fields in the logical level map to objects of -some entity type in the population of the model by mapping to the value-typed roles of its identifying -relationsips: +some entity type in the population of the model. Each mapping conforms to the following schema: | Field | Type | Required | Description | |-------|------|----------|-------------| -| `description` | string | No | Human-readable description | -| `identify_by` | list | Yes | Role bindings for each identifying relationship | +| `entity_map` | list | Yes | Role bindings for each identifying relationship | -For instance, this entity mapping: +For instance, the entity mapping in: ```yaml ontology: @@ -690,7 +721,7 @@ ontology: verbalizes: [ "{Person} is identified by {SocialSecurityNr}" ] identify_by: [ nr ] entity_mappings: - - identify_by: + - entity_map: - role: Person.nr expr: PERSONS.SSN ... @@ -699,10 +730,10 @@ ontology: uses one role binding that maps values from the `SSN` field of dataset `PERSONS` to objects that play the `SocialSecurityNr` role in the `nr` relationship. Because each link in that relationship associates a `SocialSecurityNr` object to some unique `Person` object, this -one role binding suffices to associate each distinct `SSN` value in the dataset to a distinct +role binding suffices to associate each distinct `SSN` value in the dataset to a distinct `Person` object in the ontology. -A more interesting example maps fields to instances of the `OrderLineItem` concept, whose identifier +A more interesting example maps fields to the `OrderLineItem` concept, whose identifier involves two relationships: ```yaml @@ -714,26 +745,25 @@ involves two relationships: - name: order roles: [ concept: CustOrder ] multiplicity: ManyToOne - identify_by: - requires: [ "nr", "order" ] + identify_by: [ "nr", "order" ] + requires: [ "OrderLineItem.nr", "OrderLineItem.order" ] entity_mappings: - - identify_by: + - entity_map: - role: OrderLineItem.nr expr: LINEITEMS.L_LINENUMBER - role: OrderLineItem.order - identify_by: + entity_map: - role: CustOrder.nr expr: LINEITEMS.L_ORDERKEY ``` -Notice how this entity mapping uses two role bindings -- one that maps the `L_LINENUMBER` -field to the `LineNr` role of the `nr` relationship, and one that uses a more complex -structure to map an object to the `CustOrder` role of the `order` relationship. Because -`LineNr` is a value-typed concept, we can directly map an expression involving fields to -that role just as in the previous example. But because `CustOrder` is an entity typed -concept, we cannot directly map field values to its objects but must instead use its -identifier, which here involves one relationship called `CustOrder.nr`. +This entity mapping uses two role bindings -- one that maps the `L_LINENUMBER` field to the `LineNr` +role of the `nr` relationship, and one that uses a more complex structure to map `CustOrder` objects +to the role that concept plays in the `order` relationship. Because `LineNr` is a value-typed concept, +we can directly map an expression involving fields to that role just as in the previous example. But +because `CustOrder` is an entity type, we cannot directly map field values to its objects but must +instead use its identifier, which here involves one relationship called `CustOrder.nr`. #### Relationship mappings @@ -749,10 +779,12 @@ redundancy. Each node in the tree has this schema: | Field | Type | Required | Description | |-------|------|----------|-------------| | `concept` | string | No | Concept that plays the role mapped to by this node in the tree | -| `expr` | string | No | Maps fields to objects when the concept is a value type | -| `identify_by` | list | Yes | Maps fields to identifying relationships when the concept is an entity type | -| `relationship` | string | No | Relationship to populate using tuple mapped to by this level in the tree | -| `children` | list | List of relationship child mappings | +| `expr` | string | No | Maps fields when the concept is a value type | +| `entity_map` | list | Yes | Maps fields when the concept is an entity type | +| `relationship` | string | No | Relationship whose links are mapped to by this level in the tree | +| `children` | list | No | List of relationship child mappings | + +For instance, the relationship mapping in: ```yaml ontology: @@ -763,35 +795,35 @@ ontology: multiplicity: OneToOne verbalises: "{Item} is identified by {SkuNr}" - name: active # A unary relationship - verbalizes: "{Item} is actively sold" + verbalizes: [ "{Item} is actively sold" ] - name: active_in roles: [ concept: Store ] - verbalises: "{Item} is actively sold in {Store}" + verbalises: [ "{Item} is actively sold in {Store}" ] - name: returned_in roles: [ concept: Store, concept: Amount ] - verbalizes: "{Item} returned in {Store} for {Amount}" + verbalizes: [ "{Item} returned in {Store} for {Amount}" ] multiplicitly: ManyToOne - name: sold_in roles: [ concept: Store, concept: Amount ] - verbalizes: "{Item} sells in {Store} for {Amount}" + verbalizes: [ "{Item} sells in {Store} for {Amount}" ] multiplicitly: ManyToOne identify_by: [ nr ] entity_mappings: - - identify_by: + - entity_map: role: Item.nr expr: ITEMS.SKU relationship_mappings: - - identify_by: + - entity_map: role: Item.nr expr: METRICS.SKU relationship: Item.active - children: + relationship_mappings: - concept: Store - identify_by: + entity_map: role: Store.nr expr: METRICS.STORE relationship: Item.active_in - children: + relationship_mappings: - concept: Amount expr: METRICS.SALES relationship: Item.sold_in @@ -800,12 +832,9 @@ ontology: relationship: Item.returned_in ``` -This relationship mapping maps fields of the `METRICS` dataset to links of four different relationships. -The mapping has a tree structure in which paths from the root declare how to use fields to look up -objects that form tuples of various lengths and that at each level in the tree, the tuple of objects -formed at that level can be used to form links of the relationship that is named at that level. This -hierarchical structure simplifies the mapping by not declaring how to map the `SKU` field four times -and the `STORE` field three times. +maps fields of the `METRICS` dataset to links of four different relationships. The hierarchical +structure simplifies the mapping by not declaring how to map the `SKU` field four times and the +`STORE` field three times. --- diff --git a/core-spec/spec.yaml b/core-spec/spec.yaml index d8b55450..6595d825 100644 --- a/core-spec/spec.yaml +++ b/core-spec/spec.yaml @@ -20,6 +20,9 @@ dialects: - "MAQL" # GoodData MAQL (Multi-Dimensional Analytical Query Language) +multiplicities: + - "ManyToOne" + - "OneToOne" # Supported vendors for custom extensions vendors: @@ -48,7 +51,10 @@ semantic_model: # Optional: Defines how logical datasets are connected # See Relationships section below for detailed structure - relationships: [] + join_paths: [] + + # Optional: Defines the ontology used to give meaning to the data + ontology: [] # Optional: # These metrics can span one or more logical datasets and use relationships @@ -118,7 +124,7 @@ datasets: # Relationship Schema # Defines how logical datasets or semantic models are connected # Represents foreign key relationships (many-to-one or one-to-one) -relationships: +join_paths: # Required: Unique identifier for the relationship - name: string @@ -217,3 +223,103 @@ metrics: custom_extensions: - vendor_name: string # Must be one of the values from 'vendors' enum above data: string + +--- +# Ontology Schema +ontology: + + # Required: Unique identifier for the concept + - concept: string + + # Optional: Human-readable description of the concept + description: string + + # Optional: Relationships in which this concept plays the first role + relationships: [] + + # Optional: Array of names of relationships that form the preferred identifier of this concept + identify_by: [] + + # Optional: Array of names of supertype concepts that this concept extends + extends: [] + + # Optional: Array of expressions that derive the population of this concept + derived_by: [] + + # Optional: Array of expressions that constrain the population of this concept + requires: [] + + # Optional: Array of entity mappings that map logical fields to concept objects + entity_mappings: [] + + # Optional: Array of relationship mappings that map logical fields to relationship links + relationship_mappings: [] + +relationships: + + # Required: unique name of the relationship in the context of the declaring concept + - name: string + + # Optional: Human-readable description + description: string + + # Optional: Multiplicity constraint + multiplicity: multiplicities + + # Optional: List of additional roles in this relationship + roles: [] + + # Optional: Array of expressions that derive links of this relationship + derived_by: [] + + # Optional: Array of expressions that constrain this relationship's population + requires: [] + + # Optional Array of string patterns that describe how to verbalize links of + # this relationship + verbalizes: [] + +roles: + + # Required: Name of the concept that plays this role + - player: string + + # Optional: Role name + name: string + +entity_mappings: + + - entity_map: [] + +entity_map: + + # Required: String naming the idenfier relationship whose role is mapped to using either the + # expr if the role is played by a value type or the nested entity map if the role is played + # by an entity type + - role: string + + # Optional: SQL expression whose value maps to the role + expr: string + + # Optional: Nested entity map that returns an entity object that maps to the role + entity_map: [] + +relationship_mappings: + + - relationship_map: [] + +relationship_map: + + # Optional: Name of the concept that plays this role in the relationship map + - concept: string + + # Optional: Maps fields to this role when the player is a value type + expr: string + + # Optional: Maps fields to this role when the player is an entity type + entity_map: [] + + # Optional: Name of relationship whose links are mapped to by this level in the tree + relationship: string + + relationship_mappings: [] \ No newline at end of file From 4f0e87967b2c0ffb83614d2d4c9e0280077c63d3 Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Tue, 12 May 2026 16:18:21 -0400 Subject: [PATCH 4/7] Checkpointing after improving description of mappings --- core-spec/spec.md | 171 ++++++++++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 66 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index 6f1ba2c6..2b82b61e 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -449,8 +449,8 @@ Concepts conform to the following schema: | `derived_by` | list | No | Expressions that derive this concept's population | | `identify_by` | list | No | Names of relationships that uniquely reference objects of this concept | | `requires` | list | No | Expressions that constrain this concept's population | -| `entity_mappings` | list | No | Mappings from field values to concept objects | -| `relationship_mappings` | list | No | Mappings from field values to relatioship links | +| `entities` | list | No | Mappings that determine this concept's objects (when an entity type) | +| `links` | list | No | Mappings from field values to relatioship links | ### Extends @@ -552,7 +552,7 @@ ontology: name: destination - player: NrDays multiplicity: ManyToOne - verbalizes: [ "{Store} ships to {destination} in {NrDays}" ] + verbalizes: [ "{Store} ships to {Store:destination} in {NrDays}" ] ``` the role name `destination` distinguishes the second `Store`-playing role from the first in @@ -695,20 +695,47 @@ requires any item that has sales in some store to be offered in that store. ### Mappings -Logical to conceptual schema mappings declare how field values map to conceptual objects and -relationship links among conceptual objects. The key idea is to map a SQL expression that computes -a value from one or more fields to some role that is played by a value-typed concept. +Logical to conceptual schema mappings declare how to map field values to conceptual objects +and links. The most basic kind of mapping is a value map, which has the following schema: -#### Entity mappings +| Field | Type | Required | Description | +|--------------|----------|-----|-------| +| `role` | string | Yes | Expression that indicates the role that the mapped objects will play | +| `value` | string | Yes | Expression that calculates the value to map to the role | + +These declare how to map a SQL expression that computes a value from one or more fields to a +role of some relationship, where the role must be played by a value type. -Entity mappings declare how and under what conditions fields in the logical level map to objects of -some entity type in the population of the model. Each mapping conforms to the following schema: +Entity-type objects (entities) are opaque and must be referenced by their identifying +relationships. These relationships are always binary, with one role played by the entity +type and the other played by some other concept. In the common case where an entity type +is identified by a single relationship whose other role is played by a value type, a value +map suffices to declare the existence of entities of that type and also to look them up +when mapping to links of a relationship. And if an entity type is identified by a pair +of such relationships, a pair of value maps suffice for the same purpose. + +An entity map is an array of structures, each of which is either a value map or a nested entity +map that looks up an object to map to some role of an identifier relationship. These structures +have the following schema: | Field | Type | Required | Description | -|-------|------|----------|-------------| -| `entity_map` | list | Yes | Role bindings for each identifying relationship | +|--------------|----------|-----|-------| +| `role` | string | Yes | Expression that indicates the role the mapped objects will play | +| `value` | string | if no `entity` | Expression that maps to a value (when role played by a value type) | +| `entity` | list | if no `value` | Entity map that maps to an entity (when role played by an entity type) | + +where each must provide either a `value` expression (in which case the structure reduces to a value map) +or a nested `entity` map but not both. + +Building on these structures, we can declare precisely how fields determine the entities of a concept +and the links of every relationship in which the concept plays the first role. + +#### Entities (entity maps that determine the existence of objects of some entity type) + +When a concept is an entity type, it can declare an `entities` array, each element of which +is an entity map. -For instance, the entity mapping in: +For instance, in: ```yaml ontology: @@ -720,17 +747,18 @@ ontology: multiplicity: OneToOne verbalizes: [ "{Person} is identified by {SocialSecurityNr}" ] identify_by: [ nr ] - entity_mappings: - - entity_map: + + entities: + - entity: - role: Person.nr - expr: PERSONS.SSN + value: PERSONS.SSN ... ``` -uses one role binding that maps values from the `SSN` field of dataset `PERSONS` to objects -that play the `SocialSecurityNr` role in the `nr` relationship. Because each link in that +uses a single value map to declare how values from the `SSN` field of dataset `PERSONS` +map to `SocialSecurityNr` objects in the `nr` relationship. Because each link in that relationship associates a `SocialSecurityNr` object to some unique `Person` object, this -role binding suffices to associate each distinct `SSN` value in the dataset to a distinct +value map suffices to associate each distinct `SSN` value in the dataset to a distinct `Person` object in the ontology. A more interesting example maps fields to the `OrderLineItem` concept, whose identifier @@ -740,51 +768,57 @@ involves two relationships: - concept: OrderLineItem relationships: - name: nr - roles: [ concept: LineNr ] + roles: [ player: LineNr ] multiplicity: ManyToOne - name: order - roles: [ concept: CustOrder ] + roles: [ player: CustOrder ] multiplicity: ManyToOne identify_by: [ "nr", "order" ] requires: [ "OrderLineItem.nr", "OrderLineItem.order" ] - entity_mappings: - - entity_map: + + entities: + - entity: - role: OrderLineItem.nr - expr: LINEITEMS.L_LINENUMBER + value: LINEITEMS.L_LINENUMBER - role: OrderLineItem.order - entity_map: + entity: - role: CustOrder.nr - expr: LINEITEMS.L_ORDERKEY + value: LINEITEMS.L_ORDERKEY ``` -This entity mapping uses two role bindings -- one that maps the `L_LINENUMBER` field to the `LineNr` -role of the `nr` relationship, and one that uses a more complex structure to map `CustOrder` objects -to the role that concept plays in the `order` relationship. Because `LineNr` is a value-typed concept, -we can directly map an expression involving fields to that role just as in the previous example. But -because `CustOrder` is an entity type, we cannot directly map field values to its objects but must -instead use its identifier, which here involves one relationship called `CustOrder.nr`. +This mapping contains a single entity map with two components -- a value map that maps the +`L_LINENUMBER` field to the `LineNr` role of the `nr` relationship, and a nested entity map +that maps `CustOrder` objects to the role that concept plays in the `order` relationship. -#### Relationship mappings +#### Links (mappings that determine the existence of relationship links) -Relationship mappings declare how and under what conditions fields refer to objects that -play roles in the links of relationships. Unlike entity mappings, which declare how to -construct objects from fields, relationship mappings declare how to look up objects using -fields and then form tuples that populate relationships in the model. +Each of an ontology's relationships is grouped under the concept that plays its first role. +The concept's `links` array declares schema mappings from field values to links of these +relationships. Each element in the array is a tree structure that concisely declares how +to map to tuples that form the links of one or more of these relationships. The path from +the root of a tree to each node describes how to map to tuples of objects that form the +links of the relationship that is named by the node. These structures leverage the hierarchical +nature of YAML to prevent the duplication in the typical case when the fields of a single +dataset map to many relationships. -Relationship mappings are organized hierarchically into trees with common tuple prefixes -to allow mapping to links of relationships of many different arities while minimizing -redundancy. Each node in the tree has this schema: +Each tree node has the following schema: | Field | Type | Required | Description | |-------|------|----------|-------------| -| `concept` | string | No | Concept that plays the role mapped to by this node in the tree | -| `expr` | string | No | Maps fields when the concept is a value type | -| `entity_map` | list | Yes | Maps fields when the concept is an entity type | +| `concept` | string | when level > 1 | Concept whose objects are looked up by this node | +| `value` | string | if no `entity` | Expression that computes a value (when concept is a value type) | +| `entity` | list | if no `value` | Entity map that looks up an object (when concept is an entity type) | | `relationship` | string | No | Relationship whose links are mapped to by this level in the tree | -| `children` | list | No | List of relationship child mappings | +| `children` | list | No | List of child nodes in the tree | + -For instance, the relationship mapping in: +Each node must provide either a `value` or an `entity` by which to look up objects but never both. +The level of each node coincides with the arity of the associated relationship. So a top-level +(root) node could map to a unary relationship, a node at level 2 could map to a binary +relationship, and so forth. + +For instance, the relationship mapping declared here: ```yaml ontology: @@ -799,42 +833,47 @@ ontology: - name: active_in roles: [ concept: Store ] verbalises: [ "{Item} is actively sold in {Store}" ] - - name: returned_in + - name: returned_in_for roles: [ concept: Store, concept: Amount ] verbalizes: [ "{Item} returned in {Store} for {Amount}" ] multiplicitly: ManyToOne - - name: sold_in + - name: sold_in_for roles: [ concept: Store, concept: Amount ] verbalizes: [ "{Item} sells in {Store} for {Amount}" ] multiplicitly: ManyToOne identify_by: [ nr ] - entity_mappings: - - entity_map: - role: Item.nr - expr: ITEMS.SKU - relationship_mappings: - - entity_map: - role: Item.nr - expr: METRICS.SKU + + entities: + - entity: + - role: Item.nr + value_map: ITEMS.SKU + + links: + - entity: + - role: Item.nr + value_map: METRICS.SKU relationship: Item.active - relationship_mappings: + children: - concept: Store - entity_map: - role: Store.nr - expr: METRICS.STORE + entity: + - role: Store.nr + value: METRICS.STORE relationship: Item.active_in - relationship_mappings: + children: - concept: Amount - expr: METRICS.SALES - relationship: Item.sold_in + value: METRICS.SALES + relationship: Item.sold_in_for - concept: Amount - expr: METRICS.RETURNS - relationship: Item.returned_in + value: METRICS.RETURNS + relationship: Item.returned_in_for ``` -maps fields of the `METRICS` dataset to links of four different relationships. The hierarchical -structure simplifies the mapping by not declaring how to map the `SKU` field four times and the -`STORE` field three times. +describes a tree with one root node, one node at level 2, and 2 nodes at level 3. Each node +maps fields of the `METRICS` dataset to links of four different relationships, and notice +how the mapping to `Item` objects is declared once even though `Item` plays a role in all +four of the relationships and that the mapping to `Store` objects is declared once even +though `Store` plays a role in three of the relationships. + --- From 8f022442af4a21872c4c80d708a9980ea71bfabc Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Tue, 12 May 2026 16:56:10 -0400 Subject: [PATCH 5/7] Ready for review --- core-spec/spec.md | 99 +++++++++++++++++++++------------------------ core-spec/spec.yaml | 43 +++++++++++--------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index 2b82b61e..c37b0fd6 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -1,6 +1,6 @@ # OSI - Core Metadata Specification -**Version:** 0.1.1 +**Version:** 0.1.2 ## Goals @@ -696,27 +696,24 @@ requires any item that has sales in some store to be offered in that store. ### Mappings Logical to conceptual schema mappings declare how to map field values to conceptual objects -and links. The most basic kind of mapping is a value map, which has the following schema: +and links. The most basic kind of mapping is a value map: | Field | Type | Required | Description | |--------------|----------|-----|-------| | `role` | string | Yes | Expression that indicates the role that the mapped objects will play | | `value` | string | Yes | Expression that calculates the value to map to the role | -These declare how to map a SQL expression that computes a value from one or more fields to a +which declares how to map a SQL expression that computes a value from one or more fields to a role of some relationship, where the role must be played by a value type. -Entity-type objects (entities) are opaque and must be referenced by their identifying -relationships. These relationships are always binary, with one role played by the entity -type and the other played by some other concept. In the common case where an entity type -is identified by a single relationship whose other role is played by a value type, a value -map suffices to declare the existence of entities of that type and also to look them up -when mapping to links of a relationship. And if an entity type is identified by a pair -of such relationships, a pair of value maps suffice for the same purpose. +Entity-type objects (entities) are opaque and must be mapped to via their identifying +relationships. These relationships are always binary, with the first role played by +the entity type itself. In the common case where an entity type is identified by a +single relationship whose second role is played by a value type, a value map suffices +to declare the existence of entities of that type or to look them up when mapping to +links of a relationship. -An entity map is an array of structures, each of which is either a value map or a nested entity -map that looks up an object to map to some role of an identifier relationship. These structures -have the following schema: +An entity map is an array of identifier maps: | Field | Type | Required | Description | |--------------|----------|-----|-------| @@ -724,11 +721,11 @@ have the following schema: | `value` | string | if no `entity` | Expression that maps to a value (when role played by a value type) | | `entity` | list | if no `value` | Entity map that maps to an entity (when role played by an entity type) | -where each must provide either a `value` expression (in which case the structure reduces to a value map) -or a nested `entity` map but not both. +each of which is either a value map or a nested entity map that looks up an object to map to some +role of an identifier relationship. -Building on these structures, we can declare precisely how fields determine the entities of a concept -and the links of every relationship in which the concept plays the first role. +Building on value and entity maps, we can declare precisely how fields determine the entities of +a concept and the links of every relationship in which that concept plays the first role. #### Entities (entity maps that determine the existence of objects of some entity type) @@ -755,14 +752,14 @@ ontology: ... ``` -uses a single value map to declare how values from the `SSN` field of dataset `PERSONS` -map to `SocialSecurityNr` objects in the `nr` relationship. Because each link in that -relationship associates a `SocialSecurityNr` object to some unique `Person` object, this -value map suffices to associate each distinct `SSN` value in the dataset to a distinct -`Person` object in the ontology. +uses a value map to declare how values from the `SSN` field of dataset `PERSONS` are used +to form `SocialSecurityNr` values that map to the second role of the `nr` relationship. +Because each link in that relationship associates a `SocialSecurityNr` value to some +unique `Person` entity, this value map suffices to associate each distinct `SSN` value +in the dataset to a distinct `Person` object in the ontology. -A more interesting example maps fields to the `OrderLineItem` concept, whose identifier -involves two relationships: +A more interesting example maps field values to `OrderLineItem` entities, which are +identified using two relationships: ```yaml - concept: OrderLineItem @@ -787,20 +784,20 @@ involves two relationships: ``` -This mapping contains a single entity map with two components -- a value map that maps the -`L_LINENUMBER` field to the `LineNr` role of the `nr` relationship, and a nested entity map -that maps `CustOrder` objects to the role that concept plays in the `order` relationship. +This mapping contains a single entity map with two role mappings -- a value map that maps the +`L_LINENUMBER` field to the second role of the `nr` relationship, and a nested entity map that +maps `CustOrder` objects to the second role in the `order` relationship. #### Links (mappings that determine the existence of relationship links) -Each of an ontology's relationships is grouped under the concept that plays its first role. -The concept's `links` array declares schema mappings from field values to links of these -relationships. Each element in the array is a tree structure that concisely declares how -to map to tuples that form the links of one or more of these relationships. The path from -the root of a tree to each node describes how to map to tuples of objects that form the -links of the relationship that is named by the node. These structures leverage the hierarchical -nature of YAML to prevent the duplication in the typical case when the fields of a single -dataset map to many relationships. +Each relationships is grouped under the concept that plays its first role. A concept's +`links` array declares schema mappings from field values to links of these relationships. +Each array element is a tree that concisely declares how to map fields to the links of +one or more of these relationships. More precisely, the path from the root of a tree +to a node describes how to map to tuples of objects that form the links of the relationship +that is named by the node. These structures leverage the hierarchical nature of YAML to +avoid duplication in the typical case when the fields of a single dataset map to many +relationships. Each tree node has the following schema: @@ -808,17 +805,16 @@ Each tree node has the following schema: |-------|------|----------|-------------| | `concept` | string | when level > 1 | Concept whose objects are looked up by this node | | `value` | string | if no `entity` | Expression that computes a value (when concept is a value type) | -| `entity` | list | if no `value` | Entity map that looks up an object (when concept is an entity type) | -| `relationship` | string | No | Relationship whose links are mapped to by this level in the tree | +| `entity` | list | if no `value` | Entity map that looks up an entity (when concept is an entity type) | +| `relationship` | string | No | Relationship whose links are mapped to by the path to this node | | `children` | list | No | List of child nodes in the tree | - Each node must provide either a `value` or an `entity` by which to look up objects but never both. -The level of each node coincides with the arity of the associated relationship. So a top-level -(root) node could map to a unary relationship, a node at level 2 could map to a binary -relationship, and so forth. +The level of each node coincides with the arity of the associated relationship. So a root node +could map to a unary relationship, a node at level 2 could map to a binary relationship, and so +forth. -For instance, the relationship mapping declared here: +For instance, the `links` array declared here: ```yaml ontology: @@ -868,12 +864,11 @@ ontology: relationship: Item.returned_in_for ``` -describes a tree with one root node, one node at level 2, and 2 nodes at level 3. Each node -maps fields of the `METRICS` dataset to links of four different relationships, and notice -how the mapping to `Item` objects is declared once even though `Item` plays a role in all -four of the relationships and that the mapping to `Store` objects is declared once even -though `Store` plays a role in three of the relationships. - +describes a tree with one root node, one node at level 2, and two nodes at level 3. +Each node maps fields of the `METRICS` dataset to links of four different relationships, +and notice how the mapping to `Item` objects is declared once even though `Item` plays a +role in all four of the relationships and that the mapping to `Store` objects is declared +once even though `Store` plays a role in three of the relationships. --- @@ -1016,11 +1011,11 @@ ai_context: ## Version History -- **0.1.2** (2026-05-08): Support for both logical and conceptual modeling layers - - Core conceptual model structure +- **0.1.2** (2026-05-12): Support for both logical and conceptual modeling layers (ontologies) + - Core ontology structure - Support for concepts, relationships, and logical -> conceptual schema mappings - - Renamed relationships in the logical (semantic) layer to join paths to avoid - conflict with relationships in the conceptual layer + - Renamed relationships in the logical layer to join paths to avoid conflict with + relationships in the conceptual layer - **0.1.1** (2025-12-11): Initial release - Core semantic model structure diff --git a/core-spec/spec.yaml b/core-spec/spec.yaml index 6595d825..021ef412 100644 --- a/core-spec/spec.yaml +++ b/core-spec/spec.yaml @@ -1,5 +1,5 @@ # OSI - Core Metadata Spec (YAML Schema) -version: 0.1.1 +version: 0.1.2 # # Goals: # - Standardization: Establish uniform language and structure for semantic model definitions @@ -249,11 +249,11 @@ ontology: # Optional: Array of expressions that constrain the population of this concept requires: [] - # Optional: Array of entity mappings that map logical fields to concept objects - entity_mappings: [] + # Optional: Array of entity maps that map logical fields to entities (when concept is an entity type) + entities: [] - # Optional: Array of relationship mappings that map logical fields to relationship links - relationship_mappings: [] + # Optional: Array of link trees that map logical fields to relationships + links: [] relationships: @@ -287,11 +287,12 @@ roles: # Optional: Role name name: string -entity_mappings: +entities: - - entity_map: [] + # Array of entity maps + - entity: [] -entity_map: +entity: # Required: String naming the idenfier relationship whose role is mapped to using either the # expr if the role is played by a value type or the nested entity map if the role is played @@ -299,27 +300,31 @@ entity_map: - role: string # Optional: SQL expression whose value maps to the role - expr: string + value: string # Optional: Nested entity map that returns an entity object that maps to the role - entity_map: [] + entity: [] -relationship_mappings: +links: - - relationship_map: [] + - links_tree_node: [] -relationship_map: +links_tree_node: - # Optional: Name of the concept that plays this role in the relationship map + # Name of the concept whose objects are looked up by this node. + # Optional when the node is the root of a tree. - concept: string - # Optional: Maps fields to this role when the player is a value type - expr: string + # Optional: Maps fields to a value (when concept is a value type) + # Must be present when entity is absent + value: string - # Optional: Maps fields to this role when the player is an entity type - entity_map: [] + # Optional: Maps fields to an entity (when concept is an entity type) + # Must be present when value is absent + entity: [] # Optional: Name of relationship whose links are mapped to by this level in the tree relationship: string - relationship_mappings: [] \ No newline at end of file + # Optional: Array of links_tree_node children of this node + children: [] \ No newline at end of file From d3161399f75579a71459155a8d89888539f9e6ad Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Tue, 12 May 2026 17:02:35 -0400 Subject: [PATCH 6/7] Small fix --- core-spec/spec.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index c37b0fd6..f17ea6bb 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -791,13 +791,12 @@ maps `CustOrder` objects to the second role in the `order` relationship. #### Links (mappings that determine the existence of relationship links) Each relationships is grouped under the concept that plays its first role. A concept's -`links` array declares schema mappings from field values to links of these relationships. -Each array element is a tree that concisely declares how to map fields to the links of -one or more of these relationships. More precisely, the path from the root of a tree -to a node describes how to map to tuples of objects that form the links of the relationship -that is named by the node. These structures leverage the hierarchical nature of YAML to -avoid duplication in the typical case when the fields of a single dataset map to many -relationships. +`links` array maps field schema to relationships. Each array element is a tree that +concisely declares how field values map to the links of one or more of these relationships. +More precisely, the path from the root of a tree to a node describes how to map to tuples +of objects that form the links of the relationship that is named by the node. These structures +leverage the hierarchical nature of YAML to avoid duplication in the typical case when the +fields of a single dataset map to many relationships. Each tree node has the following schema: @@ -842,12 +841,12 @@ ontology: entities: - entity: - role: Item.nr - value_map: ITEMS.SKU + value: ITEMS.SKU links: - entity: - role: Item.nr - value_map: METRICS.SKU + value: METRICS.SKU relationship: Item.active children: - concept: Store From 5ce7e1d09841d6a13a6995d8d76bbd9a886f9222 Mon Sep 17 00:00:00 2001 From: Kurt Stirewalt Date: Tue, 12 May 2026 18:59:39 -0400 Subject: [PATCH 7/7] Fixed some typos --- core-spec/spec.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core-spec/spec.md b/core-spec/spec.md index f17ea6bb..aba6572a 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -45,7 +45,7 @@ The allowable multiplicities of relationships defined in the [Ontology](#ontolog | Multiplicity | Description | |---------|-------------| | `ManyToOne` | The last role of a relationship is uniquely determined by the other roles | -| `OneToOne` | The relationship is ManyToMany in both directions (only for binary relationships) | +| `OneToOne` | The relationship is ManyToOne in both directions (only for binary relationships) | ### Vendors @@ -428,7 +428,7 @@ Enterprise data are often modeled at both the logical level, in terms of dataset and the conceptual level in the form of an ontology that comprises concepts, relationships, and business rules. This section describes how to declare an ontology and schema mappings from logical-level fields to concepts and relationships in the ontology. Ontologies are -organized hierarchically in top-level collection of tree structures, each root of which +organized hierarchically in a top-level collection of tree structures, each root of which is a concept, under which relationships and schema mappings are defined. ### Concepts @@ -450,7 +450,7 @@ Concepts conform to the following schema: | `identify_by` | list | No | Names of relationships that uniquely reference objects of this concept | | `requires` | list | No | Expressions that constrain this concept's population | | `entities` | list | No | Mappings that determine this concept's objects (when an entity type) | -| `links` | list | No | Mappings from field values to relatioship links | +| `links` | list | No | Mappings from field values to relationship links | ### Extends