Skip to main content
On this page

Model

How modeling works

Define a metric once in the shared layer, and every answer, app, dashboard and agent uses the same definition.

Audience
Data teams and platform admins
Prerequisites
  • A warehouse connection (Snowflake or DuckDB)

The layers

Data flows down from your warehouse tables to your team, agents and apps that read it. Each layer reads only from the layer above it.

Source layerDatabase datasets
kater sync --resource-type db_dataset writes one for each table in your warehouse, such as Snowflake.
Each Kater dataset adds fields and metadata to one database dataset. Semantic models group them.
A workspace is private or shared with your team. Its root reads one shared model.
Your team, your agents and apps read one or more workspace queries. Your team also builds workspaces.
  • Source layer. One database dataset per warehouse table. kater sync writes these files for you.
  • Shared layer. Kater datasets and semantic models. The data team defines dimensions, measures and joins here. Explore reads this layer.
  • Workspace layer. A workspace root reads one shared model. Queries in the workspace read the root, and charts read their query.
  • Consumers. Your team, your agents and apps read one or more workspace queries. Your team also builds the workspaces.
  • Sandbox. Scratch files in your own brainstorm folder, for quick SQL. Nothing reads them.

Models are SQL, not YAML

Every model is a .sql file in your warehouse’s own SQL, with a few Kater calls. There is no YAML file of metrics to keep in step with it. The compiler reads the SQL and makes a definition card for each field. You, your agents and every editor read the same card.

A measure is one line

The Orders dataset of Kater’s sample shop declares Revenue:

modeling/kater_objects/datasets/orders.sql
SELECT
DIMENSION('order_id', orders.order_id) AS "Order ID",
DIMENSION('region', orders.region) AS "Region",
DIMENSION('currency', orders.currency) AS "Currency",
DIMENSION('amount', orders.amount) AS "Amount",
-- ...
-- @description Order amount before discounts.
MEASURE('sum__amount', KATER__SUM(orders.amount), NOT_ADDITIVE_ACROSS(currency)) AS "Revenue", /* @format currency(currency, 2) */
-- ...
FROM shop.sales.orders AS orders

Kater makes this card from those two lines. It is the card of the Definitions tab, of a hover in the SQL editor, and of VS Code. Open a row under Use to see the query it writes, then select Preview data.

  • Computes. KATER__SUM(orders.amount) is the expression. A KATER__ function means the same on every warehouse.
  • Inputs. NOT_ADDITIVE_ACROSS(currency) is a rule: each result row holds one currency, so no total adds euros to dollars.
  • Description and format. -- @description and @format are comments in the SQL.
  • Use. The ways a reader can use Revenue: evaluate it, pass it through, or build on top of it with a running total, a rank or a share of total.

A measure built from measures

A query of the Shop workspace divides Revenue by Customers:

workspaces/shop/query_contracts/region_mix.sql
SELECT
shop.region_group,
shop.country,
shop.currency,
shop."loyalty tier",
shop.sum__amount,
MEASURE(
'revenue_per_customer',
EVAL(shop.sum__amount) / NULLIF(EVAL(shop.count_distinct__customer_id), 0)
) AS "Revenue per Customer"
FROM workspace.shop AS shop
WHERE
OPTIONAL_FILTER(
'is_equal_to__country',
KATER__IS_EQUAL_TO(shop.country, $is_equal_to__country),
label='Country'
)
AND REQUIRED_FILTER(
'text_contains_paid__channel',
KATER__TEXT_CONTAINS(shop.channel, 'paid'),
label='Channel contains paid'
)
GROUP BY
shop.region_group,
shop.country,
shop.currency,
shop."loyalty tier"

Nobody wrote a currency rule for Revenue per Customer. Its card has one anyway: it reads Revenue, so Revenue’s rule comes with it.

  • Required. One currency per result row, from Revenue.
  • Always filtered. The query’s REQUIRED_FILTER. Its OPTIONAL_FILTER is under Optional filters, for a reader to turn on.
  • Use. Each reader decides how to use it. A rank or a share of total is one call, and it stays within each currency.

How a field moves up

A new metric often starts in one query. When it proves useful, you promote it up a layer, and its meaning stays the same.

Querytoworkspace
PromoteROASReady to apply

Promoting ROAS up to Channel Performance Weekly lets you reuse the same definition everywhere rather than redefining it in multiple places.

Expression
EVAL(Conversion Value) / NULLIF(EVAL(Spend), 0)
Workspace layertoshared layer
PromoteAvg Order ValueReady to apply

Promoting Avg Order Value up to shop lets you reuse the same definition everywhere rather than redefining it in multiple places.

Expression
EVAL(Revenue) / NULLIF(EVAL(Orders), 0)

In a query. You declare it with MEASURE in one query. Only that query reads it.

To the workspace. You promote it to the workspace root. Every query in the workspace now reads one definition, and queries that computed the same formula point at it.

To the shared layer. You ask the data team to promote it. The data team approves the request, and every workspace on the connection can read it. The field now shows as verified.

See Promotion and verification.

How to promote a field

