Full source:
examples/saas-analytics/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
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 |
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 for how each kind actually compiles.
Guardrails: all three kinds
revenue-excludes-refunds/revenue-excludes-trials:mandatory_filter(error). Injectsstatus != 'refunded'andis_trial = falseinto everygross_revenuequery.board-reporting-final-only:restrict_source(error). In theboard_reportingcontext, confinesgross_revenueto the finalfct_invoicessource, 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)required_dimensionisn’t yet enforced by the compiler.
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.
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 monthsrevenue-excludes-refunds-trials-caveat(caveat)revenue-finality-policy(policy): final vs. provisional revenueltv-methodology(policy): whycustomer_ltvisopaquevault-vs-mart(reference): when to use vault facts vs. data marts
Regenerating the warehouse
setup.sql is plain, deterministic DDL + seed data and can also be run directly through any DuckDB client: duckdb saas.duckdb < setup.sql.