Semantic DomainsTemplate Authoring
Validation & Examples
Complete domain example and validation error reference
Complete Example: E-Commerce Domain
Here's a complete domain template combining all concepts: 3 datasets, a relationship, dataset metrics that build on each other, a composed metric across datasets, a composed dimension with a grain mapping, and context for agents:
schemaVersion: "2.1"
aiContext:
instructions: "Revenue is net of discounts. Use order_date for time questions unless the question is about returns."
synonyms: [sales, ecommerce]
datasets:
- name: orders
label: "Orders"
analyticsRole: fact
source:
kind: table
connectionId: "conn_1"
connectionType: PostgreSQL
schema: public
table: orders
primaryKey: [order_id]
uniqueKeys:
- [order_number]
primaryDateField: order_date
fields:
identifiers:
- name: order_id
dataType: number
sourceField: order_id
type: primary
- name: customer_id
dataType: number
sourceField: customer_id
type: foreign
dimensions:
- name: order_date
label: "Order Date"
dataType: date
sourceField: order_date
granularity: day
- name: order_number
label: "Order Number"
dataType: string
sourceField: order_number
- name: status
label: "Status"
dataType: string
sourceField: status
- name: order_quarter
label: "Order Quarter"
dataType: date
expression: "DATE_TRUNC('quarter', order_date)"
facts:
- name: amount
label: "Amount"
dataType: number
nativeDataType: "numeric(12,2)"
sourceField: amount
defaultAggregate: SUM
- name: cost
label: "Cost"
dataType: number
sourceField: cost
defaultAggregate: SUM
- name: customers
label: "Customers"
analyticsRole: dimension
source:
kind: table
connectionId: "conn_1"
connectionType: PostgreSQL
schema: public
table: customers
primaryKey: [id]
fields:
identifiers:
- name: id
dataType: number
sourceField: id
type: primary
dimensions:
- name: segment
label: "Customer Segment"
dataType: string
sourceField: segment
aiContext:
synonyms: [tier, customer type]
- name: region
label: "Region"
dataType: string
sourceField: region
- name: returns
label: "Returns"
analyticsRole: fact
source:
kind: table
connectionId: "conn_1"
connectionType: PostgreSQL
schema: public
table: returns
fields:
dimensions:
- name: return_date
label: "Return Date"
dataType: date
sourceField: return_date
facts:
- name: amount
label: "Return Amount"
dataType: number
sourceField: amount
defaultAggregate: SUM
relationships:
- name: orders_to_customers
from: { dataset: orders, fields: [customer_id] }
to: { dataset: customers, fields: [id] }
cardinality: many_to_one
defaultJoinType: LEFT
isAutoJoin: true
discoveredBy: user
isActive: true
metrics:
- name: revenue
dataset: orders
label: "Revenue"
expression: "SUM(amount)"
dataType: number
aiContext: "Gross order amount after discounts, before returns."
- name: total_cost
dataset: orders
label: "Total Cost"
expression: "SUM(cost)"
dataType: number
- name: profit
dataset: orders
label: "Profit"
expression: "revenue - total_cost"
dataType: number
- name: profit_margin
dataset: orders
label: "Profit Margin %"
expression: "profit / NULLIF(revenue, 0) * 100"
dataType: number
format:
type: percent
decimals: 1
- name: net_revenue
label: "Net Revenue"
description: "Revenue minus returns"
expression: "{sales} - COALESCE({returned}, 0)"
inputs:
sales: { metric: revenue, dataset: orders }
returned: { dataset: returns, field: amount, aggregate: SUM }
composedDimensions:
- name: order_month
label: "Order Month"
expression: "DATE_TRUNC('month', {order_date})"
inputs:
order_date: { dataset: orders, field: order_date }
dataType: date
grainMappings:
- sourceDimension: order_month
targetDataset: returns
targetColumn: return_dateIn this template:
amountandcostare facts: row-level quantities with a default aggregate.revenue,total_cost,profit, andprofit_marginare dataset metrics onorders. Each one aggregatesordersrows, and later ones reuse earlier ones by name.net_revenueis a composed metric. It combines therevenuemetric oforderswithreturns.amount, so each dataset is aggregated on its own before the two are combined.uniqueKeysdeclaresorder_numberas a second key oforders.
Validation Errors
When you save a domain template, Semaphor validates it. Errors stop the save; warnings are returned with a successful save. Each finding has a code, a message, and a severity.
Errors
| Code | Meaning | Fix |
|---|---|---|
SCHEMA | The template doesn't match the schema (a missing required property, an unknown enum value, a fact or dimension without sourceField or expression). The message names the path | Correct the property at that path |
mixed_schema_versions | The request mixes 2.0 keys (like calculatedMetrics) with 2.1 keys (like metrics) | Send one version |
duplicate_name | A name is already used in the dataset (across identifiers, facts, dimensions, and its metrics), or among composed metrics | Choose another name |
aggregate_in_row_field | A fact or dimension expression aggregates rows | Move it to metrics as a dataset metric |
row_expression_in_dataset_metric | A dataset metric's expression works row by row | Add it as a fact (to aggregate) or a dimension (to group by) |
dataset_metric_references_other_dataset | A dataset metric uses fields from another dataset | Make it a composed metric whose inputs name each dataset |
row_composed_metric_without_default_aggregate | A composed metric works row by row and has no defaultAggregate | Set defaultAggregate |
metric_input_not_found | A metric input names a metric that doesn't exist | Check the metric name and its dataset |
column_input_not_found | A metric input names a field that doesn't exist | Check the dataset and field |
unknown_dataset, unknown_field | A dataset metric or relationship refers to a dataset or field that doesn't exist | Check the spelling, or add the dataset or field |
relationship_ends_unequal | A relationship's from and to list a different number of fields | List matching fields on both ends |
ai_context_too_long | Context is over a length limit: instructions over 2,000 characters, a synonym over 100, or an example question over 300 | Shorten it |
ai_context_too_many | Context has more than 50 synonyms or more than 10 example questions | Keep the most useful ones |
invalid_data_type | A field's dataType isn't a known logical type | Use string, number, boolean, date, datetime, json, geo, or unknown |
unknown_source_kind | A dataset's source.kind isn't table or sql | Use table or sql |
| E001 | Circular dependency (metric A references B which references A) | Break the cycle by removing one of the references |
| E003 | A composed dimension input references an unknown field | Verify the dataset has that field |
| E004 | An input references an unknown dataset | Ensure the dataset is defined in datasets |
| E005 | Token {name} in an expression is not in inputs | Add the missing token name to the inputs map |
| E007 | A composed dimension references a metric | Dimensions can only reference fields or other dimensions |
| E009 | Invalid expression syntax | Remove braces inside string literals |
| E010 | An input needs an aggregate but none is specified | Add aggregate to the input or wrap the token in an aggregate function |
| E011 | An input references an unknown composed dimension | Check that the dimension exists in composedDimensions |
| E012 | Unknown name in a fact or dataset metric expression | Check the spelling; the name must be a field or metric of the same dataset |
| E013 | Invalid fact or dataset metric expression, or fact filter | Remove AS aliases, check filter configuration, and verify SQL syntax |
| E014 | Unknown field in a fact filter | Check that the filter field exists in the dataset |
| E015 | Unsupported filter path | Use same-dataset fields, or fields reachable through many-to-one joins |
| E016 | Unknown field in a dimension expression | Check the spelling; the field must exist in the same dataset |
| E017 | Invalid dimension expression | Remove AS aliases and fix the SQL syntax |
| E018 | primaryDateField names a dimension that doesn't exist | Point it at an existing date dimension |
| E019 | primaryDateField names a dimension that isn't a date or datetime | Choose a date or datetime dimension |
| E022 | A metric input wraps an aggregate metric in another aggregate | Reference the metric without an extra aggregate |
Warnings
| Code | Meaning | Action |
|---|---|---|
relationship_keys_unmatched | A relationship's to fields (or, for one_to_one, either end) don't match a key of that dataset, so joins may repeat rows | Join on the key, or declare the fields in primaryKey or uniqueKeys. See Join Keys |
duplicate_name | A name was already duplicated before this save | Rename one of them when you can; new duplicates are errors |
native_type_needs_sync | A field now reads a different column or source, so its stored database type was cleared | Run Sync data types on the dataset |
| E006 | An input is defined but not used in the expression | Remove the unused input or add it to the expression |
At query time, Semaphor may also return W001 (a dataset has no key) when it plans a join. Set primaryKey on the dataset to resolve it.
Next Steps
- Agent Guide: Build templates programmatically with validation rules
- User Guide: How end users interact with your domain in dashboards