The contact scoring pipeline
Orbit scores every contact in every tenant — churn risk, intent, propensity, and a lifetime-value estimate — and writes the result to a per-contact row that segments, automations, and dashboards read. The operational guide for training and activating predictive models is CDP predictive models; this page is the concept underneath it: why the pipeline runs daily, how it batches, what the scorer is, and where a score lands. Read it once and “where does this number come from?” stops being a mystery.Section 1 — What the pipeline scores, and what feeds it
Each pass derives a fixed signal set per contact from the tenant’s own transactional tables — the message history, the engagement profile (contact_engagement_profiles), and agent-conversation outcomes — then
blends those signals into four sub-scores:
A rule-based
segment_label — one of champion, high_value_engaged,
engaged, new, passive, at_risk, dormant, lost — is derived
from the score triad and rides on the same row. That label is what the
dashboard Health badge shows, and it always resolves to a value, even
when signals are thin.
The four built-in predictive models the
CDP predictive models guide trains and
activates — churn propensity, conversion intent, lifetime value, and
engagement fatigue — answer operator-facing “who should I target?”
questions on demand. The pipeline on this page is the always-on twin:
it keeps a per-contact score row current for every contact, so
segments and dashboards can filter on scores without running a model
first. The signals overlap (message counts, recency, engagement
velocity, channel breadth); the pipeline’s blend is deterministic and
needs no training population.
Section 2 — Why daily, and how the batch runs
Predictive scores feed slow decisions: campaign targeting, dashboard segments, lifecycle automations. None of those need a score that is milliseconds fresh — yesterday’s score targets today’s campaign just as well. Orbit therefore scores once a day, per tenant, and made that choice deliberately:- Real-time-per-event scoring was rejected. Scoring on every event turns one write into a fan of reads across messages, engagement profiles, and conversations per event — read amplification that grows with traffic and buys nothing a daily score doesn’t already give a campaign.
- A daily pass yields a trend. Recomputing on a fixed cadence makes day-over-day movement comparable — the same cut of signals, one day apart — instead of a jittery per-event number.
- Load stays predictable. Tenants are processed serially, one at a time, so a 50+-tenant fleet never pummels the shared Postgres connection pool. Inside one tenant, the walk parallelises through 500-row batches, and the whole pass is an idempotent upsert — rerunning it writes the same rows, so a retry is never harmful.
Section 3 — The auditable scorer, and where scores land
The scoring algorithm is a deterministic, weighted blend — not a trained black box. Each sub-score is a fixed-weight combination of normalized signals, and every input signal is stamped into the row’ssignals
JSONB column. Any “why is this contact flagged at-risk?” question has
a concrete answer you can read off the row: recency, reply counts,
velocity, sample depth. There is no “trust the model” hand-wave — the
score is auditable by construction.
Results persist in the tenant schema’s contact_scores table, one row
per contact:
Tenant-schema isolation applies here exactly as in
Tenant isolation: scores live in
tenant_<id>.contact_scores, never in a shared table, and one tenant’s
scoring cannot read another’s rows.
Section 4 — The on-demand pass for fresh contacts
A daily cadence leaves one visible gap: a contact imported two minutes ago has no score row until the next daily tick, so its Health badge reads Pending for up to 24 hours. To close that gap, Orbit fires a per-tenant on-demand scoring job the moment an import completes or a bulk contact create succeeds. That job runs the same scorer, scoped to just the new contact ids, and idempotently upserts the samecontact_scores rows — so a same-contact retry collapses to one write
and re-scoring later is harmless.
The daily pass and the on-demand pass are additive, not alternatives:
the daily sweep keeps every contact current and converges anything the
scoped jobs skipped; the on-demand jobs remove first-touch latency for
the contacts you just brought in.
Section 5 — Where scores surface
Score rows are inputs; the surfaces that read them are the payoff:
Because all of these read the same
contact_scores row, they never
disagree about a contact’s current standing — the daily pass is the
single writer, and everything downstream is a reader.
Cross-references
- CDP predictive models — the operational guide: catalog, train, tier, activate.
- The CDP event model — the upstream event stream the engagement signals derive from.
- How CDP segments recompute — how score-driven segment membership converges downstream.
- Tenant isolation — why
tenant_<id>.contact_scoresis per-tenant.