Skip to Content

← All archived runs

Run: sdm-supply-coverage

run.md

Run: sdm-supply-coverage

  • branch: claude/magical-cerf-kbafk6
  • pr: #525

00_intake/stub.md

Stub: SDM dashboard — supply coverage (quote & bid readiness table)

  • feature-slug: sdm-supply-coverage
  • epic: sdm-dashboard-data
  • personas: SDM
  • initiative: Build the Bridge / objective: Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow
  • depends-on: sdm-demand-aggregation
  • sequence: 4 of 8

Problem

The quote-and-bid readiness table (quoteAndBidReadiness in apps/web/lib/mock/sdm.ts, rendered by bid-readiness-table.tsx) is the supply side of each demand cell: per (platform, module, skillLevel, region, language) it shows brdDemand, and then classifies the matched experts into bidReadyExperts, deliveryReadyExperts and strongExperts, with availableExperts, shortfall, a readinessStatus RAG, a coverageStatus label and an overloaded flag. This classification — what makes an expert "bid-ready" vs "delivery-ready" vs "strong" for a cell, and whether the cell is covered — does not exist as a derivation. It is the core net-new SDM logic, and it also fills the coverage columns left pending by sdm-demand-aggregation.

Proposed change

  • Build the supply-coverage derivation: for each demand cell, find the experts whose taxonomy skills match the cell and classify them:
    • bid-ready — qualified + available to bid (skill match + capacity).
    • delivery-ready — bid-ready and meeting the delivery bar (SLA adherence from sla-stage-targets).
    • strong — delivery-ready and high quality (CSAT / low escalation / completion from expert-quality-metrics).
  • Compute availableExperts, shortfall (demand − available), coverageStatus (Fully covered / Partial / Not covered), the readinessStatus RAG, and overloaded (the matched experts are over capacity, from active work).
  • Wire the quote-and-bid readiness table, and back-fill the coverage columns on the demand table (status, readiness, coverage, coverageGap) from the same derivation.

Acceptance criteria (rough)

  • Each cell classifies its experts into bid-ready / delivery-ready / strong from real skills + reused SLA + quality metrics (not recomputed here).
  • availableExperts, shortfall, coverageStatus, readinessStatus and overloaded are derived per cell over the SDM's area.
  • The bid readiness table renders from the derivation; the demand table's coverage columns fill in.
  • No section reads lib/mock/sdm for supply/coverage.

Out of scope (this feature)

  • Recomputing CSAT / SLA / completion — consume expert-dashboard-data/{expert-quality-metrics,sla-stage-targets,expert-active-projects}.
  • The risk rows + outreach state and action strip (feature 5) — they derive from this coverage output.
  • The per-expert strength table (feature 6).

Notes for Define

  • Pin the classification thresholds (the SLA bar for delivery-ready, the CSAT/escalation bar for strong, the capacity bar for overloaded) — these are the SDM's policy and should be configurable or at least centralised, not scattered.
  • Check that sla-stage-targets, expert-quality-metrics and expert-active-projects have landed; if not, define a minimal read interface and coordinate — do not fork those models.
  • touches: packages/services/src/server (coverage derivation over experts + the demand cells), apps/web/components/dashboard/sdm/{bid-readiness-table.tsx,demand-table.tsx}.

01_define/output/spec.md

