Client Care Dashboard: Close attribution toggle (Scheduled By vs Quote Creator)

Objective

Add a toggle on the Employee Performance scheduler stats table (Client Care Dashboard) so users can switch close-rate attribution between:

  1. Scheduled By (default / current behavior) — all inspection-backed close components are credited to inspection.scheduledBy.
  2. Quote Creator — for inspections linked to a quote (Quote._finalInspectionId), credit close components to quote.createdBy; if no linked quote, fall back to scheduledBy.

This lets CC leadership compare “who booked the job” vs “who originated the quote/lead” without changing revenue, upsell, cancel, or other non-close columns unless product decides otherwise.


Current behavior (as of investigation)

Backend: GET /cc-dashboard/scheduler-metrics in apps/backend/src/routes/ccDashboard.ts

  • Main inspection aggregation groups by (scheduledBy, _companyId) for revenue, created, confirmed, upsell, cancel, add-on, etc.
  • Open quotes in the close-rate denominator already use quote.createdBy → user mapping (Steps 5–5b).
  • Result: hybrid attribution today — confirmed inspections → scheduledBy; orphan open quotes → createdBy.

Frontend: EmployeeTable.tsx renders the table inside DashboardBlock; filters live in the controller slot (Online Scheduler checkbox + scheduler multi-select). Data fetched from CCDashboardClient.tsx via cc-dashboard/scheduler-metrics.

Data model:

  • cc_inspection_base view exposes scheduledBy but not quote linkage (createCcDashboardViews.ts).
  • Quote link: Quote._finalInspectionId ↔ inspection _id; quote originator: Quote.createdBy.

Proposed implementation

1. API — new query param

Add closeAttribution=scheduledBy|quoteCreator (default scheduledBy) to GET /cc-dashboard/scheduler-metrics.

When closeAttribution=quoteCreator, re-attribute close-related fields only:

  • closeRate
  • ordersCreated (denominator basis)
  • ordersConfirmed (numerator)
  • Team average + per-company breakdown for those fields

Keep on scheduledBy (unless product expands scope): revenue, averageFee, upsellRate, oneCallCloseRate, selectedAddOnRate, cancel columns, # Core Confirmed.

2. Backend aggregation strategy

Recommended approach (no view migration required):

  1. Keep existing scheduledBy aggregation for non-close metrics.
  2. Add a parallel close-only aggregation when closeAttribution=quoteCreator:
  • $match same date/status filters as main query.
  • $lookup quotes on { localField: '_id', foreignField: '_finalInspectionId', as: 'quote' }.
  • $addFields: closeAttributionEmp: { $ifNull: [{ $arrayElemAt: ['$quote.createdBy', 0] }, '$scheduledBy'] }.
  • $group by (closeAttributionEmp, _companyId) summing only created (non-cancelled), confirmed, and cancelled counts needed for close math.
  1. Merge close buckets into existing mergedMap by resolved userId (same employeeToUser / membership mapping as today).
  2. Open-quote denominator logic stays as-is (createdBy) — already matches Quote Creator mode.

Online Scheduler row: clarify product rule when scheduledBy is null but a linked quote has createdBy — likely move those counts off the Online row onto the quote creator’s row.

3. Frontend

  • Add toggle in EmployeeTable controller (alongside Online Scheduler / All Schedulers), e.g. segmented control:
  • Scheduled By | Quote Creator
  • Thread state through CCDashboardClient.tsxfetchSchedulerMetrics params.
  • Refetch scheduler metrics on toggle change (same pattern as dateType).
  • Update empty-state copy if needed (currently mentions scheduledBy only).

4. Saved views (optional but recommended)

Follow dateType / globalCategoryMatch pattern:

  • Add closeAttribution to @attik/types/ccDashboardView.types, DB model, saved-view API, and save payload in CCDashboardClient.

Edge cases / open questions

  • [ ] Should One Call Close follow the same attribution toggle? (User request mentions close rate specifically.)
  • [ ] Inspection booked online (scheduledBy: null) with internal quote creator — counts leave Online Scheduler row?
  • [ ] Quote exists but createdBy is missing — confirm fallback to scheduledBy.
  • [ ] Reconcile with ATT-1270 semantics so # Created still matches close-rate denominator in both modes.

Files to touch

Backend

  • apps/backend/src/routes/ccDashboard.ts — param parsing, close-only re-aggregation, merge logic
  • (Optional) integration test covering both modes with fixture inspections + quotes

Frontend

  • apps/frontend/src/app/admin/client-care-dashboard/_components/EmployeeTable.tsx — toggle UI
  • apps/frontend/src/app/admin/client-care-dashboard/CCDashboardClient.tsx — state + fetch param
  • apps/frontend/src/app/admin/client-care-dashboard/_types.ts — type for param
  • (Optional) saved view types/model if persisting preference

Acceptance criteria

  • [ ] Toggle visible on Employee Performance block; default = Scheduled By (no behavior change for existing users).
  • [ ] Quote Creator mode: confirmed inspection linked to quote counts toward quote createdBy (mapped to user via membership); unlinked inspections still use scheduledBy.
  • [ ] Open quotes continue to count toward createdBy in both modes.
  • [ ] Close Rate = # Confirmed / # Created remains internally consistent per row in both modes.
  • [ ] Revenue / upsell / cancel columns unchanged when toggling (unless product expands scope).
  • [ ] Team Average row recalculates correctly for close fields in both modes.

Test plan

  1. Pick a CSR who creates quotes but has another person schedule confirmations — verify close rate shifts between modes.
  2. One-call / direct-book inspection (no quote) — identical in both modes.
  3. Online Scheduler row with mix of self-booked vs quote-originated jobs.
  4. Multi-company view: same user merged across companies; per-company expand still correct.
  5. Saved view reload preserves toggle (if implemented).

Please authenticate to join the conversation.

Upvoters
Status

Planned

Board
🏠

Main App

Date

About 4 hours ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.