Multi-tenant product database, GA4 and customer spreadsheets
One model parameterized by tenant, with an attribution window and estimated flags
Value metrics per tenant, isolated and with no sensitive data
Portal, monthly report, extract and AI agent
What it solves
- Every customer gets the same value calculation, without a manual query per request.
- No customer sees another customer's data, or any integration credential.
- The delivered number says whether it is measured, estimated or missing, and the customer can verify it.
Business questions it answers
Ask your agent these questions once the case is live.
What you need
- Product database connected to Nekt, with the tenant id in every table or reachable by key
- A per-tenant configuration table: id, destination layer, start date and active sources
- The value rule agreed with customers: attribution window, baseline and rate
What you get
- Per-tenant tables, isolated by layer and with no sensitive columns
- A monthly value report with the closed month, comparison and alerts
- A portal or extract to the customer's database fed by the same model
- An AI agent that answers per customer without mixing data
Where to activate
Sync each tenant's layer to the portal database (Postgres, Supabase or S3) and show the value type next to the number.
One row per tenant with the closed month, comparison and alerts, which CS takes to the results meeting.
Answers CS and the customer about one tenant at a time, with the window and estimated flags already applied.
The calculation is simple; the work is parameterizing by tenant and agreeing on the value rule. After that, each new customer is one configuration row. The detailed view has the business rules, the pitfalls and the acceptance test. Or send everything straight to your agent with the Nekt MCP.
SaaS customers ask for their own data and proof of value, and each request turns into a query on the production database or a copy of the transformation with the customer's id hard-coded. Some workspaces have hundreds of near-identical copies, and a rule change has to be repeated in all of them. Meanwhile, the value metric has details that change the number: attribution window, timestamps truncated by the integration, days with no measurement and missing rates.
How it is usually done, and the rule
Each row is a mistake that already happened in practice.
Write the transformation once with the tenant id as a column and read the tenant list from a configuration table (id, display name, destination layer, start date, active sources, parameters such as window and rate). A new customer is one row in that table. Copies are only justified when the customer has its own source that doesn't exist for the others, and even then they call the same calculation.
On Nekt, permissions are granted per layer. Deliver each customer in its own layer (or its own destination) and never in a shared table filtered only at read time. The consolidated table with every tenant stays in an internal layer, visible only to the SaaS team.
Tables that have the tenant id are filtered directly. Child tables without that column (items, messages, insights) come in through the foreign key of their parent table; reference tables come in with only the rows the tenant's records use. Check that every copied table has a path to the tenant.
Keep a closed list of columns that never leave: client_secret, access_token, refresh_token, passwords, API keys and integration credentials. Exclude them from every delivered table, including those from standard connectors, such as the Stripe payment intent's client_secret.
Revenue attributed to a message, campaign or automation counts only orders from the same end customer placed after the send and within the agreed window (one real case uses 3 days). Each order is attributed to a single send, the most recent one before it. Amounts in cents are divided by 100 once.
Some order integrations send only the date, with time 00:00:00. For each tenant, look at the last three orders: if all have zeroed hour, minute and second, treat the field as a date and compare the window in days; otherwise, as a timestamp. Store the decision in the tenant's configuration.
If an input is missing (rate not registered, meter offline, stalled integration), the result is null with a reason column, never zero. Distinguish a data error from a deliberate decision: a customer who only contracted kWh metering has null savings in reais and no alert, not an incomplete-rate alert.
Days with no reading can be filled with the average for the same weekday in the nearest real period, but the row carries a flag (measured or projected). Never use a fixed fill value. The savings baseline is the per-weekday average over the monitoring window before the automation; if operations change (for example, the number of devices), open a new baseline regime with a cutoff date.
To prove a feature's impact, split the customer's items into with and without it based on the list the customer maintains, and compare views, cart, purchase and revenue over the same period. The match is by item id; with no id, by a name that starts with the registered name, keeping a single match, the most specific one (longest name).
When the product writes its own derived tables (prefixes such as dw_ or dwt_) alongside the application tables, use the application tables, which are the source of truth. Record in the context document the tables whose names mislead (for example, a table called credit that stores charges), filter soft deletes (deletedAt null) and drop from the report any metric whose status is never filled in at the source.
How each delivered number is flagged
Every row delivered to the customer carries the value type, so the portal and the agent can explain the number.
Definitions
Pitfalls that already happened
One transformation per customer grew to hundreds of versions; a rule fix left customers with different numbers for the same calculation.
Parameterize by tenant and migrate the copies to the single model.
When each connected source generates tables with the connector id in the name, the id changes between workspaces and when the source is reconnected; queries with a hard-coded id break.
Resolve the current prefix by listing tables before querying, and never write the id into the context document.
A full sync from the source deleted months of raw data that no longer existed in the API, and the value report lost its history.
Keep a frozen backup of the history already delivered and reprocess on top of it.
Registration spreadsheets came with decimal commas, thousands separators, currency symbols, empty cells and errors such as #DIV/0!.
Convert with a safe try and turn errors and blanks into null, never zero.
The same date field came as year-month-day, month/day/year, day/month/year and spelled-out month, depending on the tenant.
Try the formats in a fixed order, record the format per tenant and flag what didn't convert.
Weekdays numbered differently in Spark and Athena shifted the baseline to the wrong day, and movable holidays such as Easter were left out.
Use a single calendar with business days and national and movable holidays, generated once.
Acceptance test
The case is done when every item passes.
- For one tenant and one closed month, attributed revenue matches the product's own dashboard (difference below 1%) and no order is attributed to two sends.
- In each customer layer there is exactly one distinct tenant id across all tables.
- No delivered table contains columns from the forbidden list (client_secret, access_token and the like).
- The number of rows flagged as projected equals the number of days with no reading, and there is no zero savings on a row with no rate.
- Adding a test tenant takes only one configuration row and produces its report on the next run.
Agent package
Paste into Claude, ChatGPT or any client with the Nekt MCP connected.
Data delivered to the SaaS's customers - Tenant = a customer of the SaaS. The list of tenants and their parameters (destination layer, start date, attribution window, rate, date format) is in the tenant configuration table. - Every question about a customer filters a single tenant. Never mix tenants in an answer for one customer. - Each customer's layer is the permission boundary. The consolidated table is internal. - Never expose client_secret, access_token, refresh_token, passwords or API keys. - Attributed revenue: orders after the send and within the tenant's window (default 3 days), each order on the most recent send. Amounts in cents divided by 100. - A tenant with truncated order timestamps (00:00:00) compares the window by date. - A null value means no data; read the reason column. Null is not zero. - Rows with tipo_valor = projetado are estimated from the average for the same weekday. Say they are estimated. - Monthly report: the previous closed month against the month before it. - Use the application tables, not the derived ones prefixed dw_ or dwt_. Filter out records with deletedAt filled in. - If the connector generates tables with an id prefix, find the prefix by listing the tables before querying. Per-client exceptions: - <client>: attribution window of <n> days instead of 3. - <client>: contracted metering only; savings in reais stay null and raise no alert. - <client>: baseline changed on <date> due to an operational change; before and after are separate regimes.
Build the "Data delivered to the SaaS's customers" case in my Nekt workspace following the rules at https://academy.nekt.com/en/industrias/saas/dados-entregues-aos-clientes.md. 1) Find the source tables and confirm the columns the rules use. 2) Create a transformation at the stated grain applying every rule. 3) Run the acceptance test and show me the result of each item. 4) Create the case's context document in the workspace, leaving my clients' exceptions open for me to fill in.
Variations
When the connector is one account per source, each customer comes in as one more source in the same model, with the agreed canonical conversion (e.g. a sign-up event in GA4).
Instead of metrics, the filtered copy of the tenant's tables to their database, with the same key propagation and forbidden-columns list.
The same design works for per-department dashboards: one layer per area, with the attainment calculation in the pipeline and not in the agent.