Back to course overview
Module 3Building the semantic layer 30 min

Lab: Run the layer and add a metric

Query the semantic layer directly, verify it matches your raw SQL, then define a brand-new metric end to end.

Outcome of this lab

Confidence that the layer's numbers match hand-written SQL, plus a new metric you defined yourself in metrics.yml.

Work from the starter-kit folder with your venv active, then start a Python session:

bashbash
cd course-assets/semantic-agent
source .venv/bin/activate
python3        # then paste the snippets below

Step 1 — Query the layer

pythonpython
from semantic_layer import SemanticLayer
L = SemanticLayer()

print(L.query(["revenue", "gross_margin"]))
print(L.query(["revenue", "aov"], ["channel"], order_by="revenue"))
print(L.query(["revenue"], ["date"], time_grain="month", order_by="date", descending=False))

Two parameters worth noticing: order_by sorts the result rows by any requested metric or dimension (here by revenue, then by date), and descending controls the direction — it defaults to True (biggest first), so the monthly query passes descending=False to get months in chronological order.

Step 2 — Browse the full catalog

pythonpython
import json
print(json.dumps(L.catalog(), indent=2))

catalog() is the machine-readable menu of everything the layer can answer — the same thing the agent's list_metrics tool will return in Part 2. It defines 11 metrics and 9 dimensions — more than the 7 metrics you modeled on paper in Module 2. The extras are active_subscriptions, units_sold, gross_profit, and marketing_spend: supporting measures the ratios need, plus a few the business would obviously ask for.

Step 3 — Verify against your raw SQL

  1. 1Compare the layer's total revenue to the raw-SQL number from Module 1 — both should be $743,772.80. Exact match means your definitions are faithful.
  2. 2Compare revenue-by-channel. The layer's numbers should equal your hand-written GROUP BY to the penny.
  3. 3Deliberately break something — remove the completed-only filter from revenue in metrics.yml — and watch the total change. Then put it back. You've just felt why the filter belongs in the definition.

Step 4 — Add a new metric

Add refund_rate = refunded orders ÷ all orders. It's a ratio of two measures you'll define. Add to metrics.yml:

yamlyaml
refunded_orders:
  kind: measure
  base: orders
  sql: "COUNT(DISTINCT CASE WHEN orders.status='refunded' THEN orders.order_id END)"

all_orders:
  kind: measure
  base: orders
  sql: "COUNT(DISTINCT orders.order_id)"

refund_rate:
  kind: ratio
  numerator: refunded_orders
  denominator: all_orders
  format: percent
  1. 1Add the three definitions above.
  2. 2Query L.query(["refund_rate"]) and L.query(["refund_rate"], ["platform"]).
  3. 3You defined a new governed metric — sliceable by any dimension — without writing a single query. That's the payoff.
Tip

Notice you added business capability by editing config, not code. Every dashboard and, soon, your AI agent can now answer refund-rate questions — consistently — because the definition lives in one place.