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:
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:
| Signal | Default weight |
|---|---|
activity_recency | 0.20 |
engagement_frequency | 0.15 |
pipeline_health | 0.15 |
payment_status | 0.15 |
contact_count_active | 0.10 |
executive_engagement | 0.10 |
support_ticket_count | 0.05 |
nps_score | 0.05 |
account_growth_signal | 0.05 |
engagement_velocity_trend | 0.0 |
sentiment_trend | 0.0 |
multi_thread_coverage | 0.0 |
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 & path | Purpose |
|---|---|
GET /v1/health/weights | List the effective per-signal weights (defaults + overrides) |
PATCH /v1/health/weights | Admin: 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.
Related surfaces
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.