PPact
Sales

Health weights

Tune the per-signal weights that drive each account's composite customer-health score, with tenant overrides layered over shipped defaults.

Health weights

Every account gets a 0-100 composite health score and a risk band. That score is a weighted average of individual signals, and health weights are the knobs that decide how much each signal counts for your tenant. Weights are managed under /v1/health/weights; the scoring math lives in core.customer_health.health_scorer.

Live

Backed by real code and a real table. Defaults live in DEFAULT_WEIGHTS; per-tenant overrides persist to health_score_weights and are merged in at score time by load_tenant_weights.

How the composite score works

The composite is the weighted average of each signal's sub-score, divided by the total weight — so weights do not need to sum to 1.0, and a missing or zero-weight signal doesn't drag the score down. The result maps to a risk band:

code
score >= 75   → healthy
50 – 74       → watch
25 – 49       → at_risk
0 – 24        → critical

The signals and their default weights

DEFAULT_WEIGHTS ships these values, chosen so the score is most influenced by the things a CSM intuitively cares about — are people responding, is there money in play, are execs engaged:

SignalDefault weight
activity_recency0.20
engagement_frequency0.15
pipeline_health0.15
payment_status0.15
contact_count_active0.10
executive_engagement0.10
support_ticket_count0.05
nps_score0.05
csat_score0.05
account_growth_signal0.05
engagement_velocity_trend0.0
sentiment_trend0.0
multi_thread_coverage0.0

CSAT counts only once someone answers

csat_score averages the 1–5 satisfaction answers an account gave in the last 180 days, directly or on one of its support cases. With no answers it is left out of the composite entirely — its weight neither lowers nor raises the score — so turning on a CSAT survey never moves an account nobody has rated.

Why three signals default to 0.0

engagement_velocity_trend, sentiment_trend, and multi_thread_coverage are the deal + activity data-layer signals. They default to weight 0.0 so they feed the churn model (via the feature vector and SHAP explanations) without shifting the human-facing composite score. A tenant can opt them into the composite by raising their weight override.

Managing weights

Method & pathPurpose
GET /v1/health/weightsList the effective per-signal weights (defaults + overrides)
PATCH /v1/health/weightsAdmin: upsert one or more weight overrides

Overrides are validated: a weight must be >= 0, and unknown signal names are ignored rather than persisted, so the override set can never drift out of sync with the signal registry. Every call derives tenant_id from the auth context; nothing is read from the request body.

The weights feed the same customer_health module that also exposes the at-risk feed (GET /v1/health/feed), the band histogram (GET /v1/health/distribution), save-task review queue, and the churn model endpoints (POST /v1/health/model/retrain, GET /v1/health/model/info). Weight changes affect the composite score immediately; the churn model is retrained separately.

Health weights — Pact Docs · Pact