Skip to Content

← All archived runs

Run: vendor-time-to-value

run.md

Run: vendor-time-to-value

  • branch: claude/compassionate-galileo-6xyq5z
  • pr: #539

00_intake/stub.md

Stub: Vendor dashboard — time-to-value + delivery velocity

  • feature-slug: vendor-time-to-value
  • epic: vendor-dashboard-data
  • personas: Vendor
  • initiative: Build the Bridge / objective: Q2 2026 Objective 1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: vendor-revenue-foundation
  • sequence: 3 of 8

Problem

The time-to-value section (time-to-value-section.tsx) shows average time-to-value, a revenue-weighted average, a distribution across bands, and a delivery velocity block (% within SLA, SLA target, BRD→delivery days, a velocity trend) — all from the mock. There is no engine that measures, per lead/project, how long from start to first value, nor how delivery timing performs against SLA. The revenue-risk feature (6) also needs the SLA-breach signal this produces.

Proposed change

  • Time-to-value engine: per delivered lead/project compute days from contract/BRD start → first value (delivery completed; fallback = delivery-completed date), over the window. Produce the average, a revenue-weighted average (weighted by the customer's service revenue from feature 2), and the distribution across bands (e.g. <14d / 14–21d / 21–30d / >30d) with count + pct.
  • Delivery velocity: avg BRD→delivery days, % within SLA against the SLA target, change vs prior window, and a velocity trend series — reusing expert-dashboard-data/sla-stage-targets timing rather than recomputing time-in-stage.
  • Wire the time-to-value section (both the TTV block and the delivery-velocity block) to these engines, and expose the per-lead TTV band so feature 4 can split retention by speed.

Acceptance criteria (rough)

  • Time-to-value avg, revenue-weighted avg and band distribution are computed from real lead/project timing over the window.
  • Delivery velocity (% within SLA, target, BRD→delivery days, trend) is computed against the shared SLA targets, not hard-coded.
  • The time-to-value section renders entirely from the engines; the per-lead TTV band is available to feature 4.

Out of scope (this feature)

  • Retention by speed (feature 4) — it consumes the TTV band this produces.
  • The revenue-at-risk SLA-breach bucket (feature 6) — it reuses the SLA timing but is its own feature.
  • Defining new SLA targets — those are owned by expert-dashboard-data/sla-stage-targets; this consumes them.

Notes for Define

  • Pin the "first value" definition and its fallback against the service-journey (delivery-completed vs go-live vs first milestone) so TTV is consistent with how delivery is modelled.
  • Confirm the SLA target source: reuse sla-stage-targets; if it hasn't landed, stub a minimal read for the BRD→delivery target rather than forking it.
  • touches: packages/services/src/server (TTV + velocity engines on the envelope), reads packages/services/src/db/models/{lead,milestone,status-history}.ts and the sla-stage-targets model, apps/web/components/dashboard/vendor/{time-to-value-section,delivery-velocity-card}.tsx.

01_define/output/spec.md

Spec: Vendor dashboard — time-to-value + delivery velocity

  • slug: vendor-time-to-value
  • personas: Vendor
  • touches: packages/services/src/db/services/vendor/index.ts (TTV + delivery-velocity + velocity-trend engine on the period window); reads packages/services/src/db/models/{lead,status-history}.ts; consumes slaConfigService.getSlaDefinitions (packages/services/src/db/services/sla); apps/web/app/(app)/vendor/dashboard/page.tsx (wire the section from the engine, drop the zero-state). Section components (apps/web/components/dashboard/vendor/{time-to-value-section,delivery-velocity-card}.tsx) already render from the view-model — no change expected.
  • complexity: standard

Problem

The vendor dashboard's service-speed section — "Speed to first value" (avg days to value, a revenue-weighted average, a < 14d / 14–21d / 21–30d / > 30d distribution) and the "Delivery performance against SLA" card (% delivered within SLA, an SLA goal line, a velocity trend) — renders entirely from the zero-state base since the foundation removed the mock (apps/web/components/dashboard/vendor/time-to-value-section.tsx). There is no engine that measures, per delivered project, how long from intake to value delivered, nor how delivery timing performs against the configured SLA. This is feature 3 of the vendor-dashboard-data epic, off the foundation spine (#528): it advances Build the Bridge → Q2 2026 Objective 1 (Establish Product-Market Fit with Vendor Partners) by making the paying vendor's delivery-speed cockpit real — speed-to-value is the metric the breakdown ties directly to retention ("delivery speed directly impacts retention"), and feature 4 (retention-by-speed) cannot split retention by speed until this engine produces a per-project time-to-value band.

Proposed change

  • Time-to-value engine (a new method on VendorService, period-scoped like getCustomerHealth): for each delivered project whose delivered transition falls within the selected window, compute the time-to-value span and roll it up:
    • Span (t0 → t1). t0 = the lead's createdAt (intake — the agreed full-funnel anchor); t1 = the timestamp the lead first entered the delivered status, read from statushistory (entityType: "lead", the earliest row whose status is delivered). Fallback when a delivered project has no delivered history row: the lead's most recent statushistory.changedAt (its delivery-completed date).
    • Average time-to-value in calendar days, the revenue-weighted average (each project weighted by its customer's windowed service revenue = sum of the customer's paid-invoice total over the window, read from the foundation envelope's paidInvoices — the breakdown's definition of service revenue, so this does not depend on feature 2 landing), and the distribution across the four bands < 14 days / 14–21 days / 21–30 days / > 30 days with count + pct (pct of delivered projects in band).
    • Change vs prior window: the signed change in average time-to-value against the same-length prior window (avgDaysChange), shown with goodWhenNegative (faster is better).
  • Delivery velocity (same engine, same delivered-in-window set):
    • % within SLA (pctWithinSLA): the share of delivered projects whose business-days span (createdAt → delivered) is within the SLA target. The target is the sum of the pre-delivery stage targetsRequest + BRD + Bidding + Delivery targetDays — read from slaConfigService.getSlaDefinitions(tenantId) (the sla-stage-targets engine, shipped #463); this consumes the shared targets rather than recomputing or forking them. If those definitions are absent for the tenant, pctWithinSLA is 0 (no target ⇒ nothing classed within SLA) rather than a fabricated figure.
    • SLA goal line (slaTarget): the adherence goal the card plots as its reference line — the target percentage of deliveries that should be within SLA (distinct from the days target above). There is no config entity for an adherence goal, so this is a single documented constant owned by this feature (see Out of scope).
    • Velocity trend (velocityTrend): a monthly series over the trailing 6 months of the % of that month's delivered projects within SLA, bucketed by the delivered month (reusing the existing 6-month bucket pattern in VendorService).
  • Expose the per-project TTV band for feature 4: the engine also returns a per-lead band map (lead id → one of the four band labels) over the window, so vendor-retention-cohorts splits retention by speed from this single definition instead of re-deriving it.
  • Wire the section: page.tsx fetches the engine for the resolved window and feeds timeToValue, deliveryVelocity and velocityTrend into VendorDashboardView (replacing the emptyVendorData slices), tenant-scoped, with a .catch zero-state on failure matching the existing section reads.

Acceptance criteria

  • Time-to-value avg (calendar days), revenue-weighted avg (weighted by the customer's windowed paid-invoice revenue) and the four-band distribution (count + pct) are computed from real per-project createdAt → delivered timing for projects delivered within the selected window, with avgDaysChange the signed change vs the prior same-length window.
  • % within SLA is the share of delivered projects whose business-days createdAt → delivered span is within the summed pre-delivery SLA target read from slaConfigService.getSlaDefinitions (not hard-coded); when no SLA definitions exist for the tenant it is 0. The velocity trend is the monthly % within SLA over the trailing 6 months.
  • The time-to-value section (both the TTV card and the delivery-velocity card) renders entirely from the engine over the active window — no zero-state placeholders and no mock — and re-queries when the date range changes.
  • The engine exposes a per-project TTV band (the four-band vocabulary) keyed by lead, available for vendor-retention-cohorts (feature 4) to consume.
  • The page stays tenant-scoped to the signed-in vendor (getTenantOrNull) and degrades to a zero state (not mock values) if the engine read fails.

Out of scope

  • Retention by speed / region (feature 4) — it consumes the per-project TTV band this produces.
  • The revenue-at-risk SLA-breach bucket (feature 6) — it reuses SLA timing but is its own feature.
  • Defining or editing SLA targets — owned by expert-dashboard-data/sla-stage-targets / admin-sla-config; this only reads them.
  • A configurable SLA adherence-goal entity — the slaTarget reference-% is a fixed documented constant this round; making it tenant-configurable is a later concern.
  • Extending the SLA stage map (sla/stage-map.ts) — this feature reads spans from statushistory and the per-stage targetDays directly; it does not depend on the current-stage deriveLeadSlaTiming path, so no stage-map change is needed.
  • Any change to apps/dashboards (stays the dummy source of truth) and the cosmetic Export button.

Open questions

  • none — the TTV span anchors (t0 = lead createdAt, t1 = delivered transition), the SLA target basis (sum of pre-delivery stage targets), and the calendar-days-for-TTV / business-days-for-SLA split were decided during Define. The adherence goal-line constant is a documented presentation default (see Out of scope).

02_build/output/notes.md

Build notes: vendor-time-to-value

  • commits: feat: vendor-time-to-value — TTV + delivery-velocity engine, wired to the dashboard

What changed

  • packages/services/src/db/services/vendor/index.ts — new getTimeToValue(tenantId, window) on VendorService, plus a private loadDeliveredSpans helper and the view-model types (VendorTimeToValue et al.). For each project whose delivered status-history transition lands in the window it computes the intake→delivered span (t0 = lead createdAt, t1 = earliest delivered transition): TTV avg (calendar days), revenue-weighted avg (weight = the customer's windowed paid-invoice revenue), the four-band distribution (count + pct), avgDaysChange vs the prior same-length window, % within SLA (business-day span ≤ the summed pre-delivery sla-stage-targets), the 6-month velocity trend, and the per-lead TTV band for feature 4.
  • packages/services/src/db/services/index.ts — re-export the new vendor TTV types from the barrel.
  • apps/web/app/(app)/vendor/dashboard/page.tsx — fetch getTimeToValue for the resolved window (tenant-scoped, with a zero-state .catch mirroring the other section reads) and feed timeToValue / deliveryVelocity / velocityTrend into the view, replacing the empty slices.

Acceptance criteria status

  • TTV avg / revenue-weighted avg / four-band distribution computed from real createdAt → delivered timing for projects delivered in the window, with avgDaysChange vs the prior window.
  • % within SLA = share whose business-days span ≤ the summed pre-delivery target from slaConfigService.getSlaDefinitions (0 when no definitions exist); velocity trend = monthly % within SLA over the trailing 6 months.
  • The section renders from the engine over the active window (no zero-state, no mock); changing the date range re-queries because the engine runs against the searchParams-resolved window.
  • The engine exposes the per-lead TTV band (bandsByLead, four-band vocabulary) for vendor-retention-cohorts (feature 4).
  • Page stays tenant-scoped (getTenantOrNull) and degrades to a zero state (not mock) on failure.

Verify result

  • mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here.

Notes for review

  • TTV uses calendar days (matching the < 14 / 14–21 / 21–30 / > 30 bands); the SLA within/over classification uses business days against the business-day SLA targets — an intentional split, documented in the spec.
  • The delivery-velocity slaTarget is the adherence goal line % the card plots (a documented constant, SLA_ADHERENCE_TARGET_PCT = 85), distinct from the days target that drives pctWithinSLA.
  • Revenue weighting reads paid-invoice revenue per customer straight from the window (the breakdown's service-revenue definition), so it does not depend on feature 2 (retention) landing first. A customer with multiple delivered projects weights each by that customer's revenue.
  • delivered is the real lead-workflow status (work_in_progress → delivered → survey_sent → completed); the engine reads spans from statushistory directly and only consumes the SLA targetDays, so it does not depend on the SLA stage-map.

03_release/output/changelog.md

Changelog: vendor-time-to-value

  • entry: apps/help/app/changelog/2026-06-24-vendor-time-to-value/page.mdx
  • personas: vendor

See real time-to-value and delivery speed on your vendor dashboard

The service-speed section of your vendor dashboard now shows real numbers instead of placeholders. You can see your average time to value, a revenue-weighted average, how your deliveries spread across speed bands, the share of deliveries meeting your SLA target, and how that's trending — all for the date range you choose.

03_release/output/investor-update.md

Vendors can now see real speed-to-value and delivery performance

Who it's for: Vendor partners What shipped: The vendor dashboard now computes real time-to-value, a revenue-weighted average, delivery speed bands, and % of deliveries within SLA per project over any date range. Why it matters: Speed-to-value is the lever for retention — vendors can now see and act on it, advancing product-market fit with vendor partners.

Dig deeper: <merged-PR URL> · <changelog entry URL>

03_release/output/release.md

Release: vendor-time-to-value

  • pr: #539 · merged: no — pending CI green + squash-merge (Ready to merge ticked)
  • CI: pending re-run after the review fix push
  • technical docs: no technical docs impact (adds a VendorService method; no new app/route/env/architecture)
  • business docs: no business docs impact (the vendor dashboard and its service-speed section are already the documented capability; this wires one section to real data — feature-role-matrix/vendors does not enumerate per-datapoint metrics, consistent with the sibling features)
  • release notes: both — changelog entry (apps/help/app/changelog/2026-06-24-vendor-time-to-value) + investor draft in this PR
  • deploy: pending (poll after merge)
  • sent: pending green deploy

Review summary

Ran /code-review medium (standard complexity). Merge/wiring: clean — Promise.all order matches the destructured tuple, no duplicate data keys, all referenced retention.*/ttv.* fields exist, barrel re-exports correct.

  • Prior-window boundary overlap (both bounds inclusive at currentStart) — resolved on branch: the prior window now ends at currentStart − 1ms so a delivery on the exact boundary isn't double-counted (matches the sibling window methods).
  • Revenue-weighted average weights each delivered project by its customer's windowed revenue (a customer with N deliveries contributes its revenue N times) — accepted: this is the spec's wording ("each project weighted by its customer's service revenue") and is documented in the build notes.
  • Partial SLA config sums only the configured pre-delivery stage targets (smaller budget if some stages are unset) — accepted: the spec defines the target as the summed pre-delivery targetDays from getSlaDefinitions; null/0 only when none are configured. No silent fabrication.

Acceptance check (vs spec)

  • TTV avg / revenue-weighted avg / four-band distribution from real createdAt → delivered timing, with avgDaysChange vs the prior window — verified in getTimeToValue.
  • % within SLA = share within the summed pre-delivery target from slaConfigService.getSlaDefinitions (0 when none); velocity trend = monthly % within SLA over trailing 6 months — verified.
  • Section renders from the engine over the active window (no zero-state, no mock); re-queries on date-range change via the searchParams window — verified in page.tsx.
  • Per-lead TTV band exposed (bandsByLead) for feature 4 — verified on the return type.
  • Page stays tenant-scoped and degrades to a zero state on failure — verified (.catchEMPTY_TIME_TO_VALUE).