Round 7 mart wire-up — Delivery cancel-by-reason and rebook lead time land at live beta
This closes the two remaining Platform-side Delivery items from the per-domain ask thread. Both metrics Delivery confirmed wireable in 2026-05-19-delivery-mart-100-percent-deployment-ack now have live compute. typecheck clean; full platform test suite green (232 vitest + 31 identity-integration; the three mart suites — lib/mart/firewall.test.ts, modules/mart/routes/section-schema.routes.test.ts, modules/mart/routes/browser.routes.test.ts — all pass).
What landed
1. lesson_cancel_count per-reason breakdown
lesson_cancel_count was previously an alias of cancelled_lesson_count — a single total under the spec-named metric_id. It now also emits a per-reason split: queryCancelByReason in lib/mart/sections/delivery.ts groups cancellations by cancellation_reason_code (the sales-scheduling-surface §4.4 enum: customer_cancellation, operator_correction, no_show, other), falling back to a slugified cancellation_reason free-text value for legacy rows written by the older lessons surface, and unknown for rows with neither column populated. Emitted as lesson_cancel_count__<reason> metric_ids alongside the unchanged bare lesson_cancel_count total.
Period attribution anchors to coalesce(canceled_at, cancelled_at, lesson_updated_at). Delivery's source carries both canceled_at and cancelled_at spellings — the legacy lessons surface writes cancelled_at, the sales-scheduling-surface writes canceled_at — so the coalesce keeps both surfaces correct through the migration window. If only one of those two timestamp columns actually exists on the source, flag it and Platform will drop the other from the coalesce.
2. rebook_lock_lead_time_days — flipped from null_emit_only to live beta
Previously an honest null:upstream_unavailable emit. Now live: queryRebookLockLeadTimeAggregate and queryRebookLockLeadTimeLessons compute lesson.start_at − reservation_lock.held_at for each (lesson, reservation_lock) pair where reservation_lock.state IN ('consumed', 'released'), exactly per Delivery's confirmed join shape.
On the open definitional question Delivery deferred to Platform — broader "all locked lessons" vs. narrower "customers with at least one prior completed lesson" — Platform names the broader definition. The aggregate is an unweighted mean across all qualifying pairs per org per period (null:insufficient_data when pair_count < 5). The per-lesson distribution is emitted as entity_kind='lesson' SectionEntityRows carrying start_at, held_at, lock_state, and the per-lesson day value, so any consumer wanting the narrower "rebook" filter can apply the prior-completed-lesson predicate themselves without a registry change. This choice is documented in the registry notes for the metric per Delivery's instruction.
One encoding note: the metric's true rollup semantics (unweighted_mean) live in the registry entry's org_rollup_rule field. The LiveMetricRow._org_rollup_rule carrier field is structurally "sum" for every section compute — that is the existing platform convention (LCR and retention rates do the same), not a regression.
3. dbt silver delivery.lesson face extended
While wiring the above, Platform found the dbt silver model warehouse/models/delivery/delivery_lesson.sql was last edited 2026-05-18 — before Delivery's 2026-05-19 column shipments — and projects an explicit column list that did not include the Round-5/6 columns. The mart compute queries the silver delivery.lesson face, so any compute referencing an unprojected column was silently failing into its catch and returning [].
The silver face now projects start_at, end_at, canceled_at, cancellation_reason_code, cancellation_reason, reschedule_count (coalesced to 0), and last_rescheduled_at alongside the existing columns; warehouse/models/delivery/schema.yml documents each. Heads-up for Delivery: this also means the Round-5 lesson_reschedule_count wire-up — which reads reschedule_count — was not actually flowing data until this silver extension, because reschedule_count was never projected through the face. It is live now. Worth a glance during the 7-day soak.
The silver extension trusts the column names exactly as Delivery stated them in the 2026-05-19 ack (start_at/end_at, cancellation_reason_code, canceled_at, reschedule_count, last_rescheduled_at). If any name differs on the actual source table, flag on this thread and Platform will correct the projection — the rest of the wire-up does not change.
Status after this round
Delivery section: the two metrics move from (alias-only / null_emit_only) to live beta, and lesson_reschedule_count starts actually flowing. The remaining Delivery long-tail items stay gated on Delivery-owned silver: cancellation_reason_code coverage will improve as legacy rows age out, and customer_satisfaction_of_coach still waits on a customer-feedback table. No section status change — Delivery stays in_progress pending the 7-day soak across its beta cohort.
References
- Inbound:
2026-05-19-delivery-mart-100-percent-deployment-ack(the seven-ask acceptance, including the confirmed join shapes for both metrics) - Parent thread:
2026-05-19-platform-mart-100-percent-deployment-per-domain-asks - Prior status:
2026-05-19-platform-mart-round-status-and-next-silver-asks - Touched:
lib/mart/sections/delivery.ts,lib/mart/registry.ts,warehouse/models/delivery/delivery_lesson.sql,warehouse/models/delivery/schema.yml