Spec: SDM dashboard — supply coverage (quote & bid readiness)

  • slug: sdm-supply-coverage
  • personas: SDM (admin sets the policy thresholds)
  • touches: packages/services/src/db/services/sdm-coverage (new), packages/services/src/db/services/tenant-setting (registry + typed read), apps/web/components/dashboard/sdm/{bid-readiness-table.tsx,demand-table.tsx}, apps/web/app/(app)/sdm/dashboard/page.tsx, apps/web/app/(app)/admin/settings/* (coverage thresholds tab)
  • complexity: complex

Problem

The SDM "Go live readiness" dashboard is a supply↔demand coverage cockpit: can the expert pool cover the demand flowing through BRD → quote → bid? sdm-demand-aggregation (#3, merged) now builds the demand half — real SdmDemandCell[] keyed (platform, module, region, language) with demand, quotesWaiting, bidReadyExperts, timeBand and daysToStart. But the supply half does not exist as a derivation: the quote-and-bid readiness table still reads quoteAndBidReadiness from apps/web/lib/mock/sdm.ts, and the demand table renders its two coverage columns (Coverage status, Coverage gap) as neutral Pending placeholders, explicitly waiting on this feature. Classifying matched experts into bid-ready / delivery-ready / strong per cell — and deriving whether a cell is covered — is the core net-new SDM logic of this epic. Without it the SDM cannot see where supply fails demand, which is the readiness signal behind Build-the-Bridge / Q2 2026 Objective 3 (validate the infrastructure that lets leads complete end-to-end): an uncovered or weak-bid cell is a lead that will stall before go-live.

Proposed change

Build a supply-coverage derivation that enriches each demand cell with a supply classification, then wire both tables from it and surface the thresholds as admin-configurable tenant policy.

1. Coverage derivation (new sdmCoverageService)

A new tenant-isolated, area-scoped read-service — getCoverageForSdm(tenantId, sdmId, scope) — that resolves the demand cells (via the existing sdmDemandService.getDemandForSdm, not a re-aggregation) and, for each cell, classifies the experts matched to that cell's leads. It consumes the already-merged upstream models and does not recompute any of them:

  • Candidate experts per cell — the experts on the cell's leads' lead_expert_match records (the same source sdm-demand uses for bidReadyExperts), resolved to ids + names.
  • bid-ready — qualified + available: matched to the cell and currently available (expert.isAvailable / availability), i.e. the existing bid-ready set.
  • delivery-ready — bid-ready and meeting the delivery bar: the expert's SLA adherence (derived from the existing per-lead SLA timing — deriveLeadSlaTiming / the SLA service — over the expert's recent/active leads, since no per-expert aggregate exists upstream) is at or above the configurable deliveryReadyMinSlaAdherence bar.
  • strong — delivery-ready and high quality: getExpertQualityMetrics CSAT ≥ strongMinCsat and reworkRatestrongMaxReworkRate (rework rate is the upstream analog of the mock's escalation rate; CSAT/rework/repeat all come from getExpertQualityMetrics — not recomputed here).
  • overloaded — the cell's matched experts are over capacity: active work (getExpertMatchingContext activeProjectCount, or availability.weeklyCapacityHours) at or above the configurable overloadedMinActiveProjects bar for all/most matched experts.
  • Derived per cell: availableExperts (count of bid-ready), shortfall (max(0, demand − availableExperts)), coverageStatus (Fully covered when availableExperts ≥ demand · Partial when 0 < availableExperts < demand · Not covered when 0), and a readinessStatus RAG (green / amber / red) from coverageStatus plus whether the cell has any delivery-ready / strong experts.

The service returns demand cells enriched with { bidReadyExperts, deliveryReadyExperts, strongExperts, availableExperts, shortfall, coverageStatus, readinessStatus, overloaded } so one read drives both tables. Empty/again-degraded reads return an empty list (same resilience pattern the page already uses for demand).

2. Admin-configurable thresholds

The classification bars are SDM policy, configured per tenant via the existing tenant-settings registry pattern (not scattered constants):

  • Add registry entries (category coverage) to SETTINGS_REGISTRY: coverage.deliveryReadyMinSlaAdherence (number, %), coverage.strongMinCsat (number, /5), coverage.strongMaxReworkRate (number, %), coverage.overloadedMinActiveProjects (number) — each with a sensible default and description.
  • Add a typed read getCoverageThresholds(tenantId) on TenantSettingService (mirroring getCommercialConfig / getEscalationConfig) that resolves stored values or registry defaults; the derivation reads thresholds through it.
  • The thresholds surface for editing in the admin Settings area under a coverage tab, consistent with the existing category tabs (general, commercial, …) — so an admin tunes them in-app this run.

3. Wire the dashboard

  • Quote-and-bid readiness table (bid-readiness-table.tsx) renders from the enriched cells (platform/vendor, module/product, demand, available experts, shortfall, readiness RAG) instead of mock/sdm.
  • Demand table (demand-table.tsx) fills its Coverage status and Coverage gap columns from the enriched cells, replacing the Pending placeholders (status from coverageStatus / readinessStatus, gap from coverageStatus).
  • The SDM dashboard page resolves coverage once (alongside area + demand) and feeds both tables.

Acceptance criteria

  • A new tenant-isolated, area-scoped sdmCoverageService.getCoverageForSdm(tenantId, sdmId, scope) returns the demand cells enriched with the supply classification, reusing sdmDemandService for the demand half (no second demand aggregation) and honouring scope (mine / tenant).
  • Each cell classifies its matched experts into bid-ready / delivery-ready / strong from real lead_expert_match + reused SLA timing + getExpertQualityMetrics (none of CSAT / SLA / rework is recomputed in this feature).
  • availableExperts, shortfall, coverageStatus (Fully covered / Partial / Not covered), readinessStatus RAG and overloaded are derived per cell over the SDM's area.
  • The classification thresholds (delivery-ready SLA bar, strong CSAT bar, strong max-rework bar, overloaded active-work bar) are read from tenant settings via getCoverageThresholds, defined in SETTINGS_REGISTRY with defaults, and editable by an admin in the admin Settings UI.
  • The quote-and-bid readiness table renders from the derivation — it no longer reads quoteAndBidReadiness from lib/mock/sdm.
  • The demand table's Coverage status and Coverage gap columns fill in from the same derivation, replacing the Pending placeholders.
  • No SDM dashboard section reads lib/mock/sdm for supply/coverage; an SDM with no area (under mine) or a failed read degrades to an empty/placeholder table, not a blanked dashboard.

Out of scope

  • Recomputing CSAT / SLA / rework / completion — consumed from getExpertQualityMetrics, the SLA timing layer, and getExpertMatchingContext (the merged expert epic).
  • The go-live risks rows, outreach/action state and the action strip (sdm-coverage-risk-outreach, #5) — they derive from this coverage output.
  • The per-expert strength table (sdm-expert-strength, #6) and the two analytics sections (market signals #7, service performance #8).
  • A bespoke admin CRUD UI beyond the existing registry-driven Settings tab pattern; auto-outreach automation; introducing skillLevel as a cell key (the demand cells from #3 key on platform × module × region × language, and this feature matches that granularity).
  • Any change to apps/dashboards (it stays the dummy source-of-truth) and to the demand aggregation itself (owned by #3).

Open questions

  • none — thresholds are admin-configurable (registry defaults ship; an admin can tune them). Default values (e.g. delivery-ready SLA bar, strong CSAT/rework bars, overloaded active-project bar) are a tuning detail set in the registry at Build time and adjustable in-app; they do not block any acceptance criterion.

02_build/output/notes.md

Build notes: sdm-supply-coverage

  • commits: feat(sdm-supply-coverage): supply-coverage derivation, admin thresholds, dashboard wiring

What changed

  • packages/services/src/db/services/sdm-coverage/ (new): SdmCoverageService.getCoverageForSdm(tenantId, sdmId, scope). Reuses sdmDemandService.getDemandForSdm for the demand half (no second aggregation), then classifies each cell's matched experts:
    • bid-ready = matched + isAvailable; delivery-ready = bid-ready + per-expert SLA adherence ≥ bar (adherence = share of the expert's active leads whose reused SLA band is not red); strong = delivery-ready + CSAT ≥ bar + rework ≤ bar (from getExpertQualityMetrics). overloaded cell = has bid-ready experts and all of them are at/over the active-project bar (getExpertMatchingContext).
    • Derives availableExperts, shortfall, coverageStatus (Fully covered / Partial / Not covered), readinessStatus RAG and overloaded per cell.
  • packages/services/src/db/services/sdm-demand/index.ts: extended SdmDemandCell with bidReadyExpertIds (the matched expert ids) so coverage can classify supply without re-running the aggregation. Demand counting is unchanged.
  • packages/services/src/db/services/tenant-setting/: added the coverage category to SETTINGS_REGISTRY (4 numeric thresholds with defaults) and a typed getCoverageThresholds(tenantId) getter (registry defaults when unset) — mirrors getCommercialConfig.
  • packages/services/src/db/services/index.ts: barrel exports for sdmCoverageService (placed after its dependency services) + SdmCoverageCell/CoverageStatus/CoverageReadiness/CoverageThresholds types.
  • apps/web/.../sdm/dashboard/page.tsx: resolves coverage once (alongside area) and feeds both tables; coverage failure degrades to empty tables (area stays the gate).
  • apps/web/components/dashboard/sdm/bid-readiness-table.tsx: renders from the derivation (platform, product, demand, available experts, shortfall, readiness RAG) — no longer lib/mock/sdm.
  • apps/web/components/dashboard/sdm/demand-table.tsx: Coverage status + Coverage gap columns fill from the derivation, replacing the Pending placeholders; row RAG from readinessStatus.
  • apps/web/.../admin/settings/coverage/page.tsx (new) + tabs-config.ts: a Coverage admin Settings tab that renders the coverage thresholds via the existing SettingsRegistryManager (admin-only, covered by the existing /admin/settings route policy).

Acceptance criteria status

  • sdmCoverageService.getCoverageForSdm(tenantId, sdmId, scope) returns demand cells enriched with the supply classification, reusing sdmDemandService (no second aggregation), honouring mine/tenant.
  • Each cell classifies bid-ready / delivery-ready / strong from real lead_expert_match + reused SLA timing + getExpertQualityMetrics (nothing recomputed here).
  • availableExperts, shortfall, coverageStatus, readinessStatus and overloaded derived per cell over the SDM's area.
  • Thresholds read via getCoverageThresholds, defined in SETTINGS_REGISTRY with defaults, editable in the admin Settings UI (new Coverage tab + the general Settings tab).
  • Quote-and-bid readiness table renders from the derivation — no longer reads quoteAndBidReadiness from lib/mock/sdm.
  • Demand table's Coverage status + Coverage gap columns fill from the same derivation, replacing Pending.
  • No SDM section reads lib/mock/sdm for supply/coverage; a no-area or failed read degrades to empty/placeholder tables, not a blanked dashboard.

Verify result

  • mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview. Local pnpm typecheck was run once after the changes and passed (9/9 tasks) — the service-type + barrel + component changes compile clean across the monorepo.

Notes for review

  • SLA adherence is derived, not stored. There is no per-expert SLA aggregate upstream, so adherence = share of an expert's active leads whose reused per-lead SLA band isn't red; an expert with no active leads is treated as 100% (no breaches). Worth a sanity check that this matches the intended "delivery bar".
  • Rework rate stands in for the mock's escalation rate (no escalation metric is exported; getExpertQualityMetrics exposes reworkRate).
  • Coverage keys on the demand-cell granularity (platform, module, region, language)skillLevel is intentionally not a cell key (the merged demand cells from #3 don't carry it).
  • Default thresholds (SLA 80%, CSAT 4.5, rework 10%, overloaded 5 active projects) ship in the registry and are admin-tunable.

03_release/output/changelog.md

See which experts can actually cover each gap

Your go live readiness dashboard now shows whether you have the supply to meet demand:

  • The quote and bid readiness table classifies the experts matched to each gap as bid-ready, delivery-ready or strong — from real availability, SLA and quality data, not placeholders.
  • The demand table's coverage columns now show whether each cell is fully covered, partial or not covered, and the size of any shortfall.
  • Admins can tune the bars behind these labels — the SLA, quality and capacity thresholds — under Settings → Coverage.

03_release/output/investor-update.md

SDMs now see which experts can actually cover each gap

Who it's for: Service delivery managers (SDM); admins set the thresholds What shipped: The go live readiness dashboard's quote-and-bid readiness table and demand coverage columns now classify matched experts as bid-ready, delivery-ready or strong from real quality and SLA data, with admin-tunable thresholds. Why it matters: SDMs see real coverage gaps before they bite — Build the Bridge, Q2 2026 Objective 3 (validate technical infrastructure & payout flow).

Dig deeper: https://github.com/sustentus/sustentus/pull/525 · https://help.sustentus.com/changelog/2026-06-23-sdm-supply-coverage