vendor-metricsbreakdown.mdA 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.
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.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.
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:
/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.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./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.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._done/metric-dictionary-and-claim-surface.mdNothing 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.
The dictionary becomes data, and the claim chrome becomes the only route a figure takes to a screen.
No metric family is wired up in this stub. It lands the surface and the ten definition records.
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.breakdown.md → Retired).
The registry stays read-only and there is no write path to build on.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.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.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.mdA 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.
M5 on the claim surface, as arithmetic a vendor can repeat.
packages/services/src/db/services/vendor/**, the vendor health and risk routes.Recorded by the intake-easy-features session; these rulings bind Define.
_done/vendor-dashboard-reconciliation.mdThe 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.
Bring every existing vendor-facing figure onto the dictionary, and delete what cannot come.
runs/vendor-metrics/02_design/output/stub-reconciliation.md):/dashboards/vendor and /dashboards/vendor/metrics. That comparison
is the fastest way to scope this stub.apps/web vendor dashboard routes, packages/services/src/db/services/vendor/**,
apps/demo/components/dashboard/vendor/** if the demo is kept in step.Recorded by the intake-easy-features session; these rulings bind Define.
_done/vendor-delivery-and-service-quality.md"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.
M3 and M6 on the claim surface.
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.packages/services/src/db/services/vendor/**, engagement/milestone records, the vendor
delivery route.Recorded by the intake-easy-features session; these rulings bind Define.
_done/vendor-funnel-and-activation.mdThe 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.
M1 and M2 on the claim surface, built as a cohort followed forward.
packages/services/src/db/services/vendor/**, lead status history, the vendor dashboard
funnel route._done/vendor-money-states-and-active-service-revenue.mdMost 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.
M8, plus the money vocabulary the whole dashboard then uses.
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.packages/services/src/db/services/{vendor,invoice,quote}/**, the vendor revenue route._done/vendor-retention-by-speed-and-region.mdRetention 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.
M7 on the claim surface.
packages/services/src/db/services/vendor/**, customer billing country, the vendor
retention route._done/vendor-revenue-at-risk.mdThe 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.
M10 on the claim surface: the value of in-flight work carrying at least one risk flag.
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.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.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.mdA 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.
M4 and M9 on the claim surface.
packages/services/src/db/services/{vendor,csat}/**, the vendor CSAT and customers
routes.Recorded by the intake-easy-features session; these rulings bind Define.