Measure customer lifetime value
well_measure_customer_ltvMeasure the revenue lifetime value (LTV) of every customer the workspace invoiced over a window of whole months, with the portfolio churn and lifespan it rests on. It draws nothing.
Use it to rank customers by lifetime value, or to state what a customer is worth over its life. Do not use it for revenue in a period (well_sum_invoices), for recurring revenue (MRR), or for what customers still owe (receivables aging). Do not page well_query_records to rebuild this figure: this read covers every invoice in the window, unless partial is true (a month with very many customers can be cut short; see below).
The rule is fixed, and rule echoes it on every call. LTV = average order value x monthly purchase frequency x lifespan in months. It is a REVENUE LTV: no margin or cost is applied, so say "revenue lifetime value" and never present it as profit.
Revenue: the billing documents the workspace ISSUED and did not cancel (a draft is not issued, so it is left out), net of tax (
items_total), credit notes netted. Payment status plays no part.Per customer and currency:
aov=net_revenue/invoice_count;monthly_frequency=invoice_count/active_span_months, where the span runs from the customer's first invoice month to the last month of the window, for every customer including a churned one.net_revenueis what the customer was billed over the window: give it beside the LTV.A churned customer's LTV: A churned customer's LTV can be lower than its billed net revenue, because its frequency counts the months after it churned, and it moves with the window end. Do not present it as what the customer was worth: net_revenue is what it was billed. State that limit when you name a churned customer's LTV (
churned: true), and name the window end it depends on.Churned: no invoice for 2 x the customer's usual interval (the median gap between its invoice months), and at least 3 months. A customer invoiced in one month only takes the 3-month floor.
Portfolio:
monthly_churn= churned customers /observed_customer_months, the months customers were at risk of churning (a customer counts from its first invoice month until it churns, or until the window ends if it has not churned; a churned customer adds no month after its churn);lifespan_months= 1 /monthly_churn, capped at 60.cap_applied: truemeans the lifespan IS the cap (zero churn, or a churn so low its inverse passes the cap): state that the cap applied, and give the churned count beside it.A window with no invoiced customer gives
rows: []and a nullmonthly_churnandlifespan_months. Say the window holds no invoiced customers. State no LTV, lifespan or churn for it, and do not call it zero churn.A customer flagged
younghas an active span under 12 months: its frequency is normalised per month over that short span. Say so when you name one.
The window is whole, COMPLETE months. from and to are both the first day of a month, as YYYY-MM-01; from is inclusive and to is EXCLUSIVE. The window may not reach the running month, so to is at most the first day of the current month, and it covers at most 60 months. Choose the window yourself and say which one you read.
Amounts are never converted. Each row is one customer in ONE currency, and currencies summarises each currency apart. Never add rows of two currencies without converting them first at a rate you can state. A customer billed in two currencies has two rows; converted, they add up.
counterparty_alias_sets folds company rows that are one customer. Pass the sets the own-company step confirmed, as arrays of company ids; each set folds into its first id, and alias_company_ids lists what was folded. Without it nothing is folded.
The workspace's own company should be set. Without it Well cannot tell what the workspace issued: the call still returns, but party_scope.side_unknown is true, no side filter was applied and the ranking covers invoices on both sides. Say the side is unknown, never that these are the customers' figures; resolve the own company, then call again for a true ranking.
What the figure cannot reach, stated beside it:
unattributed_count: billing documents Well could place on neither side. They may belong to a customer; state the count when it is above zero.nullmeans the count failed, which is not0.excluded_draft_count: drafts in the window. They are in no row, and a connector or document import can keep a real invoice at draft, so state the count when it is above zero.nullmeans the count failed.excluded_no_customer: issued documents with no identified customer. They are in no row.excluded_malformed: issued documents with no readable net amount or no currency.nullmeans the count failed.credit_note_only_count: customer and currency pairs with credit notes and no invoice in the window.corrected_or_consolidated_count: corrected or consolidated invoices among the rows, which may count billing twice with the invoices they replace.omitted_row_count: rows past the first 100 of a currency. They still count incurrenciesandportfolio.partial: truemeans a monthly read was cut short, so some customers' invoices are missing. That can move a customer's churn, the lifespan and every LTV in either direction: the figures are not bounds. Name the truncation in the reply and do not present any figure as a minimum or a maximum.
When the token authorizes one workspace, call this directly — no workspace call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | EXCLUSIVE end of the history: the first day of the month after the last complete month you read, YYYY-MM-01. At most the first day of the current month. | |
| from | Yes | Inclusive start of the history: the first day of a month, YYYY-MM-01. | |
| workspace_id | No | Target workspace. Omit when the token authorizes one workspace. Required when it authorizes several: this read reports one workspace's own figures and will not choose which. | |
| conversation_id | No | The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation. | |
| workspace_scope | No | Restrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace. | |
| counterparty_alias_sets | No | Sets of company ids that are one customer, as the own-company step confirmed them. Each set folds into its first id. Omit when there are none. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| rows | Yes | ||
| rule | Yes | ||
| error | No | ||
| window | Yes | ||
| partial | Yes | ||
| success | Yes | ||
| portfolio | Yes | ||
| currencies | Yes | ||
| party_scope | Yes | What the receivable side resolved to. `side_unknown: true` means the workspace resolved no own company, so NO side filter was applied (`applied: false`) and the ranking covers invoices on both sides — say the side is unknown, never that these are the customers' figures. `null` when no read reported a scope resolution. | |
| conversation_id | No | The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation. | |
| newly_connected | No | Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: "<name> is connected.". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later. | |
| omitted_row_count | Yes | ||
| excluded_malformed | Yes | ||
| resolved_workspace | No | The workspace that answered, when the caller named none and the token authorizes several. | |
| unattributed_count | Yes | ||
| conversation_id_note | No | Present only when the server opened a fresh lane, stating that no choice recorded earlier was read. | |
| excluded_draft_count | Yes | ||
| excluded_no_customer | Yes | ||
| conversation_id_source | No | Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened. | |
| credit_note_only_count | Yes | ||
| corrected_or_consolidated_count | Yes |