Like everything in Kater, there are three ways to promote a field:

In the workspace editor, open the menu of a field made in the query, on its column or in the field list. Select Promote to workspace. The promotion dialog shows where the field goes and its expression. Select Promote.

The workspace editor on a query in the Marketing Sessions workspace. The field list marks the shared fields as verified. The menu of Active Identities (Share of Total), a field made in the query, has Promote to workspace selected.
A query reads verified fields from the shared model. Promote to workspace moves a field made in the query up a layer.

Ask your agent, such as Claude or ChatGPT, connected to Kater. It drafts the promotion and shows the card: where the field goes, its expression, and each query that defines the same thing, with its rows compared. Select Promote to apply it. See Ask Kater MCP.

An agent answers Can ROAS be one measure in Channel Performance Weekly? with Yes, and every number stays the same. The promotion card shows Promote ROAS, Ready to apply, Promote to: Channel Performance Weekly, the new field ROAS with its expression and Always filtered US dollars, then three queries it replaces, roas_trend_by_platform, top_campaigns and weekly_scorecard, each the same in all its rows, and the Promote button
The agent drafts the promotion. Promote applies it.

In VS Code, run Kater: Promote a Field from the Command Palette. From a terminal, use kater model promote:

Terminal window
kater model promote revenue_per_customer --relation region_mix \
--scope connections/duckdb_sample/workspaces/shop --dry-run

--dry-run shows the change and writes nothing. Run it again without --dry-run to write.

Promotion recommendations for admins and developers

Kater finds the same formula defined in more than one place and recommends a promotion for it. A nightly scan compiles every workspace on the connection and matches formulas by fingerprint, so two fields with different names still match. Open Admin › Objects and select Promotions. Each row shows the field and the model it moves to, who asked for it or that Kater found it, its expression, how many definitions it replaces, and its state. Select a row to see the drafted field and each definition it replaces, with its numbers before and after. Then select Apply or Dismiss. See Promotion requests.

Admin Objects on its Promotions tab, with Run now and the Source filter. Rows such as Promote Cost per Conversion to Ad Reporting, which Sally Ng asked for, and Promote ROAS to Ad Reporting, which Kater found, show each expression and how many definitions it replaces. The details panel for ROAS shows Promote ROAS to Ad Reporting, Ready to apply, then Promote to: Ad Reporting and the new field ROAS with its expression. Under After promotion, replaces 4 definitions of the same thing, each definition is the same in all its rows or checks. Dismiss and Apply are at the bottom
Kater recommends a promotion for each formula defined in more than one place.

What you get

Define a metric once, and every answer, app, dashboard and agent uses that same definition. In Kater you model your warehouse in layers of plain SQL files. The data team writes the shared definitions. Everyone else builds analysis on top of them.

When a marketer asks their agent for spend by platform in the last 3 months, the agent reads the Spend measure your data team defined, with its currency rule and its description. It does not write a SUM of its own.

  • One definition per metric. A measure such as Spend is written once, in a shared model. Queries, dashboards, apps and agents read it by name.
  • Numbers that stay right. The compiler refuses a query that adds up revenue across currencies, and a join never counts an order twice inside a measure.
  • Context for AI. Descriptions, AI context and business questions sit beside each field, so an agent knows what a field means before it uses it.
  • Plain files. Every model is a .sql file with YAML frontmatter. You can read it, compare it, test it and review it.

What each model can do

These rules are fixed policy in the compiler. Every editor, the CLI and the MCP apply the same rules.

ModelLayerReadsJoinsParametersMeasuresIn Explore
Database datasetSourceOne warehouse table, described by its DDLNoNoNoNo
Kater datasetSharedExactly one database datasetNoNoYesYes
Custom SQL datasetSharedWarehouse tables and the Kater models you can readYesNoYesYes
Semantic modelSharedKater datasets and other semantic modelsYesNoYesYes
TestSharedOne semantic model or Kater dataset, or a workspace rootNoNoYesNo
Workspace rootWorkspaceExactly one semantic model or Kater datasetNoYesYesNo
QueryWorkspaceIts workspace root and other queries in the same workspaceYesYesYesNo
ChartWorkspaceIts parent queryNoUses its query’sNoNo
Scratch fileSandboxShared models, workspace roots and queries, and warehouse tables if you can edit the shared layerPlain joinsYesYesNo

A semantic model is optional. A workspace can read a Kater dataset directly.

Rules that follow from the layers

  • Shared models take no parameters. Only workspace roots and queries declare parameters and optional filters. A shared model can hold a required filter with fixed values.
  • Custom calculations live in queries. A rank or a running total over result rows is a query’s custom calculation. In a shared model a window is a dimension, and a workspace root runs no window. See Custom calculations.
  • Explore runs one way. Explore reads Kater datasets and semantic models. A card leaves Explore as an ordinary query in a workspace, and nothing in a workspace flows back into Explore.

Every model is a Kater SQL file

A model file is YAML frontmatter between --- lines, then one SQL statement. Kater SQL is your warehouse’s own SQL with a few additions: DIMENSION, MEASURE, EVAL, RELATIONSHIP, the filter calls, the KATER__ functions and -- @ comment directives.

