Skip to Content

← All archived intake epics

Intake: vendor-metrics

breakdown.md

Breakdown: Vendor dashboard metrics

What I understood

A vendor's dashboard is the evidence for their subscription, so every number on it is a claim the business makes about that vendor's money and customers. Today no number has a single written definition — what it counts, over what period, about which population, what it leaves out — so two figures can describe the same thing and disagree, and nobody can answer "where does 70% come from?" in a way that survives being checked.

This scope writes those definitions down once, for ten families of numbers, and makes every figure traceable to the records that produce it. It is a trust change, not a reporting change: the work is definitions, reconciliation and drill-downs. Redesigning how the dashboard looks is explicitly out, and so is tracking changes to the definitions themselves — see Retired below.

The prototype (/dashboards/vendor/metrics) showed the shape of the work: the expensive, reusable part is the claim surface — the chrome that carries population / period / basis date / measure / as-at with every figure, and opens it to the records that sum to it. Once that exists, each metric family is a contained piece of work against it. The last piece is reconciling the vendor dashboard that already exists, because today it reports several of these same concepts with different values.

Where it sits

  • service-journey/lead-intake and .../requirements — the opportunity stages M1 and M2 count.
  • service-journey/delivery — what "Delivered" means, which M3, M4 and M6 all hang off.
  • service-journey/invoicing — the value events behind Committed, Billed and Collected (M8, M9).
  • feature-role-matrix/vendors — the vendor dashboard surface itself, and the Admin registry that reads the same definitions.
  • feature-role-matrix/leads and .../projects — the opportunity and engagement records the drill-downs open to.

Build order

  1. metric-dictionary-and-claim-surface — the dictionary as data, and the claim chrome every figure is rendered through — depends-on: none
  2. vendor-funnel-and-activation — M1 and M2 on the claim surface, with the exact reconciliation — depends-on: metric-dictionary-and-claim-surface
  3. vendor-delivery-and-service-quality — M3 and M6, delivery facts kept out of satisfaction — depends-on: metric-dictionary-and-claim-surface
  4. vendor-money-states-and-active-service-revenue — M8 and the three states of value — depends-on: metric-dictionary-and-claim-surface
  5. vendor-satisfaction-and-revenue-weighting — M4 and M9, with mandatory coverage — depends-on: metric-dictionary-and-claim-surface
  6. vendor-customer-health-and-churn-risk — M5, every component visible — depends-on: vendor-satisfaction-and-revenue-weighting
  7. vendor-retention-by-speed-and-region — M7, buckets that sum to their population — depends-on: vendor-delivery-and-service-quality
  8. vendor-revenue-at-risk — M10, the flagged slice of the order book, each engagement counted once — depends-on: vendor-money-states-and-active-service-revenue, vendor-customer-health-and-churn-risk
  9. vendor-dashboard-reconciliation — bring the vendor dashboard that already exists onto these definitions, so no concept has two values — depends-on: vendor-funnel-and-activation, vendor-delivery-and-service-quality, vendor-money-states-and-active-service-revenue, vendor-satisfaction-and-revenue-weighting, vendor-customer-health-and-churn-risk, vendor-retention-by-speed-and-region, vendor-revenue-at-risk

Parallelizable

