Metrics
One file per metric at metrics/name.yml, named after the metric's name field. A metric is one governed expression, used exactly, never re-derived.
As with tables, the field is authoritative and the filename must match it.
Fields
| Field | Type | Notes |
|---|---|---|
name | string, required | Stable snake_case identifier, and the filename |
display_name | string, required | Human name. Enforced by import validation: the check and the import both reject a metric without it |
expression | string, required | The SQL aggregate. Enforced like display_name. Identifiers quoted, stored case |
filters | string | WHERE-clause fragment the expression assumes |
table_schema | string | Schema of the base table. Note the field name: table_schema, not schema_name |
table_name | string | Base table the expression runs over |
domain_path | string | Domain the metric is listed under. Same rules as on tables |
description | string | What the metric means in business terms |
notes | string | Usage guidance for the agent: edge cases, what not to substitute |
precomputed_in | string | Where the metric already exists materialized, if anywhere |
synonyms | list of strings | Business vocabulary that should route to this metric |
unit | string | EUR, %, sellers |
description: Share of delivered orders that arrived on or before the estimated delivery
date, as a percentage. Around 92%.
display_name: On-time delivery rate
domain_path: marketplace
expression: 100 * AVG(CASE WHEN "IS_ON_TIME" THEN 1 ELSE 0 END)
filters: '"IS_DELIVERED" = TRUE AND "ORDER_DELIVERED_CUSTOMER_DATE" IS NOT NULL'
name: on_time_delivery_rate
synonyms:
- on time rate
- punctual delivery rate
table_name: FCT_ORDERS
table_schema: STALLORA
unit: '%'
expression is the rule, not necessarily executable SQL. The agent composes the query from it, so it can carry what a bare aggregate cannot: a join requirement, a dedup key, a boundary rule. Evals gate the result against expected SQL, so prose in the expression stays checkable.
What earns its own metric
A metric exists so a phrase resolves to one definition instead of being re-derived per question. Two signals that a phrase needs one: people disagree about how to compute it, or the computation carries a filter that is easy to forget. “On-time delivery rate” qualifies on both counts, because the delivered-only filter is exactly what a hand-written query drops.
synonyms is what makes that work in practice: it is the list of things people actually say. notes is where you tell the agent what not to substitute when the metric does not apply.
The filters field is part of the definition, not a convenience. It is applied whenever the metric is used, so a metric plus its filters is the whole rule. Splitting the rule between expression and a domain README is how two answers end up disagreeing.
File naming and sanitization
Table and metric filenames derive from the object’s name, with every character outside ASCII letters, digits, _, -, and . replaced by _. A metric named revenue/total becomes metrics/revenue_total.yml. The name field keeps the original value; only the filename is sanitized.
Precomputed metrics
precomputed_in names a table where the metric already exists materialized, which lets the agent read it instead of recomputing the aggregate. Removing that table clears the pointer: a stale one would send generated SQL at a table that no longer exists.