Our engine never sees your warehouse password
datahashi

BlogModeling & metrics

Modeling revenue so agents cannot break it

How to define revenue as a certified metric with clear grain, filters, and guardrails so agents and humans get the same number.

The datahashi team3 min read

Revenue is the metric agents misuse first. Not because agents are careless, but because “revenue” is rarely one thing. Gross vs net, booked vs recognized, refunds included or not - every team has a local definition. When an agent invents SQL to answer “what was revenue last quarter?”, it picks one of those locals at random and sounds confident about it.

datahashi treats revenue as a certified metric: authored once in YAML, published as an immutable artifact, queried by name. Humans and agents use the same request shape. Neither side invents SQL.

Start with grain, not the formula

Before you write an expression, decide what one row of revenue means. Order-level? Invoice-line? Recognized-revenue event? Grain is the constraint that keeps joins honest. If revenue lives on Order and you later slice by a one-to-many child (line items, shipments), fan-out will inflate the number unless the compiler knows the relationship.

In the semantic model, declare the entity, its table, and its primary key. Put the measure on that entity:

entities:
  - name: Order
    table: public.orders
    primary_key: id
    measures:
      - name: revenue
        expr: amount
        agg: sum

The measure is the aggregation primitive. The certified metric is what you expose to agents:

metrics:
  - name: revenue
    expr: Order.revenue
    unit: USD
    format: currency

Keep the thin layer thin. Transforms (currency conversion, recognition rules, refund netting) belong in the warehouse. The semantic model names the result and governs who can ask for it.

Encode the filters that define “real” revenue

Most revenue disputes are filter disputes. Completed orders only. Exclude internal accounts. Drop test tenants. Put those constraints where they cannot be skipped.

Use curated views or metric-level defaults so every credential applies the same filter set. A role that grants named metrics and not raw measures cannot be widened by the request, and it cannot invent a WHERE clause. That is the point: governance is the request shape, not a post-filter on SQL the agent wrote.

When someone needs a different definition (bookings vs recognized), publish a second metric with a different name. Do not overload one name with mode flags the caller is expected to remember.

Guardrails against misuse

Three patterns stop agents from breaking revenue:

  1. Grants for agents. Give the MCP or API credential a role that exposes the metrics it may use. Agents call list_catalog, describe_metric, and run_semantic_query, and can also drill_down, refine_semantic_query, and list_dimension_values. None of those tools accept or return SQL. The agent picks metric and dimension names from the catalog and sends a structured query.

  2. Explicit dimensions and time grain. Allow Order.ordered_at with a declared grain (month, quarter) rather than free-form date math. Ambiguous or invalid grain fails the request instead of compiling something “close enough.”

  3. Publish, then pin. Publishing freezes the model and its access rules as one version. Dashboards and agents can pin that version. Changing revenue later means publishing again - nothing rewrites mid-flight under an old name.

Metering covers every call, so you can see which agents ask for revenue and how often. That is observability on top of the certified definition, not a substitute for it.

What “agent-safe” actually means

Agent-safe does not mean the metric is simple. It means:

  • One name maps to one expression and one grain.
  • Joins are resolved by the model, not by the caller.
  • Filters that define the metric cannot be dropped by the caller.
  • The same Semantic Query object works in the console, over REST, and over MCP.

When finance, a BI dashboard, and an MCP agent all ask for revenue by quarter, they get the same number because they never got to write the SQL. That is the modeling job: make the safe path the only path.

Model revenue once. Publish it. Hand agents the name, not the warehouse.

Product notes

An occasional email when we ship something or write something worth reading.