modeling/kater_objects/semantic_models/shop.sql
---
name: shop
model_type: semantic
label: Shop
---
SELECT
o.order_id,
o.region,
...
c.country,
o.sum__amount,
...
DIMENSION('amount_band', KATER__BUCKET(o.amount, [25, 100])) AS "Amount Band"
FROM kater_dataset.orders AS o
-- @label Customer
LEFT JOIN RELATIONSHIP('customer', kater_dataset.customers AS c ON o.customer_id = c.customer_id, 'many_to_one')

The Shared models editor reads and writes this file. A field you tick, a join you add and a label you type are all changes to its SQL, and what you type in the SQL shows in the editor. The full syntax is in the Kater SQL reference.

What a connection holds

Each warehouse connection is a folder in your organisation’s content store. Kater hosts the content store. You work in it through the portal, the CLI, VS Code or your agent.

connections/<connection>/
├── connection.yaml the warehouse and the schemas to sync
├── connection_context.md notes agents read for this connection
├── .kater/dataset-sync/ sync state, kept with the content
├── ai_tools/ agents, skills and workflows
├── modeling/ source and shared layers
│ ├── db_objects/datasets/<DATABASE>/<SCHEMA>/<TABLE>.sql
│ └── kater_objects/
│ ├── datasets/<name>.sql
│ ├── semantic_models/<folder>/<NAME>.sql
│ └── tests/<model>.sql
├── workspaces/<workspace>/ connection workspaces
│ ├── workspace.sql
│ ├── query_contracts/<name>.sql
│ ├── charts/<name>.sql
│ ├── dashboard.yaml
│ └── apps/<app>/
└── users/<email>/ your Personal folder
├── workspaces/<workspace>/ your private workspaces
├── tests/<model>.sql your own tests
└── brainstorm/ scratch files and notes, never versioned

Only each connection’s modeling/ folder can be written back to GitHub. See Content store and GitHub write-back.

Where you edit models

Every surface writes the same files through the same operations. Each save is formatted and compiled, and a save that adds a compile error is refused. Scratch files are the one exception: they save as written.

WhereGood for
Shared models in the portalSemantic models and Kater datasets: sources and joins, fields, labels and descriptions, model details, tests and history. See Semantic models.
Files in the portalAny file in the content store. A semantic model or Kater dataset opens in the same editor, inside Files. Database datasets keep Files’ own view, and Files › Modeling lists models by layer and runs schema sync.
The workspace editorWorkspace roots and queries: the field picker, field menus, the filter bar and the formula bar. See Queries.
VS Code extensionThe same editor in the Kater panel, beside the file, with diagnostics as you type, hover, completion, go to definition and quick fixes.
Kater CLISync, create, edit through typed operations, compile, run and test. The Files terminal runs the same commands.
Your agent, through the Kater MCPDescribe the change. The agent writes through the same operations, and anything outside your brainstorm folder waits for your yes.

Shared models

Go to /shared-model in the portal. With no model open, the page says “Open a model, or make a new one.” The Model Selector at the left lists your pinned models, every model by topic, and the Kater datasets. The search box finds models and fields, or takes a description of a new model for your agent. The New semantic model and New Kater dataset cards open the New dialog.

Shared models with no model open: the Model Selector with Pinned, By topic and Kater datasets at the left, and at the center the search box under Open a model, or make a new one., pills for recent models and a tag, and the New semantic model and New Kater dataset cards
Shared models, with no model open.

Open a model, and the editor shows it whole: its sources and the joins between them, its fields grouped by source, and its rows. Everything saves itself. There is no Save button, and the footer says Saving, then Saved. A model’s address is /shared-model/<connection>/<model>.

Marketing Sessions open in the Shared models editor: the field list grouped by source at the left, the toolbar with 5 sources, 4 joins and 60 fields, the joins diagram with the base GA4 Lifecycle Sessions and four joined sources, and 200 of 18,603 rows below
Marketing Sessions in the Shared models editor.

In Files

Open a semantic model or Kater dataset file in Files, and the same editor opens in the file’s tab. Files keeps its own menu bar, breadcrumb and explorer. Above the editor, the file’s view bar has Editor, Definitions and Explore, and a menu with the Model items.

MARKETING_SESSIONS.sql open in Files: the FILES explorer with the file selected, the file tab with Editor, Definitions and Explore, and the model editor with its field list, toolbar, joins and rows
A semantic model open in Files.

In VS Code

Open a semantic model or Kater dataset file in VS Code, and the KATER panel shows the same editor below the SQL. Select a join in the panel, and its lines light up in the file. The editor’s menu items are Kater commands in the Command Palette, such as Kater: Add Source, Kater: Check Every Join and Kater: Generate Tests. See VS Code extension.

VS Code with MARKETING_SESSIONS.sql open at lines 110 to 121, the Lifecycle profile join's lines lit, and the KATER panel below showing the model editor with Lifecycle profile selected in the joins diagram
The same editor in VS Code’s Kater panel.

Next steps