Re-engagement reporting
|
None of the reports on this page are built. This is a proposed reporting surface for the segments described in Re-engagement segments, written to decide what is worth building and in what order. Three of them can be built against data that exists today. The rest depend on an enrolment log that does not yet exist — see What blocks most of this. |
Why this page exists
The five segments enrol contacts onto call lists every few hours, and there is currently no
measurement of what happens next. Each handler logs its own counters
(updated, assigned, skipped_no_hs_id) to CloudWatch and nothing aggregates them; nothing
at all tracks whether an enrolled contact was ever dialled, or whether a segment’s priority
score predicts anything.
The unit of analysis is the enrolment: one contact, stamped by one segment, on one day. Every report below is a different question asked of that unit.
flowchart LR A["Segment enrols\ncall_list_date stamped"] --> B["Rep dials\nCALL engagement"] B --> C["Connect\nhs_call_status / disposition"] C --> D["Positive outcome\nwarm lead, meeting, deal"] A -.->|"never dialled"| E["Leakage"] A -.->|"suppression missed"| F["Compliance risk"]
Classification used throughout
Several reports below depend on a per-call outcome classification. The working definition, implemented as a HubSpot custom report formula field:
| Category | Rule |
|---|---|
|
|
|
|
|
|
|
Everything else |
The date condition matters: without it, a call that predates the enrolment is credited to the segment that enrolled the contact afterwards.
| Set | Dispositions |
|---|---|
Positive |
Account created, API Connected Confirmed, Connected, Follow up scheduled, Left live message, Warm Lead (AE Follow Up) |
Suppression |
Blacklisted, Do not contact, Not a seller, No longer selling/shut down, Marketplace not supported, Gatekeeper |
|
Both sets are judgement calls, not facts drawn from the portal. Two are worth revisiting:
Left live message counts as positive without a live conversation having happened, and
Gatekeeper sits in the suppression set although it describes a reached-the-wrong-person
outcome rather than a disqualification. The authoritative GUID list is
|
A. Effectiveness
Does re-engagement produce anything?
| Report | Grain | Metrics | Answers |
|---|---|---|---|
Outcome by enrolment source |
contact × |
Share of each outcome category |
Which of the five segments is worth keeping |
Enrolment-to-outcome funnel |
enrolment cohort by week |
enrolled → dialled → connected → positive → meeting/deal |
Where the drop-off is, per segment |
Positive-disposition mix |
call |
Distribution across the six positive GUIDs |
Whether a segment yields warm leads or only "connected" noise |
Priority tier performance |
|
Connect rate, positive rate |
Whether the priority formulas predict anything |
The funnel deliberately mirrors the structure of the existing v_call_campaign_funnel view so
the two can be read side by side, but it is keyed on enrolment rather than on an AI call.
|
Priority tier performance is currently unmeasurable for one segment and skewed for all of them.
The lost-deal handler does not write |
B. Execution hygiene
Are reps working the list they are given?
| Report | Grain | Metrics | Answers |
|---|---|---|---|
Time-to-first-call SLA |
contact |
Days from |
How quickly enrolment turns into a dial |
Never-called leakage |
contact |
Count enrolled with zero call engagements, split by rep, segment, enrolment age |
How much enrolment volume is pure waste |
Per-rep throughput |
rep × day |
Assigned, dialled, connected, untouched carry-over |
Whether the list size matches rep capacity |
Stale enrolment aging |
contact |
Days since |
Which contacts are rotting on the list |
Never-called leakage is the report with the largest known number behind it already: contacts
with both call_list_date and call_priority populated and no associated CALL engagement of
any kind currently number in the tens of thousands.
|
This report also validates a design assumption in the campaign segment. Its ordering was
changed to |
C. Assignment integrity
| Report | Grain | Metrics | Answers |
|---|---|---|---|
Round-robin fairness |
rep |
|
Whether |
Fallback-owner residue |
contact |
Count still owned by |
How often reassignment silently fails |
Assignment vs. actual caller |
call |
Rows where |
Whether routing is respected in practice |
Fallback-owner residue is a direct check on a known failure path: when the round-robin returns no rep, four of the five handlers log a warning and continue without an owner, leaving the contact enrolled but unrouted. Only the campaign segment aborts the batch instead.
D. Compliance and suppression
| Report | Grain | Metrics | Answers |
|---|---|---|---|
Blacklist leakage |
contact |
Contacts with a suppression disposition that were subsequently re-enrolled, split by segment |
Whether suppression is actually holding |
Re-enrolment churn |
contact |
Enrolment count, distinct segments, total attempts, no progression |
Who is being over-contacted |
Cross-segment collision |
contact × day |
Contacts claimed by two or more segments in the same window |
How often segments overwrite each other |
Blacklist leakage is the highest-priority report on this page, because the suppression rules are
not uniform across segments and the gap is structural rather than hypothetical. The supplier,
no-show, and lost-deal views all exclude contacts carrying a bad disposition. The email-click
view suppresses on a blocked-email set, but the handler’s own Python filter only checks for a
human call within two days — and the query wrapper filters on deal_id IS NULL alone. A
contact marked Do not contact or Not a seller can be re-enrolled if the blocked-email join
misses.
|
Cross-segment collision is not just a reporting curiosity. |
E. Pipeline health
| Report | Grain | Metrics | Answers |
|---|---|---|---|
Handler run summary |
handler × run |
Rows returned, qualifying, updated, assigned, |
Whether a segment has silently stopped working |
Skip-reason breakdown |
handler × reason |
Called today, human call within 2 days, no HubSpot id, invalid |
Whether a segment produces volume that is almost entirely filtered out |
View yield against cap |
BigQuery view |
Rows returned against the query |
Whether real volume is being truncated |
The handler run summary addresses a specific blind spot. Every segment returns early — with a
warning and no work — when BIGQUERY_CREDENTIALS is empty, when the query raises, or when the
view returns nothing. All three look identical from outside: no enrolments. A total credential
outage is indistinguishable from a quiet day.
View caps to watch: 5000 for the email-click, supplier, and no-show views; 2000 for lost-deal.
What blocks most of this
Three gaps, in increasing order of how much they block.
outreach_list_reasonis not written by every segment-
The lost-deal handler has the line commented out, so its enrolments carry no attribution at all. Every report keyed on segment silently drops or misattributes them. Fixing this is one uncommented line.
call_priorityis not written by every segment-
Same handler, same cause. Priority tier analysis is unreliable until this is restored, because stale values from earlier enrolments are indistinguishable from real ones.
- There is no enrolment history
-
The blocker that matters.
call_list_dateis a single date property that every segment overwrites. Only the most recent enrolment survives, which means cohort funnels cannot be built, re-enrolment counts cannot be computed, collisions are invisible after the fact, and time-to-first-call can only be measured against the latest stamp rather than the one the rep was actually given.
Two ways to close the third gap:
| Option | Shape |
|---|---|
HubSpot custom event |
An append-only |
Append-only table |
An enrolment row written through |
The custom-event route needs no schema change outside HubSpot and keeps the record next to the engagement data every report already joins against. The table route gives stronger guarantees and is queryable without waiting for an export interval.
Build spec: enrolment-to-outcome funnel
The first report built from this catalogue (section A, row 2). It lives in HubSpot’s custom report builder, not in BigQuery: every property it reads is written to the portal by the segment handlers, so the report sits directly on the source of truth and needs no export interval.
Formula fields
Four custom formula fields, each formatted as Number and aggregated as Sum. All share two guards — the call was made by a human, and it happened on or after the enrolment date.
| Field | Meaning |
|---|---|
|
A non-AI owner dialled on or after |
|
That call reached someone — |
|
Positive disposition only, no status fallback |
|
Carries a suppression disposition. No date or owner guard, by design |
stage_dialled:
IF([CALL.hubspot_owner_id] != '86817215' AND DATEDIFF("DAY", [CONTACT.call_list_date], [CALL.hs_timestamp]) >= 0, 1, 0)
stage_connected:
IF([CALL.hubspot_owner_id] != '86817215' AND DATEDIFF("DAY", [CONTACT.call_list_date], [CALL.hs_timestamp]) >= 0 AND ([CALL.hs_call_status] = "COMPLETED" OR [CALL.hs_call_disposition] = 'f240bbac-87c9-4f6e-bf70-924b57d47db7' OR [CALL.hs_call_disposition] = 'cb091f0c-6c11-43e5-8351-b9464bd07080' OR [CALL.hs_call_disposition] = 'a4c4c377-d246-4b32-a13b-75a56a4cd0ff' OR [CALL.hs_call_disposition] = '2a86928b-2094-462e-a033-84ea14d77261' OR [CALL.hs_call_disposition] = '85db30fb-aa93-4f61-b9ba-28e6c181ae6d' OR [CALL.hs_call_disposition] = 'fb19cf15-0308-40cb-8b9d-7e432311eb09'), 1, 0)
stage_positive:
IF([CALL.hubspot_owner_id] != '86817215' AND DATEDIFF("DAY", [CONTACT.call_list_date], [CALL.hs_timestamp]) >= 0 AND ([CALL.hs_call_disposition] = 'f240bbac-87c9-4f6e-bf70-924b57d47db7' OR [CALL.hs_call_disposition] = 'cb091f0c-6c11-43e5-8351-b9464bd07080' OR [CALL.hs_call_disposition] = 'a4c4c377-d246-4b32-a13b-75a56a4cd0ff' OR [CALL.hs_call_disposition] = '2a86928b-2094-462e-a033-84ea14d77261' OR [CALL.hs_call_disposition] = '85db30fb-aa93-4f61-b9ba-28e6c181ae6d' OR [CALL.hs_call_disposition] = 'fb19cf15-0308-40cb-8b9d-7e432311eb09'), 1, 0)
stage_blacklisted:
IF([CALL.hs_call_disposition] = 'b4544009-ea7a-4d3a-af90-84d10f16fb08' OR [CALL.hs_call_disposition] = '4d34d77a-64e1-45bf-8e27-679ef35ce634' OR [CALL.hs_call_disposition] = 'c70b6205-7185-4066-a78a-304205e764ca' OR [CALL.hs_call_disposition] = '9ae74d67-7118-4fa3-a715-4f32aab43bf3' OR [CALL.hs_call_disposition] = '685e7a0d-7335-4d2f-9e6a-fe8122d89bc2' OR [CALL.hs_call_disposition] = '2d471822-8c57-4071-a3b4-a461e4f92cc3', 1, 0)
|
Write these on a single line. The formula editor has no |
|
The date guard is |
Chart configuration
Horizontal bar. Two configurations work, and they answer slightly different questions.
Option A — grouped bars, three measures
| Setting | Value |
|---|---|
Data sources |
Contacts + Calls (cross-object) |
Y-axis (categories) |
|
X-axis (measures) |
Sum of |
Breakdown |
empty |
Display |
Grouped |
Sort |
Descending by |
The breakdown slot must stay empty: HubSpot does not allow multiple measures and a breakdown simultaneously. Reads as drop-off left to right within each segment.
Option B — stacked bars, outcome as breakdown (preferred)
| Setting | Value |
|---|---|
Data sources |
Contacts + Calls (cross-object) |
Y-axis (categories) |
|
X-axis (measure) |
Count of Contacts, unique |
Breakdown |
The four-output outcome label formula |
Display |
Stacked, or 100% stacked |
Preferred because it counts unique contacts rather than summing per-call values — see the double-count warning below — and because 100% stacked makes segments with very different volumes directly comparable.
Filters
-
call_list_dateis known -
call_list_datein the last 90 days -
Optionally exclude owner
86817215at report level rather than repeating it in each formula
Companion report: Enrolled
The Enrolled stage cannot appear in the chart above. A cross-object Contacts + Calls report drops contacts with no associated call — which is precisely the never-dialled population, and the largest bar in the funnel.
Build it separately as a single-object Contacts report: count of unique Contacts, filtered to
call_list_date is known, broken down by outreach_list_reason. Place the two side by side on
one dashboard. Enrolled minus Dialled, per segment, is the leakage figure that
section B asks for.
Known limitations
- Summing formula fields counts calls, not contacts
-
A contact dialled four times contributes
4to every stage it qualifies for, so funnel widths stop being comparable. Either use Option B, or treat the formulas as filters on a count-of-unique-Contacts metric rather than as summed measures. - No native funnel visualisation
-
HubSpot’s funnel chart type accepts only lifecycle stages and deal stages, not arbitrary formula fields. A horizontal bar or a table is the available shape.
- Snapshot, not cohort
-
The same overwrite problem described in the blockers section. A contact enrolled twice is attributed entirely to whichever segment wrote
call_list_datelast, and the earlier enrolment leaves no trace. - Unattributed lost-deal rows
-
Because the lost-deal handler does not write
outreach_list_reason, its enrolments group under a blank category. Worth an explicit "Unknown" label in the report rather than letting them read as a segment.
Build order
| Order | Report | Rationale |
|---|---|---|
1 |
Compliance exposure, buildable today, and the suppression asymmetry is already known |
|
2 |
Largest single quantified waste, buildable today |
|
3 |
Decides which segments to keep; buildable today with the collision caveat |
|
4 |
— |
Close the enrolment-log gap |
5 |
Everything else |
Unblocked by step 4 |
Steps 1–3 need only the outcome classification and the properties that already exist. Nothing past step 3 should be started before the enrolment log lands, because those reports would be built against a property that overwrites itself.