Semaphor
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_date

In this template:

  • amount and cost are facts: row-level quantities with a default aggregate.
  • revenue, total_cost, profit, and profit_margin are dataset metrics on orders. Each one aggregates orders rows, and later ones reuse earlier ones by name.
  • net_revenue is a composed metric. It combines the revenue metric of orders with returns.amount, so each dataset is aggregated on its own before the two are combined.
  • uniqueKeys declares order_number as a second key of orders.

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

CodeMeaningFix
SCHEMAThe 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 pathCorrect the property at that path
mixed_schema_versionsThe request mixes 2.0 keys (like calculatedMetrics) with 2.1 keys (like metrics)Send one version
duplicate_nameA name is already used in the dataset (across identifiers, facts, dimensions, and its metrics), or among composed metricsChoose another name
aggregate_in_row_fieldA fact or dimension expression aggregates rowsMove it to metrics as a dataset metric
row_expression_in_dataset_metricA dataset metric's expression works row by rowAdd it as a fact (to aggregate) or a dimension (to group by)
dataset_metric_references_other_datasetA dataset metric uses fields from another datasetMake it a composed metric whose inputs name each dataset
row_composed_metric_without_default_aggregateA composed metric works row by row and has no defaultAggregateSet defaultAggregate
metric_input_not_foundA metric input names a metric that doesn't existCheck the metric name and its dataset
column_input_not_foundA metric input names a field that doesn't existCheck the dataset and field
unknown_dataset, unknown_fieldA dataset metric or relationship refers to a dataset or field that doesn't existCheck the spelling, or add the dataset or field
relationship_ends_unequalA relationship's from and to list a different number of fieldsList matching fields on both ends
ai_context_too_longContext is over a length limit: instructions over 2,000 characters, a synonym over 100, or an example question over 300Shorten it
ai_context_too_manyContext has more than 50 synonyms or more than 10 example questionsKeep the most useful ones
invalid_data_typeA field's dataType isn't a known logical typeUse string, number, boolean, date, datetime, json, geo, or unknown
unknown_source_kindA dataset's source.kind isn't table or sqlUse table or sql
E001Circular dependency (metric A references B which references A)Break the cycle by removing one of the references
E003A composed dimension input references an unknown fieldVerify the dataset has that field
E004An input references an unknown datasetEnsure the dataset is defined in datasets
E005Token {name} in an expression is not in inputsAdd the missing token name to the inputs map
E007A composed dimension references a metricDimensions can only reference fields or other dimensions
E009Invalid expression syntaxRemove braces inside string literals
E010An input needs an aggregate but none is specifiedAdd aggregate to the input or wrap the token in an aggregate function
E011An input references an unknown composed dimensionCheck that the dimension exists in composedDimensions
E012Unknown name in a fact or dataset metric expressionCheck the spelling; the name must be a field or metric of the same dataset
E013Invalid fact or dataset metric expression, or fact filterRemove AS aliases, check filter configuration, and verify SQL syntax
E014Unknown field in a fact filterCheck that the filter field exists in the dataset
E015Unsupported filter pathUse same-dataset fields, or fields reachable through many-to-one joins
E016Unknown field in a dimension expressionCheck the spelling; the field must exist in the same dataset
E017Invalid dimension expressionRemove AS aliases and fix the SQL syntax
E018primaryDateField names a dimension that doesn't existPoint it at an existing date dimension
E019primaryDateField names a dimension that isn't a date or datetimeChoose a date or datetime dimension
E022A metric input wraps an aggregate metric in another aggregateReference the metric without an extra aggregate

Warnings

CodeMeaningAction
relationship_keys_unmatchedA relationship's to fields (or, for one_to_one, either end) don't match a key of that dataset, so joins may repeat rowsJoin on the key, or declare the fields in primaryKey or uniqueKeys. See Join Keys
duplicate_nameA name was already duplicated before this saveRename one of them when you can; new duplicates are errors
native_type_needs_syncA field now reads a different column or source, so its stored database type was clearedRun Sync data types on the dataset
E006An input is defined but not used in the expressionRemove 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

On this page