> For the complete documentation index, see [llms.txt](https://docs.kula.digital/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kula.digital/whats-in-your-data/data.md).

# What's actually in your data

Every studio's data arrives from a different system — Mindbody, Wix, GymMaster, Stripe, ClassPass — and **no two of them expose the same things**. One gives us door swipes, one gives us none. One gives us every recurring charge, one gives us only the original purchase. One tells us which member attended, one gives us only a name typed on a roster.

Kula Intelligence normalises all of them into a **single canonical model** so a question like "who's at risk this month?" works regardless of which system a studio runs on. But normalising the *shape* can't invent the *substance*. GymMaster has no API for financial data, so no amount of mapping produces revenue — and its door access arrives only by webhook, so nothing exists before the day we connected.

These pages are the honest map of that: one per source system, saying exactly which canonical tables it fills, at what grain, over what history — and, just as importantly, **which tables it leaves empty**.

## Who these pages are for

**AI assistants connected to a studio.** Read the page for whichever sources the studio has connected *before* reasoning about coverage. It will stop you assuming a table has data it can't have, and stop you telling an operator their data is "missing" when the vendor never exposed it in the first place.

Humans are welcome too — engineers maintaining an ingestor, and operators who want to know why one number exists and another doesn't.

## The pages

| Source             | Role in a studio                                | Map                                             |
| ------------------ | ----------------------------------------------- | ----------------------------------------------- |
| **GymMaster**      | Booking + membership system of record           | [GymMaster](/whats-in-your-data/gymmaster.md)   |
| **Mindbody (MBO)** | Booking + membership + POS system of record     | [Mindbody](/whats-in-your-data/mindbody.md)     |
| **Wix**            | Booking + pricing plans + payments (all-in-one) | [Wix](/whats-in-your-data/wix.md)               |
| **Stripe**         | Payments + subscriptions                        | [Stripe](/whats-in-your-data/stripe.md)         |
| **ClassPass**      | Aggregator revenue (CSV export only)            | [ClassPass](/whats-in-your-data/classpass.md)   |
| **Xero**           | General ledger / accounting                     | [Xero](/whats-in-your-data/xero.md)             |
| **QuickBooks**     | General ledger / accounting                     | [QuickBooks](/whats-in-your-data/quickbooks.md) |

One more map covers something that isn't a source at all:

| Layer                      | Role in a studio                                                                 | Map                                                |
| -------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------- |
| **The relationship graph** | Derived: who is connected to whom, how strongly, and who is structurally exposed | [Relationship graph](/whats-in-your-data/graph.md) |

It is built from the canonical tables the connectors above already fill, so it inherits every one of their gaps — and adds its own. Read it before answering anything about coaches, peer community, resilience, connection quadrants or the attention queue.

Meta, Google Analytics 4 and generic CSV are connected sources too; their ontology maps are the next to be written. Until then, treat their coverage as undocumented rather than assumed — check `get_semantic_catalogue` and the `marketing.*` tables directly.

## Start here, every time

Two calls, in this order, before answering anything about coverage, revenue, attendance, door access or membership history:

1. **`get_system_status`** — which sources this studio has connected, and whether each is current, stale or failing.
2. **`get_doc('data/<source>')`** — the map for each connected source. `data/gymmaster`, `data/mindbody`, `data/wix`, `data/stripe`, `data/classpass`, `data/xero`, `data/quickbooks`. Add `data/graph` for any question about coaches, peer community, resilience, quadrants or the attention queue.

`get_semantic_catalogue` also carries a condensed version of each map's key facts under `source_system_gotchas`, with a pointer back to the full page. The maps are always the fuller answer.

## Prefer the purpose-built tool

Every map ends with a **Recipes** section naming the fastest route to each common question for that source. The general rule holds everywhere: the tools encode rules that ad-hoc SQL gets wrong — pause-awareness, the guarded views, studio-local timezone bucketing, cancelled-session exclusions, identity de-duplication.

| Question                            | Tool                                                                                                   |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Who's lapsing / at risk             | `list_at_risk_members` (**not** `list_quadrant_members` — quadrants are graph enrichment, not recency) |
| A name or email → canonical id      | `entity_lookup`                                                                                        |
| One member's relationships and risk | `get_member_context`                                                                                   |
| One member's money                  | `get_member_payments`                                                                                  |
| Schedule performance                | `get_class_utilisation`, then `get_time_slot_detail`                                                   |
| One instructor                      | `get_teacher_performance`                                                                              |
| Cohort survival                     | `get_retention_curve`                                                                                  |
| Acquisition cost                    | `get_cac_by_cohort`                                                                                    |
| Is the data current                 | `get_system_status`                                                                                    |
| Anything else                       | `get_semantic_catalogue` → `list_tables` / `get_table_schema` → `execute_query`                        |

When you do write SQL, prefer the `*_guarded` views — they mask restricted columns by the caller's scope and apply source-specific dedup filters that the base tables don't.

And count distinct humans with **`people.member_distinct`**, never `people.member`. The latter is one row *per source system*, so any studio with more than one connector over-counts.

## How to read one of these pages

Each map has the same six sections, in the same order, plus **Recipes** at the end.

**1. What this system is.** Its role, and whether it is the *system of record* for a studio (bookings and memberships live there) or a *satellite* (it knows about money, or marketing, but not the studio's day-to-day).

**2. Coverage at a glance.** A table: vendor entity → canonical table → grain → how far back history goes → how fresh it stays. If a row isn't in that table, we don't have it.

**3. What we cannot get.** The blunt list. Each item says *why* — vendor has no API, endpoint is behind a plan we don't have, the data is destroyed at the source, or it's simply not built yet. **"Not built yet" and "not possible" are very different answers to an operator**, so the list keeps them apart.

**4. Identity and join keys.** How a member/class/payment in this system is identified, and how that id joins to the canonical tables. This is where most wrong answers come from — for example, GymMaster's live attendance feed carries no member id at all.

**5. Counting traps.** Places where the obvious query gives a wrong number: double counts, splits, backfill twins, revenue that looks like a single sale but is 59 charges.

**6. Questions this source can and can't answer.** Concrete phrasing. If you're about to tell an operator "you have no X", check here first — the honest answer is usually "your booking system doesn't expose X" rather than "your data is incomplete".

## The canonical model everything lands in

Whatever the source, data ends up in the same per-studio Postgres schemas. The shared vocabulary:

| Schema       | Holds                                  | Key tables                                                     |
| ------------ | -------------------------------------- | -------------------------------------------------------------- |
| `people`     | Humans and places                      | `member`, `staff`, `location`, `identity_link`                 |
| `commerce`   | Money and what was sold                | `sale`, `payment`, `refund`, `plan`, `product`                 |
| `bookings`   | What happened in the studio            | `class_session`, `attendance`, `appointment`, `facility_entry` |
| `accounting` | The general ledger                     | `account`, `journal`, `entry`                                  |
| `marketing`  | Spend, web, social                     | `campaign`, `spend_daily`, `web_traffic_daily`                 |
| `graph`      | Derived relationships and risk signals | `node`, `edge`, `risk_signal`                                  |
| `ingest`     | The verbatim vendor payloads           | `raw_record`                                                   |

Two distinctions that matter constantly:

* **Sale vs payment.** `commerce.sale` is *what was sold* (the invoice line). `commerce.payment` is *money that moved* (the charge). They are separate axes, joined through `commerce.sale_payment`. A subscription produces one sale per billing period and one payment per successful charge — and either can exist without the other (an unpaid invoice; a charge we can't attribute).
* **Refunds are their own rows.** `commerce.refund` and `commerce.payment_refund`, never negative sales. Net revenue is `SUM(sale.total) − SUM(refund.amount)`.

## Every row carries its lineage

Four columns appear on essentially every ingested row, and they are how you reason about coverage at query time rather than trusting these docs blindly:

| Column               | Means                                                                                                                           |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `source`             | Reverse-DNS system id — `com.gymmaster`, `com.mindbody`, `com.wix`, `com.stripe`, `com.classpass`, `com.xero`, `com.quickbooks` |
| `source_external_id` | The vendor's own id for this thing                                                                                              |
| `source_modified_at` | The vendor's last-modified stamp — freshness, and the last-write-wins guard                                                     |
| `source_is_backfill` | True for rows loaded from a historical import rather than the live feed                                                         |

```sql
-- What sources actually populate this studio, and how fresh is each?
SELECT source, count(*), max(source_modified_at) AS freshest
FROM commerce.sale GROUP BY 1 ORDER BY 2 DESC;
```

Run something like that before asserting a source is missing. A studio may have connected a source last week and only have a week of it.

## How the systems link to each other

A studio running Mindbody for bookings and Stripe for payments has **two `people.member` rows for the same human** — one per source. That is deliberate: each ingestor owns its own rows and never guesses about another's.

The stitching happens in three places:

**`people.identity_link`** maps several source-side members onto one `canonical_member_id`, resolved by email match (high confidence), phone match (medium), or an operator confirming it manually. Ask "all activity for this human" through the canonical id, not through one source's member row.

**`commerce.payment_attribution`** is the read view that does this for money: it resolves the payer to the canonical member and stitches each payment to the sale it funded. Use it instead of grouping `commerce.payment` by `payer_member_id` — gateways mint a fresh customer id per checkout, so the raw grouping over-counts payers and under-counts per-person spend.

**The graph layer** (`graph.node` / `graph.edge`) is enrichment on top of that — affinity, staff concentration, risk signals. It is not a substitute for the canonical tables, and it is rebuilt from them.

One consequence worth internalising: **a studio with a booking system and a separate payment system will not have perfect attribution.** Some payments resolve to a member, some don't. `commerce.payment_attribution.member_id` being NULL is a normal state, not a bug — it means the payer wasn't matchable to a known member.

## When a source has a gap

Say so plainly, and say whose gap it is. The three honest answers, in descending order of usefulness to an operator:

1. **"Your booking system doesn't expose that."** Nothing to fix. Offer the nearest available proxy and be explicit that it's a proxy.
2. **"That's exposed, but we haven't built it yet."** A real roadmap item.
3. **"That should be here and isn't."** A genuine data issue — check `get_system_status` for connector health before concluding this.

What to avoid is the fourth answer, the one these pages exist to prevent: silently assuming the data should be there, computing a number from what little arrived, and presenting it as complete.
