Delivery declares the coach-grain CSAT silver face and ships it
Why
Wiring coach_quality_score's mart compute in lib/mart/sections/coaching.ts is gated on Delivery declaring the silver table name and face for the coach-grain CSAT aggregate. The compute reads silver by name, and while 2026-05-22-coaching-nps-program-v1-cadence-accepted froze the aggregate's shape, the table name itself was not yet declared. This FYI declares the name and face, and reports that the surface is shipped, so the Platform side is unblocked.
The silver face
Delivery exposes the coach-grain aggregate as a view in its own Postgres schema:
delivery.coach_csat_aggregate
| Column | Type | Meaning |
|---|---|---|
tenant_id |
text | The organization. |
coach_id |
text | The coa_ of the coach who delivered the rated lessons. |
week |
date | The ISO week (Monday) the row is emitted for. |
response_count |
integer | CSAT responses in the trailing 8 weeks ending at week. |
mean_rate |
double precision | Mean normalized CSAT rate (0 to 1) over that window. |
Grain: one row per (tenant_id, coach_id, week), emitted weekly. Each row's value summarizes the trailing 8 weeks of CSAT, the week itself plus the seven prior, matching the trailing-8-week period_attribution Platform landed on coach_quality_score in 2026-05-22-platform-nps-program-v1-period-attribution-confirmation.
The view is CSAT-only and customer-identity-stripped: the CSAT filter and the identity strip both happen inside the view, so no customer_id, participant_id, or lesson_id column ever crosses the firewall through this surface. NPS rows never enter it.
The < 5 minimum-N guard is applied inside the view as a HAVING count >= 5: a coach with fewer than 5 CSAT responses in the trailing window produces no row. Platform's compute should read an absent (tenant_id, coach_id, week) row as null:insufficient_data.
Analytics questions
This surface supports exactly one mart metric, coach_quality_score. The question it answers: over the recent window, how well are a coach's delivered lessons rated by customers? mean_rate is the answer, and response_count is the sufficiency gate that backs the < 5 minimum-N guard. No other analytics question is in scope for this surface. NPS and the relationship-level customer_satisfaction_of_coach are a separate instrument and a separate Delivery-owned compute, and are deliberately not in this view.
Shipped
delivery.coach_csat_aggregate ships in delivery migration 20260522180000_survey_program, alongside the capture surface it projects from (delivery.survey_send and delivery.survey_response) and the V1 cadence engine. The view is created as raw SQL in the migration, not as a Prisma model, consistent with how Delivery exposes silver-facing surfaces.
Two notes
Suppressed-row encoding. Delivery ships sub-threshold coaches as absent rows, not as rows with a null mean_rate. Coaching parked the suppressed-row encoding for when coach_quality_score's compute is wired. If Platform and Coaching would rather the view emit a row with a null mean_rate and the true response_count, that is a one-line change to the view's HAVING clause; say so on this thread and Delivery will land it.
Schema qualifier. The view is delivery.coach_csat_aggregate, in Delivery's own schema, following the precedent of domains exposing helper surfaces in their own schema for Platform's silver build to read. If Platform's silver layer needs it surfaced under mart_delivery instead, Delivery will add the alias. The column face above is the contract either way.
References
- Settled V1 survey design:
2026-05-22-coaching-nps-program-v1-cadence-accepted - Delivery's cadence counter-proposal:
2026-05-22-delivery-nps-program-v1-cadence-counter-proposal - Platform's period_attribution edit:
2026-05-22-platform-nps-program-v1-period-attribution-confirmation - Thread root:
2026-05-19-platform-mart-100-percent-deployment-per-domain-asks - Delivery migration:
delivery/prisma/migrations/20260522180000_survey_program/migration.sql - Platform mart compute to wire:
platform/lib/mart/sections/coaching.ts