GASP Adapter
The adapter is a JSON file that maps the tables and columns in your data warehouse to the GASP datasets and the 280 canonical GASP fields. Once it is filled in, your AI tools and your data team read Monthly Recurring Revenue (MRR) from the same column.
Why an Adapter?
GASP defines what to measure and how to calculate it. Where the data sits is different in every company.
Your MRR might live in billing.subscriptions.amount, stripe_data.mrr or dbt_marts.monthly_revenue.mrr_value.
The adapter writes that answer down once, as a declarative mapping: "GASP's mrr field is analytics.fct_subscriptions.mrr_amount in our warehouse."
Any tool that reads GASP can then query your warehouse without a custom integration.
For Data Engineers
Map your warehouse once. The SQL compiled from every GASP formula then reads your own tables and columns.
For AI Agents
An LLM with the GASP MCP server and your adapter knows which tables and columns to query for any metric.
For Teams
Everyone uses the same field names, so nobody has to ask "which column is MRR again?" in Slack.
How It Works
- Download the template. A JSON file with a slot for every GASP dataset and 418 mapping slots, one per field per dataset it lives on, covering all 280 GASP fields. Each slot lists the field's source system and the metrics that use it; enum fields also list the values the column must produce.
- Fill in your mappings. Add a
schema.tablefor each dataset you have and a column for each field your warehouse provides. Skip fields you don't have. - Validate. Run the
validate_adapterMCP tool. It checks the file against the adapter JSON Schema, flags fields mapped to a dataset that does not carry them and shows which metrics are fully covered, partially covered or not covered. - Use it. Hand the adapter to your AI tools, share it with your data team or use it as the foundation for a SaaS digital twin.
Adapter Format
The adapter is a JSON file with this structure. This excerpt is cut from the example adapter validated below: two of its datasets and six of their field mappings.
{
"$schema": "https://gaspwiki.com/schemas/gasp-adapter-v1.json",
"version": "1.1",
"organization": "Acme SaaS",
"warehouse": "snowflake",
"metricOwners": [
{
"metric": "MRR",
"team": "Finance",
"contact": "[email protected]"
},
{
"metric": "NRR",
"team": "Finance"
}
],
"datasets": {
"subscription": "billing.subscriptions",
"revenue_event": "billing.mrr_movements"
},
"mappings": [
{
"gaspField": "event_type",
"warehouseRef": "movement_type",
"transform": "CASE WHEN {{value}} = 'upgrade' THEN 'expansion' WHEN {{value}} = 'downgrade' THEN 'contraction' WHEN {{value}} = 'cancel' THEN 'churn' ELSE {{value}} END"
},
{
"gaspField": "mrr",
"warehouseRef": "mrr_cents",
"transform": "{{value}} / 100",
"owner": "Data Engineering",
"notes": "Stored in cents in our billing system"
},
{
"gaspField": "acquisition_date",
"warehouseRef": "acquisition_date"
},
{
"gaspField": "auto_renewed",
"warehouseRef": "auto_renewed"
},
{
"gaspField": "billing_trigger_date",
"warehouseRef": "billing_trigger_date"
},
{
"gaspField": "change_nature",
"dataset": "revenue_event",
"warehouseRef": "change_nature"
}
]
} Warehouse
The top-level warehouse is a free string naming your platform. snowflake, bigquery, databricks and postgres select the SQL dialect generate_query writes in.
Redshift reads Postgres SQL, so redshift selects the Postgres dialect. Any other value compiles to Postgres SQL; the tool always says which dialect it used.
Metric owners (recommended)
Every adapter should declare who owns each metric definition: the person or team that settles disputes about how a metric is calculated. GASP exists to end the "five companies, five definitions" problem, which only stays ended inside your company if someone owns the answer.
| Property | Required | Description |
|---|---|---|
| metric | Yes | Metric name or formula id (e.g. NRR, nrr), or All Core Metrics |
| team | Yes | Owning team (e.g. Finance, RevOps, Customer Success) |
| contact | No | Contact person. Email, Slack handle or name |
Field mappings
The top-level datasets map names the warehouse table (schema.table) behind each GASP dataset you have.
Each entry in mappings then maps one GASP field to a column:
| Property | Required | Description |
|---|---|---|
| gaspField | Yes | Canonical GASP field name (e.g. customer_id, mrr_start) |
| warehouseRef | Yes | Your warehouse column, as schema.table.column or as a bare column name when the dataset's table is declared under datasets |
| dataset | No | GASP dataset this mapping applies to. Fields that live on several datasets (customer_id, snapshot_date, event_date) need one mapping per dataset you use. A mapping without dataset applies to every dataset of the field |
| transform | No | SQL expression applied to the column, with {{value}} standing for it. Use it to convert units or to map your enum values onto the values GASP declares for the field (e.g. your 'Closed Won' onto GASP's 'closed_won') |
| owner | No | Team or person that owns this data pipeline. Who to contact when the data is stale or incorrect |
| notes | No | Free-text notes about this mapping |
Validating Your Adapter
Pass your adapter JSON to the validate_mapping MCP tool (validate_adapter is the same tool under its original name) and it reports which metrics you can compute. The coverage part of that report, for the example adapter:
Example: Acme SaaS maps 4 Billing tables (subscriptions, MRR movements, customers and invoices) and only the columns its billing system holds. This is the report the tool returns for that adapter, in full, generated when the site is built. # GASP Adapter Validation: Acme SaaS
## Schema
Valid against https://gaspwiki.com/schemas/gasp-adapter-v1.json.
**Warehouse:** snowflake
**Mapped fields:** 31 / 305 field and dataset pairs that canonical formulas read (written field@dataset: the logical table the column comes from)
**Datasets with a table:** 4 / 54
**Metrics:** 34 fully covered, 29 partially covered, 223 not covered (of 286 with a formula)
## Fully covered (34)
Every field the canonical formula reads is mapped:
- MRR (Monthly Recurring Revenue)
- ARR (Annual Recurring Revenue) (no owner declared)
- ARPA (Average Revenue Per Account) (no owner declared)
- MRR Growth Rate (no owner declared)
- New MRR (no owner declared)
- Expansion MRR (no owner declared)
- Churned MRR (no owner declared)
- Contraction MRR (no owner declared)
- Reactivation MRR (no owner declared)
- Carry MRR (no owner declared)
- Net New MRR (no owner declared)
- Total Customers (no owner declared)
- New Customers (no owner declared)
- Churned Customers (no owner declared)
- Net Revenue Retention (NRR)
- Gross Revenue Retention (GRR) (no owner declared)
- Logo Churn Rate (Customer Churn) (no owner declared)
- Revenue Churn Rate (no owner declared)
- Logo Retention Rate (no owner declared)
- Expansion Revenue Rate (no owner declared)
- Renewal Rate (no owner declared)
- Days to Renewal (no owner declared)
- Billings (no owner declared)
- Collection Rate (no owner declared)
- Billing Accuracy (no owner declared)
- Invoice Cycle Time (no owner declared)
- Billing Disputes (no owner declared)
- Credit Memo Rate (no owner declared)
- Aging Buckets (no owner declared)
- Accounts Receivable Balance (no owner declared)
- Payment Method Distribution (no owner declared)
- Renewal Notification Rate (no owner declared)
- Auto-Renewal Rate (no owner declared)
- Renewal Processing Time (no owner declared)
## Partially covered (29)
- **Customer Health Score**: missing `composite_score@customer`
- **Health Score Coverage**: missing `composite_score@customer`
- **At-Risk Customer Rate**: missing `composite_score@customer`
- **Renewal Forecast Accuracy**: missing `forecast_renewal_amount@subscription`
- **Accounts per CSM**: missing `csm_id@customer`
- **ARR per CSM**: missing `csm_id@customer`
- **Product Qualified Accounts (PQAs)**: missing `criteria_met@customer`
- **Order-to-Activation Time**: missing `activation_date@customer`
- **Quarterly Business Review (QBR) Completion Rate**: missing `qbr_completed@customer`, `qbr_due@customer`
- **Burn Multiple**: missing `cash_balance@balance_sheet`, `snapshot_date@balance_sheet`
- **Onboarding-to-Active Rate (Activation Rate)**: missing `activation_date@customer`, `onboarding_completion_date@customer`
- **30-Day Retention (Post-Onboarding)**: missing `last_active_date@customer`, `onboarding_completion_date@customer`
- **Collection Effectiveness Index (CEI)**: missing `accounts_receivable@balance_sheet`, `snapshot_date@balance_sheet`
- **Tickets per Customer**: missing `created_at@ticket`, `ticket_id@ticket`
- **CAC (Customer Acquisition Cost)**: missing `cost@cost_event`, `cost_function@cost_event`, `event_date@cost_event`
- **Rule of 40**: missing `ebitda@financials`, `period_start@financials`, `revenue@financials`
- **Contact Coverage**: missing `contact_engaged@contact`, `contact_id@contact`, `snapshot_date@contact`
- **Cross-sell Rate**: missing `amendment_type@amendment_event`, `customer_id@amendment_event`, `event_date@amendment_event`
- **Infrastructure Cost per Customer**: missing `cost@cost_event`, `cost_function@cost_event`, `event_date@cost_event`
- **Revenue per Employee**: missing `employee_id@employee`, `employee_status@employee`, `snapshot_date@employee`
- **Magic Number**: missing `cost@cost_event`, `cost_function@cost_event`, `event_date@cost_event`
- **Executive Sponsor Coverage**: missing `contact_engaged@contact`, `contact_role@contact`, `customer_id@contact`, `snapshot_date@contact`
- **Upsell Rate**: missing `amendment_type@amendment_event`, `customer_id@amendment_event`, `event_date@amendment_event`, `upgrade_eligible@customer`
- **ARR Including Usage Run-Rate**: missing `consumption_basis@credit_event`, `credit_amount@credit_event`, `credit_event_type@credit_event`, `event_date@credit_event`
- **Consumption Net Revenue Retention**: missing `credit_amount@credit_event`, `credit_event_type@credit_event`, `customer_id@credit_event`, `event_date@credit_event`
- **Marketing CAC**: missing `cost@cost_event`, `cost_centre@cost_event`, `cost_function@cost_event`, `event_date@cost_event`
- **LTV (Customer Lifetime Value)**: missing `cost@cost_event`, `cost_function@cost_event`, `event_date@cost_event`, `period_start@financials`, `revenue@financials`
- **LTV:CAC Ratio**: missing `cost@cost_event`, `cost_function@cost_event`, `event_date@cost_event`, `period_start@financials`, `revenue@financials`
- **CAC Payback Period**: missing `cost@cost_event`, `cost_function@cost_event`, `event_date@cost_event`, `period_start@financials`, `revenue@financials`
## Not covered (223)
NPS (Net Promoter Score), Gross Margin, License Utilization, Uptime (Availability), SLA Adherence, Error Rate, Latency (Response Time), Incident Count, Mean Time to Detect (MTTD), Mean Time to Acknowledge (MTTA), Mean Time to Resolve (MTTR), Change Failure Rate, Deployment Frequency, Lead Time for Changes, Mean Time to Recovery (MTTR), Infrastructure Utilization, Headroom, Vulnerability Count, Time to Patch, Security Incident Count, Revenue (GAAP), Bookings, Consumption Revenue, Usage Run-Rate, Prepaid Credit Balance, Credit Breakage, Consumption Revenue Growth Rate, Operating Margin (EBIT Margin), EBITDA Margin, Net Income Margin, Cash Burn Rate, Cash Runway, Free Cash Flow (FCF), Free Cash Flow Margin, Opex Ratio, S&M as % of Revenue, R&D as % of Revenue, G&A as % of Revenue, Bad Debt Rate, Visitors, Visitor-to-Lead Rate, Leads, Marketing Qualified Leads (MQLs), Lead-to-MQL Rate, Sales Qualified Leads (SQLs), MQL-to-SQL Rate, SQL-to-Opportunity Rate, Lead Velocity Rate (LVR), CAC by Channel, Cost per Lead (CPL), Cost per MQL, Marketing Sourced Pipeline, Marketing Sourced Revenue, Marketing Influenced Pipeline/Revenue, Organic Traffic, Paid Media Efficiency (ROAS), Share of Voice, NPS (Marketing context), Time to First Value (TTFV), Time to Go-Live, Time to Full Adoption, Onboarding Duration, Onboarding Completion Rate, Onboarding Milestone Completion Rate, On-Time Completion Rate, Session Attendance Rate, Task Completion Rate, Onboarding CSAT, Onboarding NPS, Onboardings per Resource, Cost per Onboarding, Time Spent per Onboarding, Feature Adoption at Go-Live, Total Active Partners, Partner Tier Distribution, Partner Activation Rate, Partner Churn Rate, Partner-Sourced Revenue, Partner-Influenced Revenue, Channel Revenue Percentage, Partner Revenue Growth, Average Partner Revenue, Partner Deal Registration Volume, Deal Registration-to-Close Rate, Partner Win Rate, Partner Deal Size, Partner Sales Cycle, Certification Rate, Training Completion, Joint Marketing Activities, Partner NPS, Partner Margin / Commission, Cost of Partner Acquisition, Partner Lifetime Value, Partner ROI, Marketplace Revenue, Marketplace as % of Revenue, Total Headcount, Headcount Growth Rate, Employee Turnover Rate, Voluntary Turnover Rate, Involuntary Turnover Rate, Regrettable Turnover, Average Tenure, 90-Day Turnover, Employee Net Promoter Score (eNPS), Engagement Score, Survey Participation Rate, Open Positions, Time to Fill, Time to Hire, Offer Acceptance Rate, Quality of Hire, Cost per Hire, Source of Hire, Compa-Ratio, Pay Equity Ratio, Diversity Representation, Diversity in Leadership, Diversity Hiring Rate, Daily Active Users (DAU), Weekly Active Users (WAU), Monthly Active Users (MAU), DAU/MAU Ratio (Stickiness), WAU/MAU Ratio, Active Accounts, Activation Rate, Trial-to-Paid Conversion Rate, Feature Adoption Rate, Core Feature Adoption, Feature Depth, Time to Feature Adoption, Breadth of Adoption, Session Frequency, Session Duration, Time in Product, Actions per Session, User Retention (Day N), Cohort Retention, Resurrection Rate, Release Frequency, Feature Delivery Rate, Roadmap Completion, Time to Market, Error Rate (User-Facing), Bug Escape Rate, User-Reported Issues, Feature Request Volume, Services Revenue, Services as % of Total Revenue, Services Attach Rate, Average Services Deal Size, Services Backlog, Services Gross Margin, Project Margin, Realization Rate, Write-off Rate, Billable Utilization, Total Utilization, Bench Rate, Capacity, Capacity Utilization, On-Time Delivery, On-Budget Delivery, Scope Change Rate, Project Duration Accuracy, Project Milestone Completion Rate, Project CSAT, Project NPS, Project Escalation Rate, Rework Rate, Reference-ability, Revenue per Consultant, Projects per Consultant, Average Project Duration, Services Pipeline, Services Win Rate, Time to Start, Pipeline Hygiene Score, Forecast Accuracy, Commit Accuracy, Stage Conversion Rates, Quote Volume, Quote-to-Order Rate, Quote Cycle Time, Quote Accuracy, Discount Rate, Order Processing Time, Order Error Rate, Days Sales Outstanding (DSO), Recognized vs Billed, Deferred Revenue Balance, Unbilled Revenue, Revenue Recognition Accuracy, System Sync Accuracy, Data Completeness, Pipeline Value, Pipeline Coverage Ratio, Pipeline Velocity, Win Rate, Lead-to-Opportunity Rate, Opportunity-to-Close Rate, ACV (Annual Contract Value), Average Deal Size, Sales Cycle Length, Quota Attainment, Revenue per Rep, Ramp Time, Ticket Volume, Ticket Backlog, Ticket Distribution by Type, First Response Time (FRT), First Response SLA Adherence, Resolution Time (Time to Resolution), First Contact Resolution (FCR), Resolution SLA Adherence, Reopen Rate, Customer Satisfaction Score (CSAT), Customer Effort Score (CES), Ticket Escalation Rate, Tickets per Agent, Cost per Ticket, Self-Service Rate (Ticket Deflection)
## Enum fields mapped without a transform (7)
The column must already produce GASP's declared values; otherwise add a CASE transform:
- `change_nature`: permanent, temporary, scheduled_reversal
- `customer_status`: trial, active, churned, dormant
- `expansion_classification`: same_product, new_module, new_buying_centre, pricing_adjustment, recontracting, usage_realisation
- `lifecycle_attribution`: acquisition, activation, retention, expansion, reactivation, servicing
- `payment_method`: credit_card, ach, wire, check, other
- `payment_status`: paid, unpaid, partial, written_off
- `subscription_status`: active, cancelled, expired, paused
## Covered metrics without an owner (32)
Add metricOwners entries so someone resolves definition disputes:
ARR (Annual Recurring Revenue), ARPA (Average Revenue Per Account), MRR Growth Rate, New MRR, Expansion MRR, Churned MRR, Contraction MRR, Reactivation MRR, Carry MRR, Net New MRR, Total Customers, New Customers, Churned Customers, Gross Revenue Retention (GRR), Logo Churn Rate (Customer Churn), Revenue Churn Rate, Logo Retention Rate, Expansion Revenue Rate, Renewal Rate, Days to Renewal, Billings, Collection Rate, Billing Accuracy, Invoice Cycle Time, Billing Disputes, Credit Memo Rate, Aging Buckets, Accounts Receivable Balance, Payment Method Distribution, Renewal Notification Rate, Auto-Renewal Rate, Renewal Processing Time
## Highest-value fields to map next
Each field@dataset pair with the number of partially covered metrics that still need it:
- `cost@cost_event` (7)
- `cost_function@cost_event` (7)
- `event_date@cost_event` (7)
- `period_start@financials` (4)
- `revenue@financials` (4)
- `composite_score@customer` (3)
- `csm_id@customer` (2)
- `activation_date@customer` (2)
- `snapshot_date@balance_sheet` (2)
- `onboarding_completion_date@customer` (2)
- `contact_engaged@contact` (2)
- `snapshot_date@contact` (2)
- `amendment_type@amendment_event` (2)
- `customer_id@amendment_event` (2)
- `event_date@amendment_event` (2)
From billing data alone Acme can compute 34 metrics, including Net Revenue Retention (NRR) and every MRR movement.
The partially covered list names the exact field and dataset each remaining metric is waiting for. The ranked list at the end shows
which single mapping would move the most metrics to fully covered.
Your Adapter as an Apache Ossie Mapping
The adapter is the on-ramp. The interchange form is an Apache Ossie mapping: your warehouse
as a semantic model (your tables as datasets, your columns as fields, a transform as a calculated field) and a mapping of
the GASP ontology onto it. validate_mapping converts an adapter into that pair on load and
checks the result against the Ossie schemas and the GASP ontology before scoring coverage, so an adapter you validate today
is already an Ossie mapping. A company that keeps its own Ossie model can pass the mapping and the model instead of an adapter,
to the same tool and to generate_query.
The example adapter above as the start of its mapping document: 4 concept mappings over a model of 4 datasets and 46 fields, generated when the site is built. # Generated from the GASP Standard (https://koinedata.com) by `npm run derive` in gasp-wiki-mcp. Do not edit; edit /standard/ and re-run.
version: 0.2.0.dev0
name: gasp_mapping_acme_saas
description: Maps the GASP ontology onto Acme SaaS semantic model.
ontology_ref:
name: GASP
iri: https://gaspwiki.com/downloads/ossie/gasp.ontology.yaml
semantic_model_ref:
name: Acme SaaS semantic model
iri: ./acme_saas.semantic_model.yaml
concept_mappings:
- concept: RevenueEvent
object_mappings:
- referent_mappings:
- relationship: event_id
expression: revenue_event.event_id
link_mappings:
- object_mapping:
referent_mappings:
- relationship: event_id
expression: revenue_event.event_id
children:
- object_mapping:
concept: ChangeNature
expression: revenue_event.change_nature
relationship: change_nature
- object_mapping:
concept: Currency
# ... 218 more lines And the start of the semantic model it maps onto: # Generated from the GASP Standard (https://koinedata.com) by `npm run derive` in gasp-wiki-mcp. Do not edit; edit /standard/ and re-run.
version: 0.2.0.dev0
name: Acme SaaS semantic model
description: Acme SaaS's warehouse as the GASP datasets it carries, synthesised from a GASP adapter (v1.1).
datasets:
- name: revenue_event
source: billing.mrr_movements
primary_key:
- event_id
description: Revenue event (GASP dataset revenue_event)
fields:
- name: change_nature
expression:
sql: change_nature
datatype: String
description: permanent, temporary or scheduled_reversal (Adjusted NRR/GRR)
- name: currency
expression:
sql: currency
datatype: String
description: ISO 4217 currency code
- name: customer_id
# ... 249 more lines compile_model then writes the GASP metrics into that model. Every formula entry the
mapping covers becomes a gasp_ metric: a direct expression over your datasets and fields where the Ossie
expression language can state it, else a staged per-period dataset whose source is the compiled SQL over your tables, in the
dialect your adapter's warehouse names. The example adapter compiles 47 metrics
(17 direct, 30 staged) in snowflake and lists the
316 entries it cannot reach with the fields they need. Recompiling replaces the earlier additions and
leaves the rest of the model as it was.
MRR compiled directly and NRR staged, with the start of NRR's staged dataset (its source is the compiled query over the example tables): # Generated from the GASP Standard (https://koinedata.com) by `npm run derive` in gasp-wiki-mcp. Do not edit; edit /standard/ and re-run.
metrics:
- name: gasp_mrr
expression:
sql: COALESCE(SUM(subscription.mrr) FILTER (WHERE subscription.subscription_status = 'active'), 0)
description: MRR (Monthly Recurring Revenue). The total predictable revenue from active subscriptions, normalized to a monthly value.
datatype: Decimal
ai_context:
synonyms:
- MRR
instructions: "Canonical definition: https://koinedata.com/v1/metrics/core/mrr-monthly-recurring-revenue. Reads the subscription snapshot: select one snapshot date (the period end) with the time dimension, since summing snapshot rows across dates double counts."
custom_extensions:
- vendor_name: GASP
data: "{\"gasp_id\":\"mrr\",\"gasp_version\":\"1\",\"unit\":\"currency\",\"formula\":\"sum(field.mrr where field.subscription_status = 'active') at period_end\"}"
- name: gasp_nrr
expression:
sql: SUM(gasp_staged_gasp_nrr.value)
description: Net Revenue Retention (NRR). The percentage of revenue retained from existing customers over a period, including expansion, contraction and churn. Also known as Net Dollar Retention (NDR).
datatype: Decimal
ai_context:
synonyms:
- NRR
- NDR
instructions: "Canonical definition: https://koinedata.com/v1/metrics/core/net-revenue-retention-nrr. Staged (cohort): gasp_staged_gasp_nrr holds one row per reporting period, so filter its period_start to one period. gasp.period_spine is a table of (period_start, period_end) rows the adopter provides."
custom_extensions:
- vendor_name: GASP
data: '{"gasp_id":"nrr","gasp_version":"1","unit":"percent","formula":"(metric.mrr at period_start + metric.expansion_mrr - metric.contraction_mrr - metric.churned_mrr) / metric.mrr at period_start over cohort(field.customer_id, period_start)","staged":true}'
datasets:
- name: gasp_staged_gasp_nrr
source: SELECT p.period_start, p.period_end, ((CAST((((((SELECT COALESCE(SUM((mrr_cents / 100)), 0) FROM billing.subscriptions WHERE snapshot_date = ...
description: "Staged dataset for gasp_nrr: one row per reporting period holding the metric value, computed by the GASP compiled query (needs cohort, at period_start)."
primary_key:
- period_start
fields:
- name: period_start
expression:
sql: period_start
dimension:
is_time: true
datatype: Date
description: First day of the reporting period
- name: period_end
expression:
sql: period_end
# ... 11 more lines score_drift then runs your own metrics against the canonical ones. Each metric in your
semantic model is matched to a GASP metric by exact name, by the synonyms in its ai_context or by a pair you pass;
near names are listed for review and never scored on a guess. The canonical metric is compiled into your names, as above, and
both run on the GASP synthetic SaaS dataset for six monthly periods in an in-process Postgres. Where the numbers differ you get
both values per period and the likely cause read from the canonical formula. The example company's 8 metrics
score 2 agreeing, 4 differing, 1 that cannot run on the dataset
and 1 to review.
The drift report for the example company's metrics, generated when the site is built: # GASP drift: Acme SaaS
**Matched:** 7 of 8 metrics (1 by name, 5 by synonym, 1 by your matches); 1 to review.
**Drift:** 4 differ, 2 agree, 1 could not run.
**Bridges:** 0 scored (0 differ), 1 with one form matched.
## Differs (4)
### customer_count → Total Customers
Largest difference in Apr 2026: yours 19, canonical 15 (26.7%). Differs in 6 of 6 periods.
Likely causes:
- canonical keeps customer_status in ('active'); yours applies no filter on it
- canonical aggregate `COUNT(DISTINCT customer.customer_id) FILTER (WHERE customer.customer_status = 'active')` = 15 in Apr 2026 has no equal among yours (yours: `COUNT(DISTINCT customer.customer_id)` = 19)
Yours: `COUNT(DISTINCT customer.customer_id)`
Canonical: `COUNT(DISTINCT customer.customer_id) FILTER (WHERE customer.customer_status = 'active')`
| Period | Yours | Canonical |
|---|---|---|
| Jan 2026 | 15 | 13 ≠ |
| Feb 2026 | 15 | 13 ≠ |
| Mar 2026 | 17 | 15 ≠ |
| Apr 2026 | 19 | 15 ≠ |
| May 2026 | 20 | 16 ≠ |
| Jun 2026 | 21 | 18 ≠ |
### lost_mrr → Churned MRR
Largest difference in Jan 2026: yours 500, canonical 0 (100.0%). Differs in 4 of 6 periods.
Likely causes:
- canonical keeps event_type in ('churn'); yours keeps revenue_event.event_type in ('churn', 'contraction')
- canonical aggregate `COALESCE(SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type = 'churn'), 0)` = 0 in Jan 2026 has no equal among yours (yours: `SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type IN ('churn', 'contraction'))` = -500)
Yours: `-SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type IN ('churn', 'contraction'))`
Canonical: `(-COALESCE(SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type = 'churn'), 0))`
| Period | Yours | Canonical |
|---|---|---|
| Jan 2026 | 500 | 0 ≠ |
| Feb 2026 | 500 | 500 |
| Mar 2026 | 400 | 0 ≠ |
| Apr 2026 | 800 | 800 |
| May 2026 | 200 | 0 ≠ |
| Jun 2026 | 900 | 600 ≠ |
### cancelled_mrr → Churned MRR
Largest difference in Jan 2026: yours null, canonical 0 (one side is null). Differs in 3 of 6 periods.
Likely causes:
- yours is null in 3 of 6 periods where canonical has a value: a sum over no matching rows is NULL in SQL, and GASP compiles sum() as COALESCE(SUM(...), 0), so a period with no event of a type the formula sums gives 0; in the 3 periods where both sides have a value they agree
- canonical aggregate `COALESCE(SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type = 'churn'), 0)` = 0 in Jan 2026 has no equal among yours (yours: `SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type = 'churn')` = null)
Yours: `-SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type = 'churn')`
Canonical: `(-COALESCE(SUM(revenue_event.mrr_change) FILTER (WHERE revenue_event.event_type = 'churn'), 0))`
| Period | Yours | Canonical |
|---|---|---|
| Jan 2026 | null | 0 ≠ |
| Feb 2026 | 500 | 500 |
| Mar 2026 | null | 0 ≠ |
| Apr 2026 | 800 | 800 |
| May 2026 | null | 0 ≠ |
| Jun 2026 | 600 | 600 |
### acme_nrr → Net Revenue Retention (NRR)
# ... 29 more lines What You Can Do With an Adapter
Metric Coverage Audit
See which of the 313 GASP metrics your warehouse can support and which fields to map next to cover the rest.
AI-Assisted Analysis
An LLM with the GASP MCP server and your adapter can answer "why did NRR drop?" by calling infer_drivers to rank upstream candidates,
then generate_query to compile the SQL for each candidate against your own tables.
SaaS Digital Twin Foundation
In a SaaS digital twin the GASP knowledge graph holds the business model, the adapter ties that model to the tables
you actually run on and the MCP server lets AI agents read both.
Revenue Quality Analysis
Map the optional change_nature field from your Commercial Event Ledger (CEL) to separate permanent revenue movements from temporary ones.
Adjusted NRR and Adjusted Gross Revenue Retention (GRR) read it to tell true contraction, which is a risk signal, from a reversal you already expected, which is neutral.
See the CEL change_nature spec.
Scoped Access for Agents
GASP recommends a sensitivity tier and access scope for every metric.
With those and your adapter's field ownership you have what you need to write row-level security policies in Snowflake, BigQuery or Postgres that give each agent and user only the metrics in their scope.
See the governance framework.
Downloads
Quick Start
# 1. Download the template
curl -O https://koinedata.com/downloads/gasp-adapter-template.json
# 2. Open it and fill in your warehouseRef values
# (tip: start with Billing fields, they cover the revenue and retention metrics)
# 3. Validate via the MCP server
# In Claude Code with the GASP MCP installed:
# > Use validate_adapter with the contents of my adapter file