Stub 1 is the gate. After it, stubs 2, 3, 4 and 5 are independent of each other and can run in any order. Stub 6 needs 5 (health's largest component is satisfaction). Stub 7 needs 3 (speed buckets are median delivery time). Stub 8 needs 4 and 6 (its risk flags are their outputs, and it has to sit inside their money vocabulary). Stub 9 needs every metric stub landed, because its whole job is proving no concept has two values.

Retired

admin-metric-definition-governance — was stub 8 of 10, retired 2026-08-19 by Jamie, before any run or PR existed for it. Never started; nothing to revert.

It would have built the Admin write path over the metric dictionary: dated, attributed and retained definition changes, a vendor-facing dated note announcing each one, and the explicit no-route-to- override rule. The decision was that tracking changes to dashboard definitions is not work worth doing — if change tracking is wanted, it is wanted across the ecosystem (a lead edited, a proposal made), which is a different scope from this one and belongs to the platform-wide activity trail, not to a metric registry.

Consequences, recorded so nobody rediscovers them as bugs:

  • BR-24 goes unbuilt. Definitions are not editable in the product at all. /admin/metrics stays a read-only registry, and there is no change history and no vendor announcement. Changing a definition means a code change and a deploy, which is a stricter control than BR-24 asked for but a slower one. BR-24 stands in scope.md; the approved Doc should be updated to match if it is being retired for good rather than deferred.
  • BR-25 holds by construction, not by enforcement — no role can override a reported figure because nothing anywhere writes one.
  • BR-22's demonstration labelling is dropped too, by the same decision: demos are presented from demo.sustentus.com in a presenting setting, so no in-product "demonstration data" badge is wanted. The other half of BR-22 — a demonstration running on these identical definitions — holds by construction: the dictionary is platform-wide and not tenant-scoped.
  • CSM and SDM still cannot reach the dictionary. Stub 1 shipped it at /vendor/metrics (vendor-only) and /admin/metrics (admin-only); the shared read-only route for CSM and SDM was this stub's to add. It has no home now.

Out of scope (whole scope)

  • Redesigning how the vendor dashboard looks — this is about what the numbers mean.
  • An eleventh metric family. Ten are defined — the nine in scope.md plus Revenue at Risk, which Paul asked for at Design and which stub 9 defines properly rather than inheriting the existing dashboard's overlapping-categories figure. Another is a separate conversation.
  • Revenue-retention metrics — NRR, GRR, renewal rate, expansion and churn revenue. Stub 10 removes the two the dashboard shows today because nothing here defines them; defining the family is a separate scope, and Paul has confirmed the wider KPI taxonomy behind it is out for now.
  • Metrics on any other persona's dashboard. Shared numbers must match these definitions, but reviewing those screens is not in this round.
  • Benchmarking a vendor against other vendors, or any industry average.
  • Forecasting, prediction, or projecting a part-period to a full one.
  • Exporting, scheduling or emailing reports.
  • Vendor-configurable definitions — they are the business's, set centrally, identical for everyone.
  • Restating historical periods on the new definitions.
  • Changing what the satisfaction survey asks or how it is issued — the Customer Satisfaction scope.
  • Changing pricing or how money is billed. This scope reports money; it does not move it.
  • Alerting a vendor when a number crosses a threshold.

_done/metric-dictionary-and-claim-surface.md

Stub: The metric dictionary, and the claim surface every figure is shown through

  • feature-slug: metric-dictionary-and-claim-surface
  • scope: vendor-metrics
  • personas: Vendor, Admin
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: none
  • sequence: 1 of 9

Problem

Nothing in the platform holds what a vendor-facing number means. Population, period, basis date, measure and as-at live in whoever's head wrote the query, so two figures describing the same thing can disagree and nobody can answer where a percentage came from. Every other stub in this scope needs somewhere to put that answer, and a single way to put it on the screen.

Proposed change

The dictionary becomes data, and the claim chrome becomes the only route a figure takes to a screen.

  • A metric definition record: id, name, one-line summary, question it answers, why it matters, the other KPIs it moves, which group of the grid it belongs to, shape (cohort / activity / stock), population, period, basis date, measure, deliberate exclusions, what it opens to, and the Admin-owned thresholds it depends on.
  • A claim surface that renders any figure in three levels: the number and its name; then that metric's picture with the one or two things that change how to read it; then the full definition and the records producing it, together with the arithmetic that must hold between them. Everything the scope requires is reachable without leaving the page, and none of it competes with the number a vendor came to read.
  • Two surfaces read that one record, and both ship: the dictionary page, where the whole vocabulary lives at one address; and the definition where the number is — a popover on each dashboard section and headline tile, linking into the dictionary at that metric.
  • The grid is grouped and ordered — Revenue, then Growth, then Delivery & Quality — with the group carried on the definition record, so the vendor grid and the Admin registry cannot disagree about where a metric belongs.
  • An as-at stamp and a freshness statement on every figure, sourced once per read.
  • Period selection — last 90 days rolling (default), current quarter, last 12 months — with a part-finished period labelled in progress, and day boundaries at local midnight in the vendor's business timezone (UTC, stated, where none is recorded).

No metric family is wired up in this stub. It lands the surface and the ten definition records.

Acceptance criteria (rough)

  • A metric definition exists for each of M1–M10, and it is the only place each definition lives — including the metric's name, its one-line summary and its grid group, so no surface can state a different one.
  • Any figure rendered through the claim surface reaches its population, its period and the moment it was worked out in at most one interaction, without leaving the page (BR-1).
  • Both definition surfaces read the same record: the dictionary page, and a popover on the vendor dashboard that opens the definition where the number stands and links into the dictionary at that metric.
  • The grid is grouped Revenue → Growth → Delivery & Quality and ordered within each group, off the group field on the record — the dictionary follows the dashboard's order, so a vendor meets a metric's explanation in the order they met the metric.
  • Every KPI a definition names as related resolves to another definition in the dictionary, or is explicitly marked as not yet defined. A dictionary that points at undefined vocabulary fails at the job it exists for.
  • The first screen carries the numbers and what they are, and nothing else — definitions, caveats beyond the one or two that change how a figure reads, and cross-cutting rules are a click down. Available and visible are different things, and burying the numbers under their own provenance costs the trust this scope is buying.
  • Every figure exposes a drill-down listing the records behind it, plus the arithmetic that must hold (BR-2) — the surface refuses to render a figure with no record set attached.
  • Each definition declares whether it follows a cohort forward or reports activity in the period, and that declaration is visible (BR-5).
  • A figure never displays as illustrative, indicative or smoothed; zero renders as zero with its record count (BR-3).
  • Rounding happens only at display — money in whole units, percentages whole, averages to one decimal (BR-19).
  • No figure renders without an as-at moment, and the freshness statement is visible (BR-20).
  • Period boundaries fall at local midnight in the vendor's business timezone, weeks start Monday, and a vendor with no timezone is shown as UTC with that stated.

Out of scope (this feature)

  • Any of the ten metric families themselves — stubs 2 to 8.
  • Admin editing of definitions — was stub 8, retired 2026-08-19 (see breakdown.md → Retired). The registry stays read-only and there is no write path to build on.
  • Retiring or correcting the vendor dashboard that already exists — stub 9.

Notes for Define

  • KPI names are title-cased, and this is a deliberate departure from CONVENTIONS.md § sentence case, scoped to KPI names only. Paul asked twice, explicitly. In a dictionary they are proper nouns, and a vendor meets the same string on the tile, in the dashboard popover and in the Admin registry. Every other string on both surfaces stays sentence case. Carry the exception in the spec or a later reviewer will "fix" it back.
  • The prototype's apps/demo/lib/mock/vendor-metrics.ts shows the definition record shape and the drill-down payload shape (RecordSet: columns, rows, reconciliation lines). Both survived contact with all ten families and are worth carrying over.
  • The period selector is the one thing here the prototype does not evidence. It is pinned to a single complete quarter, with no selector. The criterion stands and Build has no reference for it — and note the scope's default is 90 days rolling, which is not what the demo shows.
  • The reconciliation lines are the interesting part: they are what turn "here are some records" into "here is why this number is right". Model them as data, not prose baked into a component.
  • Tenant scoping is a hard requirement of the surface itself, not of each metric: a vendor's figures contain only that vendor's opportunities and customers, by every route including drill-downs (BR-23). Put the scoping in the query layer the surface owns, so no metric stub can forget it.
  • touches: packages/services/src/db/services/vendor/**, apps/web/app/(app)/** vendor dashboard routes, packages/ui if the claim chrome is shared, apps/demo/components/dashboard/vendor/metrics/** (demo: seed — the prototype surface is reference-level material and stays in step).

_done/vendor-customer-health-and-churn-risk.md

Stub: Customer health and churn risk, with every component visible

  • feature-slug: vendor-customer-health-and-churn-risk
  • scope: vendor-metrics
  • personas: Vendor, CSM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: vendor-satisfaction-and-revenue-weighting
  • sequence: 6 of 9

Problem

A health score that asserts a customer is Amber, without showing why, is an opinion with a decimal point. A vendor cannot act on it, cannot check it, and has no reason to believe it — which is worse than showing nothing, because it spends trust the rest of the dashboard needs.

Proposed change

M5 on the claim surface, as arithmetic a vendor can repeat.

  • The headline is the count of customers at renewal risk — the accounts to act on while there is still time. The health score is what stands behind that count, one per customer, and the score is what makes the count checkable rather than asserted.
  • A score out of 100 from four weighted components: satisfaction 40, engagement recency 25, delivery reliability 20, commercial trend 15.
  • Recency scores full marks within 30 days and none beyond 90. Commercial trend compares collected spend this period against the equivalent previous period.
  • Bands: Green 70 and above, Amber 45 to 69, Red below 45. Churn risk means Red, or Amber with collected spend down more than 15%.
  • All four component contributions are always visible, with the evidence behind each, so the score says why rather than asserting it.
  • Nobody can adjust a score. The same inputs always produce the same score.
  • A customer with no completed engagement is shown as "not yet rated" with the components that do exist — not Green by default, and not hidden.

Acceptance criteria (rough)

  • The headline is the number of customers at renewal risk, and opening it lists exactly those customers, each with the score and the component that put them there.
  • The four components, their weights and their evidence are visible on every scored customer, and they add up to the score shown beside them (BR-18).
  • The same inputs always produce the same score, and no role has any route to adjust one (BR-25).
  • A customer with no completed engagement shows as "not yet rated" with its available components, is not banded, and is not hidden.
  • A customer with a completed engagement but no satisfaction response is shown in its own explicit state rather than being scored zero on satisfaction or silently banded (BR-17).
  • Churn risk is Red, or Amber with collected spend down more than 15% on the equivalent previous period — and the trend figure that triggered it is visible.
  • Each component opens to the records behind it.
  • Changing a weight or a band is an Admin act only, and takes effect from its change date without restating earlier periods.

Out of scope (this feature)

  • Alerting a vendor when a score crosses a threshold. Churn risk is shown on the dashboard, not pushed.
  • Predicting churn, or any model-derived score. This is stated arithmetic over stated inputs.
  • Letting a vendor configure the weights or bands — they are the business's, identical for everyone.

Notes for Define

  • A component with no input is not a component worth zero. The prototype showed a customer who has never answered a survey scoring 0 of 40 on satisfaction would be banded Red purely for not responding — which is false. It instead shows them "partly rated" on three components out of four and does not band them. That treatment is a real decision the scope does not settle: it needs to be specified, not improvised in Build.
  • The scope's worked example gives Northwind 12 of 25 for recency with "last activity 47 days ago". Under the scope's own 30-day/90-day rule, 47 days scores about 18, and 12 corresponds to roughly 61 days. The prototype keeps the stated rule and uses 61 days so the worked example's total of 38 still holds. The rule is normative; the 47 is the slip.
  • Round each component once and sum the rounded components, rather than rounding the total. It is the only way the four numbers a vendor sees add up to the total beside them.
  • touches: packages/services/src/db/services/vendor/**, the vendor health and risk routes.

Answers from Jamie — interrogation 2026-08-17

Recorded by the intake-easy-features session; these rulings bind Define.

  • Components with no input: no band until complete — a customer missing any component stays unbanded ('insufficient data'); never a possibly-misleading partial score.

_done/vendor-dashboard-reconciliation.md

Stub: Reconcile the vendor dashboard that already exists onto these definitions

  • feature-slug:
  • scope: vendor-metrics
  • personas: Vendor, CSM, SDM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: vendor-funnel-and-activation, vendor-delivery-and-service-quality, vendor-money-states-and-active-service-revenue, vendor-satisfaction-and-revenue-weighting, vendor-customer-health-and-churn-risk, vendor-retention-by-speed-and-region, vendor-revenue-at-risk
  • sequence: 9 of 9

Problem

The vendor dashboard already reports funnel, retention, revenue, health, CSAT and delivery — with figures derived a different way from the definitions this scope establishes. Landing eight stubs of correct numbers beside a screen of differently-derived ones produces exactly the failure the scope exists to prevent: the same concept, two values, on the same dashboard.

Proposed change

Bring every existing vendor-facing figure onto the dictionary, and delete what cannot come.

  • Audit every number on the existing vendor dashboard and its sub-pages against the ten definitions: which concept it claims, which definition governs it, and whether its value agrees.
  • Repoint the ones that map onto a defined metric so they read from the same source as the metric surface, carrying the same population, period and as-at.
  • Remove or rename figures that describe no defined concept, rather than leaving them to be read as though they did. A number nobody can define is not a number to keep.
  • Remove unlabelled money figures and any that add or compare stocks against period totals.

Acceptance criteria (rough)

  • No concept appears twice on the vendor dashboard with two different values, under any combination of period and filter (BR-4).
  • Every number on the vendor dashboard is rendered through the claim surface, carrying its population, period and as-at (BR-1).
  • Every number on the vendor dashboard opens to the records behind it, and those records add up to it (BR-2).
  • No unlabelled money figure remains anywhere on the vendor dashboard (BR-10).
  • No screen adds a stock to a period total or compares the two (BR-11).
  • Every section on the vendor dashboard carries the definitions behind its numbers, in place, linking into the dictionary at that metric — both definition surfaces read the same record.
  • Net revenue retention and gross revenue retention are removed from the vendor dashboard. Both are revenue-retention measures this scope defines nothing to produce, and neither may remain as a number without a definition. Reinstating them is a separate scope.
  • Every money figure on the vendor dashboard uses one formatter and one reporting currency, in whole units, never abbreviated (BR-19).
  • Every figure that was removed is gone, not hidden — no dead component or unreachable route left behind.
  • A person who has never seen the dashboard can reproduce any headline figure by hand from its drill-down, and reach the same answer, in under ten minutes.

Out of scope (this feature)

  • Redesigning how the vendor dashboard looks. Numbers change, layout does not.
  • Metrics on any other persona's dashboard. Where a shared number appears elsewhere it must match these definitions, but reviewing those screens is not in this round.
  • Defining net or gross revenue retention, or any other metric family, to replace something removed. This stub deletes what it cannot govern; defining a replacement is a new scope.

Notes for Define

  • This is the stub most likely to be dropped under time pressure, and dropping it forfeits the scope's headline acceptance criterion. It is not optional cleanup; it is the point.
  • Expect deletions. The repo's rule is that superseding deletes the superseded in the same change — no old tiles left on disk "for reference".
  • The audit output — every existing figure, its claimed concept, its governing definition, its verdict — is worth writing down in the run before any code changes. It is what makes the review of this PR possible. Five entries are already known, from comparing the two surfaces during Design (full detail in runs/vendor-metrics/02_design/output/stub-reconciliation.md):
    • NRR and GRR — revenue-retention figures with their own formulas and no definition anywhere. Removed, per the criterion above.
    • "Revenue at risk" — four overlapping risk categories added together. Superseded by M10.
    • The ARR movement strip — revenue per lifecycle stage, mixing stocks and a flow in one strip, unlabelled, with the last cell showing the ARR figure rather than delivered value.
    • The time-to-value section — a section whose own concept has no definition, carrying links to two definitions that are not it. Define time to value or stop pointing at it.
    • Currency and formatting — the existing dashboard is in pounds and abbreviates to thousands; the dictionary is euros in whole units, and BR-19 rules abbreviation out. The prototype needed its own formatter because the shared one abbreviates. Settle on one for the real app.
  • The prototype deliberately left the existing vendor dashboard untouched, so the two can be compared side by side at /dashboards/vendor and /dashboards/vendor/metrics. That comparison is the fastest way to scope this stub.
  • touches: apps/web vendor dashboard routes, packages/services/src/db/services/vendor/**, apps/demo/components/dashboard/vendor/** if the demo is kept in step.

Answers from Jamie — interrogation 2026-08-17

Recorded by the intake-easy-features session; these rulings bind Define.

  • Currency/formatting: euros, whole units, one shared formatter — per the dictionary and BR-19; the pound-abbreviating presentation goes.
  • Time to value: remove the section until a real definition exists — defining it is out of scope for the reconciliation.

_done/vendor-delivery-and-service-quality.md

Stub: Revenue Delivered, and on-time delivery measured from delivery facts

  • feature-slug: vendor-delivery-and-service-quality
  • scope: vendor-metrics
  • personas: Vendor, SDM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: metric-dictionary-and-claim-surface
  • sequence: 3 of 9

Problem

"Delivered" depends on the final bill settling as well as the work finishing, so a payment delay currently reads as a delivery slowdown and nobody can tell the two apart. Separately, the dashboard promises a "service-quality breakdown" that cannot exist: a customer answers exactly one satisfaction question, so there are no sub-scores for communication, timeliness or expertise to break down. Showing one would invent detail the customer never gave.

Proposed change

M3 and M6 on the claim surface.

  • M3 is Revenue Delivered — a value, not a count. Its headline is the contracted value of the engagements that reached Delivered in the period, with the count of those same engagements shown beside it, in its caveat and in its records. One record set read two ways, never two numbers. The trend over 12 months is the same value per month.
  • Delivered is dated on the day the engagement reached Delivered, with the date the last milestone was accepted shown alongside, so a payment delay never reads as a delivery slowdown.
  • M6 is the on-time measure, and it is not called an SLA. Its headline is the share of engagements delivered on or before the agreed date. An agreed change to the delivery date resets the date measured against, and the number of engagements whose date changed is shown.
  • Behind that headline sit four counts over the same delivered engagements — delivered on time, delivered to plan, escalation-free, billed without query — which replace the "service-quality breakdown" the dashboard promises and cannot have.
  • The screen states the distinction: satisfaction is what the customer said, delivery quality is what happened. They are allowed to disagree.

Acceptance criteria (rough)

  • Delivered means every milestone complete and acknowledged by the customer, and every bill against it settled — the same definition the Customer Satisfaction scope uses to trigger a survey.
  • Revenue Delivered's headline is a money value; the count of engagements behind it is shown beside it and in its records, derived from the same record set — the two can never disagree.
  • Revenue Delivered names itself as a fourth money concept alongside Committed, Billed and Collected, and no screen adds it to any of them (BR-10).
  • The last-milestone-accepted date is shown alongside the delivered date wherever the trend is read, and work finished in one month but billed in the next counts in the later month without restating the earlier one.
  • Engagements that ended as closed lost appear in the funnel losses and never in Revenue Delivered.
  • An agreed date change resets the date measured against, and the count of changed dates is visible.
  • The on-time metric's name contains no reference to an SLA, and the screen says the measure is the agreed date.
  • The four delivery-quality measures are counts of observable delivery facts, reported beside satisfaction and never merged into a satisfaction score (BR-15).
  • No sub-score for communication, timeliness or expertise appears anywhere.
  • Opening any month or any of the four measures lists engagements that add up to exactly that figure, with agreed date, actual date, and whether the date was changed.
  • An engagement delivered but never paid appears in active service revenue as complete and awaiting payment, and not in the delivery trend.

Out of scope (this feature)

  • The satisfaction average itself — stub 5.
  • Active service revenue and money states — stub 4.
  • Changing what a milestone is, or how acceptance is recorded.

Notes for Define

  • M3's headline is value, not volume — decided, not inherited. The approved scope defines M3 as a count; Paul's mock-up put "Revenue Delivered" over it. Rather than ship a count under a money heading — the exact failure this scope exists to prevent — the metric matches its name and the count rides alongside. The consequence is that the "never added together" rule now covers four amounts, and stub 4 carries that.
  • "SLA Success Rate" was rejected as the name for M6. The platform has a real, configured SLA object with its own types and targets (apps/web/app/(app)/admin/settings/sla/, apps/web/app/(app)/admin/sla/). A vendor KPI called "SLA Success Rate" that measures the agreed date rather than any configured SLA would be one concept with two values, on the very dashboard this scope is cleaning up. The metric measures the agreed date and is named for that. Measuring the configured Completion SLA instead would be a definition change under stub 8, and is deliberately not this round's work.
  • The prototype's M6 definition record is internally inconsistent — its summary describes the on-time share, its question describes the four-measure breakdown, and its measure describes the four counts. Do not carry it forward as-is; the spec picks one headline (the on-time share) with the four counts behind it.
  • Paul answered open question 2 with "acknowledged by the Customer as so" — customer acknowledgement is part of Delivered, not just internal completion. Check what the delivery flow records today; if acknowledgement is not captured, that is a real gap this stub has to close or explicitly defer.
  • "Delivered to plan" means completed without an agreed change to scope or delivery date. The prototype only had date changes to work from; scope changes need a source of truth before this measure is honest. Do not approximate one.
  • Paul answered open question 7 "YES V1" — the four measures are the first version, not the final set. Do not build a configurable measure list on the strength of that.
  • touches: packages/services/src/db/services/vendor/**, engagement/milestone records, the vendor delivery route.

Answers from Jamie — interrogation 2026-08-17

Recorded by the intake-easy-features session; these rulings bind Define.

  • Delivered-acknowledgement gap: capture customer acknowledgement in this stub if the delivery flow doesn't record it today — the measure is honest from day one, never approximated.

_done/vendor-funnel-and-activation.md

Stub: Service Activation Rate, with the funnel behind it, reconciling exactly

  • feature-slug: vendor-funnel-and-activation
  • scope: vendor-metrics
  • personas: Vendor, CSM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: metric-dictionary-and-claim-surface
  • sequence: 2 of 9

Problem

The funnel a vendor sees today counts whatever is sitting in each stage, mixes ended opportunities into stage counts, and cannot be checked. A conversion percentage built that way can be arithmetically right and still describe nothing a vendor can act on — and when they ask where 70% came from, there is no answer.

Proposed change

M1 and M2 on the claim surface, built as a cohort followed forward.

  • Service Activation Rate is the headline; Service Leads is its denominator. M1 keeps a full definition record and a row in the Admin registry, but it is not a tile on the vendor grid — the funnel and its losses live inside M2's drill-down. A count of leads on its own says nothing about the business; the share of them that became real work does.
  • The population is every opportunity attributed to this vendor whose sourced date falls inside the period, followed to whatever end it reached.
  • Each stage reports how many of the cohort have ever reached it, with both stage-to-stage and overall conversion shown and labelled as which.
  • Losses are their own set of counts — disqualified, abandoned, gone cold, unmatched, no fit, closed lost — one reason per ended opportunity, never folded into a stage count.
  • Activation rate is the cohort that reached Activated over the cohort size, with the count still inside the 14-day activation window shown beside it.
  • Both open to the individual opportunities, and both carry the reconciliation on screen.

Acceptance criteria (rough)

  • Service Leads has a definition record and an Admin registry row but no tile on the vendor grid; the funnel stage counts and loss counts are reachable through Service Activation Rate's drill-down.
  • Cohort size = delivered + ended for a stated reason + still open, on every period and every filter (BR-6).
  • Every ended opportunity carries exactly one reason, and loss counts appear nowhere inside the stage counts (BR-7).
  • Stage counts report "ever reached", the screen says so, and each stage count is at least as large as the one after it (BR-8).
  • Disqualified opportunities remain in both the funnel and the activation denominator.
  • The count of opportunities still inside the 14-day activation window is shown beside the activation rate.
  • Test and demonstration records are excluded, and the exclusion is stated on the definition rather than assumed (BR-21).
  • Where two records for the same request were merged, the survivor keeps the earliest sourced date and the absorbed record appears in no count — not even as a loss.
  • Opening any stage count or loss count lists opportunities that add up to exactly that count (BR-2).

Out of scope (this feature)

  • Delivery, satisfaction, money, health and retention — later stubs.
  • Changing the opportunity stages themselves, or the reasons an opportunity can end.
  • The existing funnel page on the vendor dashboard — stub 10 reconciles it.

Notes for Define

  • Paul's ruling on the demotion, in his words: "Service lead is not really of any interest… first KPI really is Service Activation Rate… Activation is signal of health." The definition is not deleted — a rate needs its denominator, and the place to look when activation falls is the funnel.
  • Cohort membership is fixed by sourced date, so a stage a record reaches after the period ends still counts in that cohort. That is deliberate and needs to survive review.
  • "Ever reached" needs a durable record of stage entry, not the current stage. Check what the lead status history already gives before assuming new persistence is needed — the status workflow engine is the place to look, not a new collection.
  • An opportunity belongs to exactly one vendor for its whole life. Paul answered open question 9 with "not a feature" — moving a customer between vendors is not something the product does, so no re-attribution logic is needed and none should be built speculatively.
  • touches: packages/services/src/db/services/vendor/**, lead status history, the vendor dashboard funnel route.

_done/vendor-money-states-and-active-service-revenue.md

Stub: Money in four named states, and active service revenue as a stock

  • feature-slug: vendor-money-states-and-active-service-revenue
  • scope: vendor-metrics
  • personas: Vendor, CSM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: metric-dictionary-and-claim-surface
  • sequence: 4 of 9

Problem

Most reporting disputes are two people using one word for three different amounts. The dashboard shows unlabelled "revenue" figures that mix committed, billed and collected value, and it shows active service revenue — a snapshot of work in flight — next to period totals as though the two were comparable. That last one is the single most likely way this dashboard misleads someone.

Proposed change

M8, plus the money vocabulary the whole dashboard then uses.

  • Every money figure names which of Delivered, Committed, Billed or Collected it is. There is no unlabelled revenue anywhere, and no two of the four are ever added together or compared as though they were the same money. Delivered is stub 3's Revenue Delivered; this stub owns the vocabulary all four sit in.
  • Active service revenue is the sum of contracted value of every engagement accepted and not yet Delivered or ended, as at a moment, split by stage — accepted not started, work in progress, awaiting final acceptance, complete awaiting payment — with the splits summing exactly.
  • It is labelled a stock and is never added to, or compared against, a period total.
  • The headline is the customer's committed spend. Paul answered open question 3 "No Service FEE" — there is no platform service fee, so there is no second net figure underneath it.
  • Currency conversion is frozen at the rate on the date of the value event.

Acceptance criteria (rough)

  • Every money figure on the dashboard names which of the four states it is, and no screen adds or compares any two of them (BR-10).
  • Active service revenue is presented as a stock as at a moment, and no screen adds it to or compares it against a period total (BR-11).
  • Revenue at Risk (stub 9) reads as a labelled slice of active service revenue, never as a second amount beside it.
  • The four delivery-stage splits sum exactly to the total, and days in stage is shown per engagement (BR-18).
  • No service-fee figure, net-of-fee figure, or fee line appears anywhere on the dashboard.
  • Amounts in other currencies convert at the rate on the date of the value event, and that conversion is frozen — historical figures do not move when rates move (BR-12).
  • A credit or refund reduces Collected on the date the credit is issued and never restates the period the original payment fell in (BR-13).
  • An engagement that ends mid-delivery leaves active service revenue on the day it ends; whatever was already collected stays collected, and billed-but-unpaid value shows as outstanding rather than as revenue.
  • A bill issued, queried, cancelled and re-issued counts once — the currently valid bill only.
  • All figures exclude tax, and each opens to the engagements or value events behind it.

Out of scope (this feature)

  • Top customers by spend and revenue-weighted satisfaction — stub 5.
  • Revenue at Risk itself — stub 9. This stub establishes the vocabulary it has to sit inside.
  • Changing pricing, invoicing or how money is billed. This stub reports money; it does not move it.
  • The vendor's reporting currency being changeable — record the frozen-history rule, do not build a currency-switching flow.

Notes for Define

  • The scope says three money states; there are four. Revenue Delivered (stub 3) is a money value over the same work, so Delivered / Committed / Billed / Collected are four different amounts and the "never added together" rule covers all four. The scope's three-state wording predates that decision — state four in the spec rather than inheriting three.
  • The service fee is being removed from the platform under the service-fee-deprecation scope. This stub must not reintroduce a gross/net distinction on its strength, and Define should check those runs have merged before specifying money reads.
  • "Committed" is derivable as contracted value less what has been billed against it — the prototype did exactly that and it reconciled. Confirm the invoice records support it before committing to it as the definition.
  • Active service revenue and the period money totals answer different questions and must not share a card without the stock/flow distinction being visible. The prototype put them side by side with an explicit "never added together" line; that wording earned its place.
  • touches: packages/services/src/db/services/{vendor,invoice,quote}/**, the vendor revenue route.

_done/vendor-retention-by-speed-and-region.md

Stub: Retention by delivery speed and region, in buckets that sum

  • feature-slug: vendor-retention-by-speed-and-region
  • scope: vendor-metrics
  • personas: Vendor, SDM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: vendor-delivery-and-service-quality
  • sequence: 7 of 9

Problem

Retention is the number that answers whether delivering faster makes customers come back, but a bucketed rate with no counts is unreadable — a bucket of three customers is an anecdote and looks identical to a bucket of thirty. And customers with no recorded billing country get quietly folded somewhere, which hides both the retention truth and the data-quality problem underneath it.

Proposed change

M7 on the claim surface.

  • The population is customers with at least one engagement delivered in the previous period.
  • The measure is the share of those customers who started at least one new engagement in the current period — quarter against previous quarter, with a rolling 12-month figure alongside.
  • Speed buckets place each customer by their median delivery time in the previous period: 14 days or fewer, 15 to 30, 31 to 60, over 60.
  • Region groups the customer's billing country. A customer with no recorded country goes into an explicit "unknown region" bucket that is always shown.
  • Every bucket carries its own customer count, and both breakdowns sum to the whole population.

Acceptance criteria (rough)

  • Speed buckets sum to the whole population, and so do region buckets — same population, same returner count, both reconciling on screen (BR-18).
  • Every bucket shows the number of customers in it, however small, and nothing is suppressed or merged into an "other" bucket.
  • A customer with no recorded billing country appears in the unknown-region bucket, never silently dropped and never assigned to the vendor's own region (BR-17).
  • A customer who changes country is counted where they were at the time of the engagement; history is not restated.
  • Speed buckets use the median delivery time in the previous period, not the mean.
  • The rolling 12-month figure is shown with its own population and returner counts.
  • Opening any bucket lists its customers, retained and not retained, adding to the bucket counts exactly.
  • Where the previous period had no customers, no rate and no change is shown — a change from nothing is not a percentage.

Out of scope (this feature)

  • Benchmarking a vendor's retention against other vendors or an industry average.
  • Fixing the missing billing countries the unknown-region bucket exposes — surfacing them is the job here.
  • Forecasting retention, or projecting a part-period.

Notes for Define

  • The scope's worked example collapses the last two speed buckets into "over 30 days". The normative bucket list in §3.6 has four buckets; the prototype uses four. Keep four.
  • Both breakdowns are cuts of the same 22 customers, not two independent queries. Building them from one joint record set is what makes the two reconciliations hold; two separate aggregations will drift.
  • The unknown-region bucket is a feature, not a defect to hide: four customers with no recorded country is itself something the vendor should fix, and the bucket is how they find out.
  • touches: packages/services/src/db/services/vendor/**, customer billing country, the vendor retention route.

_done/vendor-revenue-at-risk.md

Stub: Revenue at Risk — a labelled slice of the order book, each engagement counted once

  • feature-slug: vor-revenue-at-riskend
  • scope: vendor-metrics
  • personas: Vendor, CSM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: vendor-money-states-and-active-service-revenue, vendor-customer-health-and-churn-risk
  • sequence: 8 of 9

Problem

The vendor dashboard already shows a "revenue at risk" figure, and it is built by adding four overlapping risk categories together. An engagement that is both late and unhappy is counted twice, so the total describes no set of engagements at all — and it sits beside the order book as though it were a second pot of money rather than a part of the same one. It is the clearest example on the dashboard of the failure this whole scope exists to prevent, and the vendor cannot tell.

Meanwhile the accounts genuinely at risk are already known — customer health flags them, delivery records show what is stuck — but nobody has put a number on what that risk is worth.

Proposed change

M10 on the claim surface: the value of in-flight work carrying at least one risk flag.

  • The population is exactly the active service revenue population, narrowed to engagements carrying at least one risk flag: the customer is at renewal risk, the engagement has sat over the stalled threshold in one stage, or its satisfaction is at or below the low-satisfaction threshold.
  • Each engagement is counted once, however many flags it carries.
  • It is presented as a labelled slice of active service revenue — the money is already counted there — never as a second amount beside it, and never added to it.
  • The per-flag totals are shown, together with an explicit statement that adding them is meaningless, because they overlap by design.
  • Both thresholds are Admin-owned settings on the definition, not constants in the query.

Acceptance criteria (rough)

  • Every engagement in the figure is counted exactly once, whatever combination of flags it carries, and the drill-down lists each with every flag it holds.
  • The figure is stated as a share of active service revenue, on screen, and no screen adds the two together or presents them as separate pots (BR-11).
  • The per-flag totals are shown, and the screen states that their sum is not a number that means anything.
  • The stalled-in-stage and low-satisfaction thresholds are Admin-owned settings named on the definition, changeable only through stub 8's governance (BR-24).
  • Opening the figure lists engagements whose contracted values add up to exactly it (BR-2).
  • The two things the data cannot yet support are recorded as stated exclusions on the definition, not silently approximated: delay against an agreed date, and scope reductions.
  • The existing unlabelled "revenue at risk" figure built by adding overlapping categories is gone, not hidden.

Out of scope (this feature)

  • Alerting a vendor when the figure moves. It is shown on the dashboard, not pushed.
  • Predicting which engagements will fail. The flags are stated facts over stated thresholds.
  • Recording scope reductions, or putting an agreed date on in-flight work. Both are real gaps this stub surfaces and neither is this stub's job to close.

Notes for Define

  • This is a tenth metric family, added after the scope was agreed. scope.md and breakdown.md both exclude one ("nine are defined; another is a separate conversation"). Paul asked for it in his dashboard mock-up and the prototype built it properly rather than shipping his €44,000 figure, which is the existing dashboard's four overlapping categories added together. The exclusion has been amended in breakdown.md; the spec should note the metric arrived this way.
  • The two exclusions are real findings, not caveats to soften. In-flight engagements carry no agreed date, so delay is untestable and "days in the current stage" stands in for it — a weaker test, and the definition must say so. Scope reductions are recorded nowhere, so downscope risk cannot be measured at all. Do not approximate either.
  • Why it depends on both: the risk flags are outputs of customer health (stub 6) and of the money vocabulary and ASR population (stub 4). Neither dependency is optional — building this first would mean inventing both.
  • The prototype's treatment is worth carrying: apps/demo/lib/mock/vendor-metric-claims.ts → M10, where the slice-of-ASR framing and the "adding the per-flag totals is meaningless" statement are both on screen rather than in a comment.
  • touches: packages/services/src/db/services/vendor/**, the vendor revenue and risk routes, apps/demo/components/dashboard/vendor/revenue-risk-section.tsx if the demo stays in step.

_done/vendor-satisfaction-and-revenue-weighting.md

Stub: Satisfaction average, top customers, and revenue-weighted satisfaction

  • feature-slug: vendor-satisfaction-and-revenue-weighting
  • scope: vendor-metrics
  • personas: Vendor, CSM
  • initiative: Refine the bridge / objective: Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: metric-dictionary-and-claim-surface
  • sequence: 5 of 9

Problem

A plain satisfaction average treats a €900 engagement and a €90,000 engagement identically, so it can look healthy while the customers who actually pay are the unhappy ones. Weighting by revenue answers the commercial question instead — but a weighted average is meaningless without knowing how much of the spend it covers, and today no such coverage figure exists.

Proposed change

M4 and M9 on the claim surface.

  • The satisfaction average is exactly the Customer Satisfaction scope's definition — the mean of recorded scores, one per delivered engagement, published only once three responses exist, always with the response count and response rate beside it. This scope adds nothing to it.
  • It is counted by the date the response was recorded, so a late response never restates a closed period.
  • Top customers lists value collected in the period by customer, highest first, top 10, ties broken by most recent activity.
  • Revenue-weighted satisfaction multiplies each responding customer's satisfaction by their collected spend, sums, and divides by the summed spend of those same customers.
  • The weighted figure always states the share of collected spend it covers. Customers who never responded are excluded from it — not treated as neutral — and their spend still appears in the table.

Acceptance criteria (rough)

  • The satisfaction average matches the Customer Satisfaction scope's definition exactly; where the two documents could be read as disagreeing, that scope governs (BR-14).
  • No average is published below three responses, and the screen says which records it is waiting for rather than showing zero.
  • A response arriving after the period counts in the period it arrived; the delivery period is not restated. Responses arriving after the survey closed are not accepted at all.
  • Every average shows the number of records behind it, and the weighted average also shows the share of collected spend it covers (BR-16).
  • A customer with spend but no response appears in the top-customers table and is absent from the weighted figure.
  • Where the top 10 does not cover every paying customer, the share of collected spend the table covers is stated.
  • The weighted figure and the plain average are shown together, and their difference is readable rather than hidden.

Out of scope (this feature)

  • Changing what the satisfaction survey asks, how it is issued, or who it is attributed to — that is settled in the Customer Satisfaction scope.
  • Service-quality measures — stub 3.
  • Customer health — stub 6, which consumes the per-customer satisfaction this stub establishes.

Notes for Define

  • The one thing Define must settle before Build. Paul answered open question 6 "YES — drilldown and sort": a vendor may see a per-customer satisfaction score for their own customers. The body of the scope (M4, M9, §3.7) says aggregates only, and BR-14 says the Customer Satisfaction scope governs where the two disagree. Paul's answer is the later thought and the prototype follows it, but the two documents genuinely conflict and the spec has to state which wins — including whether "drilldown" extends to the individual response and its optional comment, or stops at the customer-level score. Do not let Build discover this. The approved prototype has already gone to the individual response: CSAT's drill-down lists one row per response — engagement reference, named customer, delivered date, individual score — with the free-text comment not shown. So the decision is now whether to ratify or reverse built behaviour, not to choose in the abstract.
  • The scope's worked example states a revenue-weighted satisfaction of 4.1 covering 76% of spend. That figure cannot be produced from its own stated inputs: with the largest customer (€68,400 collected) scoring 2 out of a €205,400 responding pool, the arithmetic maximum is 4.0. The prototype's dataset reconciles to 3.7. Nothing is wrong with the definition — only with that illustrative number — but the spec should not copy 4.1 forward as though it were reproducible.
  • A customer exercising a data-deletion request keeps their contribution in every aggregate — the money was real and the work happened — while their name leaves the drill-down.
  • touches: packages/services/src/db/services/{vendor,csat}/**, the vendor CSAT and customers routes.

Answers from Jamie — interrogation 2026-08-17

Recorded by the intake-easy-features session; these rulings bind Define.

  • Drilldown: ratify the full drilldown — a vendor may drill to the individual survey response including its optional comment, as the approved prototype already shows. BR-14 conflict resolves in favour of Paul's 'YES — drilldown and sort'.