Skip to content
Not released yet: version 0.1 is being built. See what works today.

Metrics

A metric is defined once, in a file in git, with a description that tells an agent when to use it. Reports, the API and the MCP connector all read that one definition, so two people asking for “net sales” get the same number.

The e-commerce template has 115 metrics across 8 datasets, each defined this way.

The semantic layer lives in anfra, the engine from Holistics. You write it in AML files under semantic/ in your project:

  • models/*.model.aml map a table the transform step built.
  • datasets/*.dataset.aml group models with their dimensions and metrics. A dimension is a field you group or filter by, such as channel. A metric is a number you compute, such as total net sales.
  • apps/ holds reports.

Each metric has one key, one definition and one description. The key is a name like total_net_sales_excl_vat. Its definition is a formula in anfra’s query language, AQL:

definition: @aql fct_orders_base__order_lines.total_net_sales_excl_vat ;;

If two reports need a number that differs in what it counts, that is two metrics with two names. A period comparison is not a metric. Year over year and month over month are the same calculation on a shifted period, which anfra does when it runs the query.

AML has no field for aliases or for when to use a metric. So the description holds them, in a fixed shape. The first sentence says what the number is. Then come these labelled sections:

Section What it answers
Aliases Other names people use for it, so search finds it
When to use The question it fits, and which metric to use for a nearby question
Not the same as The metrics it is often confused with, and how they differ
Grain and filters What one row is, and what is left out
Sample questions Questions a person might ask that it answers
Caveats When the number misleads

mb check refuses a metric with no first sentence, no Aliases (it may say none), no When to use, no Grain and filters, or no unit: tag. It also requires Not the same as when another metric shares a word of the label.

Here is the real description of total_net_sales_excl_vat in the template. This copy is shortened: the real one lists more look-alike metrics under Not the same as.

metric total_net_sales_excl_vat {
label: 'Total net sales excl VAT'
description: '''
What customers paid for the items they bought, after sale prices and
discounts and less the money paid back for them.
**Aliases:** net_sales, Net sales, Net revenue, Revenue
**When to use:** To see how much the shop sold. For what customers paid
at checkout, shipping and VAT included, use Total order value incl VAT.
**Not the same as:** Total gross sales excl VAT, which is at list price
before discounts and refunds.
**Grain and filters:** One sold item, on the day its order was placed.
Gift cards, cancelled orders and test orders left out. Shipping and VAT
excluded.
**Sample questions:** How much did we sell last month? Which sales
channel sold the most?
**Caveats:** Refunds keep arriving for weeks, so the latest weeks read
high until they do.
'''
}

An agent reads these sections to pick the right metric and to say what a number includes. The Caveats line is why it can warn that last week’s net sales will still fall.

A key says what kind of number it is, then what it counts, then its qualifiers. The label is the key in words, in sentence case.

Kind Form Example
count number_of_<objects> number_of_orders
sum total_<amount> total_net_sales_excl_vat
mean average_<thing>[_per_<unit>] average_number_of_items_per_order
rate <event>_rate[_<base>] return_rate_items

Qualifiers come in a fixed order: deduction stage, basis, unit, then VAT. Every money metric ends in _excl_vat or _incl_vat. Use business words, and put abbreviations such as AOV in Aliases. The full rules are in docs/standards/naming.md in the core repository.

A term is a business word with one meaning, such as “Cancelled order”. Most terms are a dimension. “Cancelled order” is the boolean is_cancelled_order. You write its definition on the dimension, next to the logic. A concept with no field behind it, such as a cohort, goes in a file terms/<term_name>.md in your project. The glossary shows both together.

A report is one HTML file under semantic/apps/. The web app hosts it. Every query a report makes passes the same access check as any other query, so opening a report never gives its viewer data they could not query. See Security.

No tool writes a definition. The API and the connector read and query. To add or change a metric, your agent edits the files and opens a pull request. A person reviews and merges it. The history of every number is the git log.

See Built for agents for how an agent works with these files.