Sales ships two helper SQL views in its Postgres schema to make the SDR-allocation volume signal and the stale_lead_count last_touched_at signal directly readable by Platform's silver build; sales.lead_sdr_summary and sales.lead_last_touched_at_signal are live, column lists below, no Sales-side compute beyond standard SQL aggregation over already-silver source tables
Why
The prior Sales reply (2026-05-19-sales-sdr-allocation-and-stale-lead-predicate-response) outlined what Sales would expose for Platform's CAC-numerator + stale-lead-count work. To make those answers directly readable by Platform's silver build rather than requiring Platform to re-derive the SQL on its side, Sales has now shipped two helper views in the sales. schema. Both views are pure aggregations over already-silver source tables (sales.lead, sales.lead_attempt, sales.touch); they do not import any cross-domain data and do not violate the firewall principle.
The views are convenience exports for Platform's mart compute. They are read-only. They can be referenced from the silver build directly (SELECT * FROM sales.lead_sdr_summary WHERE ...) or re-derived in Platform's silver build if Platform prefers — either path is correct. Sales' purpose in shipping them is to make the on-Sales-side semantic explicit and discoverable.
sales.lead_sdr_summary
Migration: prisma/migrations/20260519120000_lead_sdr_summary_view/migration.sql
Grain: one row per Lead (keyed by lead_id).
Filter: is_test_data = false. Backfill rows (is_backfill = true) ARE included; downstream filters can drop them via the is_backfill column on this view.
Columns:
| Column | Type | Source | Notes |
|---|---|---|---|
lead_id |
text | lead.id |
Primary key. |
tenant_id |
text | lead.tenant_id |
ADR-0001 scope. |
person_id |
text | null | lead.person_id |
Null until intake.matched lands. |
intake_created_at |
timestamptz | lead.intake_created_at |
Originating intake moment per Growth's intake.captured. |
intake_cohort_week |
timestamptz | date_trunc('week', intake_created_at) |
Pre-computed week-bucket for per-cohort joins. |
is_backfill |
bool | lead.is_backfill |
Active-cohort filter discriminator. |
lead_stage |
text | lead.stage |
Current lifecycle stage. |
lead_status |
text | lead.status |
ADR-0002 spec status (active/stalled/lost). |
attempt_count |
bigint | COUNT(lead_attempt) |
Cadence call attempts (1-4 capped). |
reached_attempt_count |
bigint | COUNT(lead_attempt) FILTER (contact_status = 'reached') |
Subset that produced operator contact. |
first_attempt_at |
timestamptz | null | MIN(attempted_at) |
First operator dial. |
last_attempt_at |
timestamptz | null | MAX(attempted_at) |
Most recent dial. |
distinct_call_operator_count |
bigint | COUNT(DISTINCT operator_id) |
How many SDRs touched this Lead via cadence calls. |
touch_count |
bigint | COUNT(DISTINCT touch.id) |
Broader operator-evidence grain: calls + texts + system touches. |
outbound_touch_count |
bigint | COUNT(... FILTER direction='outbound') |
Operator-originated touches. |
inbound_touch_count |
bigint | COUNT(... FILTER direction='inbound') |
Customer-originated responses. |
last_touched_at |
timestamptz | null | MAX(touch.occurred_at) |
Most recent operator-evidence event. |
distinct_touch_actor_count |
bigint | COUNT(DISTINCT actor_id WHERE actor_id IS NOT NULL) |
Distinct operators reflected in touch (broader than call-operator count). |
Join keys for Finance's CAC compute:
intake_cohort_weekfor the per-cohort grouping.org_market_idis NOT on this view directly; join viasales.lead_market_assignment WHERE status = 'confirmed'to retrieve org-market for each Lead. Sales' lead-grain face intentionally keeps market normalized rather than denormalizing onto every row.
What this view is NOT:
- Not a dollar-allocation table. Sales does not have per-attempt durations or per-operator hourly rates. The volume columns above are inputs; Finance multiplies by its own assumption layer (per-attempt minutes constant, per-operator rate from payroll) in
lib/mart/sections/finance.tsto derive dollars. - Not a Person-grain face. The view is per-Lead. For per-Person CAC (a Person can have multiple reactivation Leads), Finance groups by
person_idand sums the volume columns; the result is sound under ADR-0003's many-Leads-per-Person model.
sales.lead_last_touched_at_signal
Migration: prisma/migrations/20260519121000_lead_last_touched_at_signal_view/migration.sql
Grain: one row per Lead (keyed by lead_id).
Filter: is_test_data = false.
Columns:
| Column | Type | Source | Notes |
|---|---|---|---|
lead_id |
text | lead.id |
Primary key. |
tenant_id |
text | lead.tenant_id |
ADR-0001 scope. |
intake_created_at |
timestamptz | lead.intake_created_at |
Fallback anchor. |
last_touched_at_raw |
timestamptz | null | MAX(touch.occurred_at) |
Null when the Lead has zero touch rows. |
last_touched_at_with_fallback |
timestamptz | COALESCE(MAX(touch.occurred_at), intake_created_at) |
The signal Platform's stale_lead_count predicate reads. |
source_of_signal |
text | derived | 'touch_occurred_at' or 'intake_created_at_fallback' — audit-friendly tag for analysts who want to distinguish "operator touched" from "fell through to fallback." |
touch_count |
bigint | COUNT(touch.id) |
Convenience; same as in lead_sdr_summary. |
Platform's stale_lead_count SQL becomes:
SELECT count(*)
FROM sales.lead AS l
JOIN sales.lead_last_touched_at_signal AS s
ON s.lead_id = l.id
WHERE l.stage = 'qualified'
AND s.last_touched_at_with_fallback < $period_end - INTERVAL '14 days'
-- (l.tenant_id, l.is_test_data filters as usual)
The fallback semantic Sales recommended in 2026-05-19-sales-sdr-allocation-and-stale-lead-predicate-response §Ask 3 (use intake_created_at over lead.updated_at to avoid system-write contamination) is encoded inside this view so it stays Sales-owned rather than scattered across Platform's mart query.
What stays the same
- Sales' silver source list per
2026-05-17-sales-silver-source-schema-confirmationis unchanged. The two views are pure aggregations over those source tables; they do not add new source-grain faces. - The firewall principle stands: Sales does not import payroll, ad-spend, or any other cross-domain data into these views. Finance's assumption layer for SDR cost allocation remains in
lib/mart/sections/finance.ts. - The
stale_lead_countregistry note continues to live with Platform; Sales' role is only to provide thelast_touched_at_with_fallbacksignal that the registry predicate reads.
What Platform can do now
- Platform's silver build for the Sales section can
SELECT FROM sales.lead_sdr_summaryandSELECT FROM sales.lead_last_touched_at_signaldirectly. No re-derivation needed; the views encode Sales' canonical semantic for both signals. - Finance's
cac-payback.md§"Source silver columns" gets a referenceable source for the volume input:sales.lead_sdr_summary.attempt_count,.touch_count, etc. - Platform's
stale_lead_countregistry note can pin tosales.lead_last_touched_at_signal.last_touched_at_with_fallbackand drop the inlinecoalesce(last_touched_at, lead_updated_at)clause from the registry SQL.
No commitments
The views are shipped. No Sales-side commitments fall out of this memo.
References
- Prior Sales reply with the three-ask answers:
2026-05-19-sales-sdr-allocation-and-stale-lead-predicate-response - Parent thread:
2026-05-19-platform-mart-100-percent-deployment-per-domain-asks - Sales silver source schema confirmation:
2026-05-17-sales-silver-source-schema-confirmation - Migrations:
sales/prisma/migrations/20260519120000_lead_sdr_summary_view/migration.sqlsales/prisma/migrations/20260519121000_lead_last_touched_at_signal_view/migration.sql
- ADR-0001 (tenant scoping)
- ADR-0002 (spec status
active/stalled/lost; Stage 1 Day 14 auto-lost threshold) - ADR-0003 (Person canonical; many Leads per Person)
- ADR-0016 (cross-domain reporting; Finance permitted cross-warehouse reads)
- Sales domain scope:
coordination/domains/sales.md