# Customer engagement and re-engagement cohorts

## What it does

The work queue can surface an existing paid relationship, sustained webinar learning and a previously lost property sale. These are source-backed reasons to review a customer. They do not set loan, cash, decision-maker or relationship readiness, create a purchase opportunity, reopen a lost sale, or authorize a customer send.

Cohorts are independent of the queue's urgency rules. The engagement order is paid and engaged, previous lost sale, paid membership, then webinar engagement. Contact restrictions take precedence over every actionable re-engagement suggestion.

## How it works

`CustomerEngagement::prime($viewer, $leadsOrContexts)` reads one authorized batch. `forLead($leadId)` and `append($leadId, $workspaceRow)` perform no queries. `append` preserves the workspace's stricter live contact gate. `matches($row, $cohort)` implements the all / paid_member / webinar_engaged / paid_engaged / prior_lost filter contract. The prior-lost filter excludes do-not-contact and active contact holds; restricted history remains readable in the customer's detail.

- **Paid membership:** active, positive, dated membership receipts in `purchase_histories`. Refunded, pending, expired checkout and deleted receipts do not qualify. A linked enrolment never counts again. A positive, dated, noncancelled legacy `member_subscriptions` record without a receipt is separately labelled as a recorded membership payment. It is not presented as a gateway receipt. There is no fixed-price whitelist, and currencies are totalled separately. This describes paid history, not necessarily current membership access.
- **Sustained webinar engagement:** at least two distinct measured webinar sessions and strictly more than 100 minutes over the available lifetime history. Only registrant, exact-email and created-lead matches qualify; name-only matches do not. `MeasuredWebinarAttendance` merges overlapping intervals within each webinar, so repeated joins or duplicate rows cannot inflate minutes or session count. Open, future and invalid intervals do not prove duration. A shorter provider-reported duration conservatively limits an interval.
- **Previous lost sale:** current, nondeleted canonical `Engagement::STATUS_LOST` rows with an existing, visible project. The customer can review the project, recorded reason, stage and loss date before deciding whether circumstances changed. Reading this history never changes the CRM status.
- **Scope:** current journey and lead access always apply. Payment/membership evidence requires view-members or view-sales. Webinar evidence requires view-zoom, view-events or view-events-reports, matching the channel adapter. Lost sales require view-sales and the existing project/engagement group partition.
- **First purchase:** creating the first purchase explicitly links current intake learning evidence alongside Need/Relationship, preserving the original source and immutable acceptance. Current source access is checked again on every read. Learning stays **Developing**, never Ready, and cannot supply cash, loan, decision-maker, property-fit or booking readiness. Later purchases do not inherit this first-purchase association.
- **Discovery:** `EngagementCohortCandidates` adds valid paid members and previous lost sales to the existing discovery pass, including quiet customers. The existing inactive-user, merged-identity, fake-lead and staff exclusions remain. Ingestion creates an **unactivated intake** with no primary action or purchase opportunity. Customer do-not-contact remains intact. Refund, subscription and engagement updates wake incremental synchronization; the hourly full pass reconciles quiet records.
- **Learning interpretation:** attendance supports observed learning engagement. Actual understanding still requires teach-back or an assessment. The channel adapter retains a bounded historical attendance source to keep genuine older learning visible without copying every historical segment into the journey event store.

The compact DTO is `row.engagement`: booleans for each cohort; `priority {key,label,rank}`; `membership {payment_count,receipt_count,recorded_payment_count,totals,latest_paid_at,payments}`; `webinars {count,total_seconds,total_minutes,unknown_duration_count,latest_topic,latest_at,sessions}`; `lost_sales {count,items}`; source links and short explanatory `signals`. Lists of payments, sessions and lost sales are capped at three; their counts and totals cover all eligible rows.

## Related files

- `src/RevenueJourney/Services/CustomerEngagement.php`
- `src/RevenueJourney/Services/EngagementCohortCandidates.php`
- `src/RevenueJourney/Channels/MeasuredWebinarAttendance.php`
- `src/RevenueJourney/Services/JourneySync.php`
- `src/RevenueJourney/Repositories/ChannelIngestionRepository.php`
- `src/RevenueJourney/Channels/CustomerChannelSources.php`
- `tests/Feature/RevenueJourney/CustomerEngagementTest.php`

## Reference usage

The `JourneyWorkspace` queue primes the service alongside a scoped customer batch, then appends the read-only engagement DTO after current readiness and contact restrictions have been presented. Consumers must not convert an engagement rank or a payment amount into a readiness score.
