Docs
Guides

Health score

Design, compute, calibrate, and explain a customer health score.

A health score is a current, explainable summary, not a churn probability. Start with a few independent behaviors that Customer Success can act on.

Define the inputs

Declare every event and trait used by the score:

kite.config.ts
import { defineConfig } from '@kitesdk/config';

export default defineConfig({
  project: 'acme',
  events: {
    'feature.used': {
      properties: { feature: { type: 'string' } },
    },
    'api.error': {},
  },
  customerTraits: {
    availableFeatures: {
      type: 'array',
      items: { type: 'string' },
      optional: true,
    },
    seats: { type: 'number', optional: true },
  },
});

Then combine weighted components:

health/overall.ts
import { defineHealth } from '@kitesdk/config';

export default defineHealth({
  id: 'overall',
  name: 'Overall health',
  components: {
    adoption: {
      name: 'Feature adoption',
      weight: 0.4,
      compute: {
        type: 'feature_adoption',
        features: ['exports', 'dashboards', 'alerts'],
        period: '30d',
        respectPlanFeatures: true,
      },
    },
    engagement: {
      name: 'Engagement',
      weight: 0.4,
      compute: {
        type: 'event_frequency',
        event: 'feature.used',
        period: '30d',
        target: 20,
        minScore: 5,
      },
    },
    stability: {
      name: 'Stability',
      weight: 0.2,
      compute: {
        type: 'inverse_event_count',
        event: 'api.error',
        period: '30d',
        penaltyPerEvent: 5,
        maxScore: 100,
      },
    },
  },
  thresholds: { excellent: 80, good: 60, poor: 30, critical: 0 },
  recomputeOn: [
    { event: 'feature.used' },
    { event: 'api.error' },
    { schedule: '0 0 * * *' },
  ],
});

Kite supports one health definition. Component weights must total 1.0. Components are rounded and clamped to 0100 before weighting, then the weighted total is rounded and clamped again.

Understand each computation

TypeCalculation and edge cases
event_frequencycount / target * 100, capped at 100 and floored by optional minScore.
inverse_event_countmaxScore - count * penaltyPerEvent; maxScore defaults to 100.
feature_adoptionDistinct eligible features observed in feature or features properties divided by the eligible set.
customDeterministic numeric formula using event counts, numeric traits, and prior components.

Feature adoption scans properties across all events in the period; it is not tied to one event name. When respectPlanFeatures is true and availableFeatures is an array, eligibility is the intersection with configured features. If the trait is absent, all configured features are eligible. An empty eligible set scores 0.

Trait updates alone do not necessarily trigger a health recomputation. Include relevant event or schedule triggers, or run a manual recompute after entitlement changes.

Use custom formulas carefully

Components are evaluated by component ID in lexical order. A formula can reference only a component whose ID sorts earlier:

components: {
  a_engagement: {
    name: 'Engagement',
    weight: 0.7,
    compute: {
      type: 'event_frequency',
      event: 'feature.used',
      period: '30d',
      target: 20,
    },
  },
  b_depth: {
    name: 'Depth',
    weight: 0.3,
    compute: {
      type: 'custom',
      formula: ({ events, traits, computed }) =>
        Math.min(
          100,
          events.count('team.member_invited', { days: 60 }) * 10 +
            (traits.seats ?? 0) +
            (computed.a_engagement ?? 0) * 0.25,
        ),
    },
  },
}

Formulas cannot close over external variables or use nondeterministic globals such as Date.now() and Math.random().

Recompute triggers

recomputeOn controls normal event-driven and scheduled refreshes. If it is omitted, those automatic triggers do not update health. Manual recompute always evaluates the score.

Use scheduled recomputation for time-based decay when no new event arrives:

recomputeOn: [
  { event: 'feature.used' },
  { schedule: '0 0 * * *' },
]

Thresholds on the overall score and individual components are validated metadata. The engine does not assign labels from them. Define operational cohorts explicitly with healthScore or healthComponent conditions.

Calibrate and inspect

  1. Choose windows matching the product's natural usage frequency.
  2. Compare component distributions across representative retained and churned cohorts.
  3. Check new customers and low-frequency plans for unfair default scores.
  4. Remove redundant components and cap behaviors one customer can inflate.
  5. Run a dry-run before persisting changed semantics.
  6. Explain individual results after deployment.
kite validate --strict
kite deploy --env test
kite recompute --all --dry-run --env test
kite health explain account_42 --env test
kite customers inspect account_42 --env test --json

Changing a score changes current interpretation. Record the model version and rollout decision, and review Backfill and recomputation before rebuilding live state.

On this page