# The definitions document isn't documentation. It's an accuracy intervention

In a previous [post](https://zhaidata.com/better-column-descriptions-made-the-ai-right-more-often-not-reliably) I showed that column descriptions improve the accuracy of AI-generated SQL but don't make it deterministic. This is the follow-up: what happens when you go further and give the model a structured business definitions document?

Short answer: it fixed every error I tested, and — more interesting — it changed the *kind* of answer the model gave.

### Setup

I built a dbt project over BigQuery's `thelook_ecommerce` dataset. The marts layer has two main tables: `fct_order_items` (line-item grain) and `fct_orders` (order grain, pre-aggregated). Both are clean, well-named, documented in the schema yaml.

Before building anything AI-related, I wrote a [definitions document](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/docs/DEFINITIONS.md) — ten business decisions, each traceable to a [profiling finding](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/docs/SCHEMA_NOTES.md). Things like: what does "revenue" mean (gross or net)? Which statuses count as "sold"? Which timestamp anchors a time period? Which join path reaches the product table?

Then I asked Claude the same three questions, twice: once with just the schema, once with the definitions document pasted as context.

### Question 1: "How many items did we sell in 2023?"

**Without definitions:**

```sql
SELECT COUNT(*) AS items_sold
FROM fct_order_items
WHERE EXTRACT(YEAR FROM ordered_at) = 2023
  AND line_status != 'Returned'
```

It filtered out returns — reasonable. But it kept Cancelled items. In this dataset, a cancelled item isn't a sale. The model made a judgment call about which statuses count as "sold" and got it partially right.

**With definitions:**

```sql
SELECT COUNT(*) AS items_sold
FROM fct_order_items
WHERE EXTRACT(YEAR FROM ordered_at) = 2023
  AND line_status NOT IN ('Returned', 'Cancelled')
```

Correct. And it cited the reasoning: "the definitions specify that Processing lines count as sales because the customer committed to buy."

The difference isn't just the filter. Without definitions, the model explained what it did. With definitions, it explained *why*, referencing a convention rather than improvising one.

### Question 2: "What was our revenue in 2023?"

This is the question with two defensible answers.

**Without definitions:**

```sql
SELECT SUM(gross_revenue) AS revenue_2023
FROM fct_orders
WHERE EXTRACT(YEAR FROM ordered_at) = 2023
  AND order_status != 'Cancelled'
```

Three problems:

1.  It picked `gross_revenue` — which includes returned items. In this dataset, returns are 10% of revenue. That's a material difference.
    
2.  It added an ad-hoc `order_status != 'Cancelled'` filter on the header, but `gross_revenue` was already computed from all lines regardless of status. The filter and the column contradict each other.
    
3.  It never mentioned that the table has a `net_revenue` column, or that the choice between gross and net existed.
    

**With definitions:**

```sql
SELECT SUM(net_revenue) AS revenue_2023
FROM fct_orders
WHERE EXTRACT(YEAR FROM ordered_at) = 2023
```

Correct. No ad-hoc filter needed — `net_revenue` already excludes Returned and Cancelled at the line level. And it explained why the previous approach was wrong: "adding `AND order_status != 'Cancelled'` would actually under-count by dropping entire orders that have a Cancelled header status."

This is the result that surprised me most. Without definitions, the model didn't just pick the wrong column — it built an inconsistent query that mixed a gross measure with a net filter. With definitions, it picked the right column and *caught the mistake it would have made*.

### Question 3: "What was our gross margin by category in 2023?"

**Without definitions:**

```sql
SELECT category, SUM(line_margin) AS gross_margin, ...
FROM fct_order_items
WHERE EXTRACT(YEAR FROM ordered_at) = 2023
GROUP BY category
```

No status filter. Margin included returned and cancelled items. The number is defensible in isolation — you could argue margin should include all items — but it's inconsistent with how the same model answered question 1, where it filtered out returns.

That inconsistency is the real problem. Across three questions, the model without definitions made three different decisions about what "sold" means. A user asking these questions in sequence would get numbers that don't reconcile with each other.

**With definitions:**

```sql
SELECT category, SUM(line_margin) AS gross_margin, ...
FROM fct_order_items
WHERE EXTRACT(YEAR FROM ordered_at) = 2023
  AND line_status NOT IN ('Returned', 'Cancelled')
GROUP BY category
```

Correct filter, consistent with questions 1 and 2.

### The pattern

Without definitions, the model was:

*   Partially right on status filtering (caught returns, missed cancellations)
    
*   Inconsistent across questions (filtered status for counts, not for margin)
    
*   Silent about ambiguity (never mentioned that "revenue" has two meanings)
    
*   Incoherent within a single query (gross column + net filter on Q2)
    

With definitions, the model was:

*   Correct on every status filter
    
*   Consistent across all three questions
    
*   Explicit about conventions ("per the business definitions...")
    
*   Self-correcting ("this differs from my earlier draft because...")
    

The definitions document didn't just improve accuracy. It gave the model a coherent framework to reason from, and the reasoning was visible in the response.

### What a definitions document actually is

This is the point I want to make clearly, because it changes how you think about the work.

Most teams treat business definitions as documentation — something you write for onboarding or compliance, stored in Confluence, read once, and never updated. The definition of "active user" is written somewhere, and nobody checks whether the dashboard agrees with it.

What this experiment suggests is that a definitions document is an *accuracy intervention* — it directly changes the correctness of AI-generated queries, measurably, on every question that touches a defined term. That makes it engineering work, not paperwork. And it makes maintaining it operational, not optional.

The ten definitions I wrote took about two days, including the [profiling](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/scripts/profile.sql) that produced the evidence behind each one. Every one resolved an ambiguity that would otherwise be resolved differently on every query, by every user, with no way to detect the inconsistency.

### Caveats

Three questions, one model (Claude), tested 2026-09-03. I didn't run N repetitions on this one because I used the chat interface rather than the API — so I can't report variance. The findings are directional, not statistical. The full eval harness with systematic runs across multiple models is coming.

This also tested the ideal case: the definitions were pasted directly into context. In a real deployment, the definitions would need to be retrieved, selected, and injected automatically — which is a harder problem. What this shows is the ceiling: *if* the model has the definitions, it uses them well.

* * *

*Part of a project building an evaluation harness for AI-generated SQL. The definitions document, the profiling methodology, and the dbt project are open-source.*

Repo: [github.com/Sean-Liu-GitHub/genbi-ecommerce](https://github.com/Sean-Liu-GitHub/genbi-ecommerce)

Key files:

*   [Business definitions](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/docs/DEFINITIONS.md)
    
*   [Schema profiling notes](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/docs/SCHEMA_NOTES.md)
    
*   [Profiling SQL](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/scripts/profile.sql)
    
*   [The intermediate model that resolves the join path](https://github.com/Sean-Liu-GitHub/genbi-ecommerce/blob/main/dbt_project/thelook/models/intermediate/int_order_items_enriched.sql)
