> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getcanonic.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Guide: SaaS Analytics (all 7 binding kinds)

> The broadest example: every metric binding kind, all three guardrail kinds, finality, and assertions.

A complete, self-contained data-warehouse example: a SaaS subscription business modeled Kimball-style as a Business Vault (dimensions + facts, including a monthly snapshot fact) with a condensed Data Mart layer on top, all in a single bundled DuckDB file.

This is the broadest example in the set: it exercises every [metric binding kind](/concepts/contracts-and-guardrails#beyond-single-composable-metrics) canonic supports, all three guardrail kinds, finality/restrict-source, query-based assertions, and a global knowledge base, in one project.

<Info>Full source: [`examples/saas-analytics/`](https://github.com/mischuh/canonic/tree/main/examples/saas-analytics)</Info>

## Schema

Business Vault (8 dimensions, 10 facts) + 4 data marts:

| Table                                            | Rows      | Description                                                             |
| ------------------------------------------------ | --------- | ----------------------------------------------------------------------- |
| `dim_customer`                                   | 12        | One row per customer account (conformed dimension)                      |
| `dim_plan` / `dim_geo` / `dim_industry`          | 4 / 5 / 5 | Plan catalogue, geography, industry                                     |
| `dim_sales_rep` / `dim_campaign` / `dim_feature` | 6 / 5 / 8 | Reps, acquisition campaigns, product features                           |
| `dim_date`                                       | 731       | Conformed daily calendar (2024–2025)                                    |
| `fct_mrr_snapshot`                               | 144       | **Snapshot**, MRR position per customer per month                       |
| `fct_subscription_events`                        | 18        | Lifecycle events (new/expansion/contraction/churn)                      |
| `fct_invoices` / `fct_invoices_rt`               | 131 / 3   | Final billing + provisional intraday estimates                          |
| `fct_usage` / `fct_feature_usage`                | 130 / 842 | Account usage rollup + per-feature usage                                |
| `fct_support_tickets` / `fct_opportunities`      | 24 / 20   | Support tickets, sales pipeline                                         |
| `fct_nps_responses` / `fct_payments`             | 18 / 130  | NPS survey, payment transactions                                        |
| `mart_*`                                         | n/a       | Pre-aggregated monthly MRR, cohort retention, account health, rep quota |

## Quickstart

```bash theme={null}
cd examples/saas-analytics
bash scripts/build.sh   # (optional) rebuild the warehouse from setup.sql

canonic query --metrics ending_mrr --dimensions snapshot_month
canonic query --metrics arpu --dimensions snapshot_month
canonic query --metrics customer_ltv --dimensions customer_id

canonic assert       # runs the contract assertions, gates on accuracy
canonic mcp start
```

No LLM is required: this example ships hand-curated semantics and contracts.

## Metric catalogue: all 7 binding kinds

| Kind             | Metric(s)                                                                                             | Binding highlight                                            |
| ---------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `single`         | `gross_revenue`, `expansion_mrr`, `support_tickets`, `pipeline_value`, `settled_payments`, `avg_csat` | source + measure                                             |
| `ratio`          | `arpu`, `churn_rate`, `win_rate`, `cac`, `nps`                                                        | numerator / denominator (both `single`)                      |
| `weighted_avg`   | `blended_discount`                                                                                    | `weighted_sum` / `weight`                                    |
| `semi_additive`  | `ending_mrr`                                                                                          | `collapse_dimension: snapshot_month`, `collapse_agg: last`   |
| `distinct_count` | `active_subscribers`, `active_features`                                                               | `distinct_on` + `population_filter`                          |
| `percentile`     | `median_contract_value`, `p90_resolution_time`, `median_deal_size`                                    | `column` + `quantile`                                        |
| `opaque`         | `customer_ltv`                                                                                        | `native_grain: [customer_id]`, served at customer grain only |

Ratio and weighted-avg components must themselves be `single`-kind metrics, so a set of small helper metrics (`mrr_total`, `active_accounts`, `churned_customers`, `new_customers`, `won_opportunities`, `total_opportunities`, `campaign_spend`, `discount_value_sum`, `total_contract_value`, `nps_net`, `nps_responses`) provide those building blocks. See [The semantic compiler](/concepts/compiler#fanout--additivity) for how each kind actually compiles.

## Guardrails: all three kinds

* **`revenue-excludes-refunds`** / **`revenue-excludes-trials`**: `mandatory_filter` (`error`). Injects `status != 'refunded'` and `is_trial = false` into every `gross_revenue` query.
* **`board-reporting-final-only`**: `restrict_source` (`error`). In the `board_reporting` context, confines `gross_revenue` to the final `fct_invoices` source, paired with a finality rule (`finality-revenue`) that declares the final/provisional realizations and coalescing rule.
* **`ending-mrr-requires-month`**: `required_dimension` (`warn`). Declared and surfaced, but (as noted in [Contracts & guardrails](/concepts/contracts-and-guardrails#guardrails)) `required_dimension` isn't yet enforced by the compiler.

```json theme={null}
// refunds + trials silently excluded; provisional rows excluded under board_reporting
query({"metrics": ["gross_revenue"], "context": "board_reporting"})

// opaque grain guard: this errors with UNSUPPORTED_MEASURE
query({"metrics": ["customer_ltv"], "dimensions": ["segment"]})
```

## Assertions

Query-based, with expected values derived from the deterministic seed:

* `gross-revenue-2025-q1` → `17814.00` (paid, non-trial, non-refunded invoices, Q1)
* `active-subscribers-2025-03` → `12`

`canonic assert` runs them and reports accuracy (expected: 100%). See [`canonic assert`](/cli-reference/query-sql-assert#canonic-assert).

## Knowledge

`knowledge/global/`: bound to semantic entities via `sl_refs`:

* `mrr-definition` (`definition`, with a live `{{ sl:… }}` template)
* `semi-additive-mrr-caveat` (`caveat`): never sum MRR across months
* `revenue-excludes-refunds-trials-caveat` (`caveat`)
* `revenue-finality-policy` (`policy`): final vs. provisional revenue
* `ltv-methodology` (`policy`): why `customer_ltv` is `opaque`
* `vault-vs-mart` (`reference`): when to use vault facts vs. data marts

## Regenerating the warehouse

```bash theme={null}
bash scripts/build.sh   # rebuilds saas.duckdb from setup.sql (idempotent)
```

`setup.sql` is plain, deterministic DDL + seed data and can also be run directly through any DuckDB client: `duckdb saas.duckdb < setup.sql`.
