---
name: churn-risk-summarizer
description: Produce a daily (or weekly) digest of accounts that crossed a churn-risk threshold in the last N hours. Aggregates Gainsight risk-score deltas, recent timeline events, and ARR exposure into a tightly bucketed Slack-ready summary that names the change driver and one specific next action per account.
---
# Churn-risk summarizer
## When to invoke
Run on a schedule (typically 7am local, daily) or on demand to generate a prioritized list of accounts that crossed a churn-risk threshold in the trailing window. Input is a Gainsight account list with risk signals plus recent timeline activity; output is a bucketed digest sized for a CSM team to read in under three minutes.
Do NOT invoke this skill for:
- Triggering automated CSM actions (creating tasks, sending playbook emails, opening cases). The digest is read-only signal — humans decide the next action.
- Any customer-facing communication. Nothing this skill produces is cleared for sending to the customer; treat all output as internal.
- Backfilling historical churn analysis (the prompt is tuned for the trailing 24-168 hours). For longitudinal review, use BI on the Gainsight warehouse instead.
- Risk scoring itself. The skill summarizes scores it is given; it does not compute or reweight them.
## Inputs
- Required: `accounts` — JSON array of account records with at minimum `id`, `name`, `arr`, `risk_score_current`, `risk_score_prior`, `health_score_current`, `health_score_prior`, `owner_email`, `segment`, `renewal_date`.
- Required: `events` — JSON array of timeline events for the same accounts in the trailing window. Each event needs `account_id`, `type` (one of `usage_drop`, `support_escalation`, `sponsor_change`, `qbr_missed`, `nps_detractor`, `contract_renegotiation`, `exec_disengagement`), `severity` (1-5), `occurred_at`, `summary`.
- Optional: `window_hours` — lookback window. Defaults to 24.
- Optional: `min_arr` — drop accounts below this ARR floor. Defaults to 0 (no floor).
- Optional: `segments` — filter to a list of segments (e.g. `["enterprise", "mid-market"]`). Defaults to all.
- Optional: `cap` — maximum accounts to surface in the digest body. Defaults to 15. Overflow goes into a "+N more" link.
## Reference files
Read these from `references/` before generating the digest. They encode the team's risk-weighting opinion and the digest format. Without them, the output is generic.
- `references/1-risk-signal-weights.md` — per-event-type weighting config the skill applies when ranking accounts within a bucket.
- `references/2-sample-digest.md` — the literal Slack-ready digest format the skill emits, with a worked example.
- `references/3-escalation-criteria-thresholds.md` — the bucket thresholds (red / amber / watch) and the rules for when a single signal is enough to escalate by itself.
## Method
Run these five steps in order. Do not parallelize: bucketing depends on aggregation, narrative depends on bucketing.
### 1. Signal aggregation
For each account, collect the trailing-window events from `events`, sort by `occurred_at` descending, and compute a per-account `signal_score` as the sum of `severity * weight` per event using the weights in `references/1-risk-signal-weights.md`. Cap any single event's contribution at 5 — one escalation should not single-handedly dominate the score unless the weights file says so explicitly.
The reason for explicit weighting (rather than letting the model "decide what's important"): weights are auditable. When a CSM lead disagrees with what got surfaced, they can edit one number in the weights file. A per-run model judgment cannot be edited.
### 2. Threshold-based bucketing
Apply the thresholds in `references/3-escalation-criteria-thresholds.md` to assign each account to exactly one bucket:
- **Red** — risk_score crossed the explicit churn-risk line, OR signal_score >= the red threshold, OR a single event is on the always-escalate list (e.g. `exec_disengagement` at severity 5).
- **Amber** — health_score dropped by more than the amber delta, OR signal_score is between amber and red.
- **Watch** — any other accounts that crossed *into* the band but do not meet amber criteria.
If `min_arr` or `segments` filters drop an account below the floor, exclude it from all buckets — but record the count for the footer.
### 3. Per-account evidence-grounded narrative
For each account in Red and Amber (skip Watch — it gets a count-only line), compose:
- One-line change driver naming the dominant event, never paraphrased away from `events[].summary`. If the dominant event was a usage drop, say "active seats fell from 142 to 89 over 7 days," not "engagement is declining."
- One concrete suggested action, formatted as a verb plus a named artifact (a meeting, a person, a doc). Examples: "Call the CFO before Friday's renewal kickoff." "Open a case with support to triage the open P1." "Forward last week's QBR deck to the new VP of Eng."
- If no concrete action can be supported from the evidence, output `needs human review` rather than padding. Vague actions are the primary failure mode the digest exists to avoid.
### 4. Prioritization
Within each bucket, sort by ARR descending, breaking ties by `renewal_date` ascending (closer renewals first). Apply `cap` to the combined Red + Amber list. If the cap drops accounts, surface them only as a count and a link to the full Gainsight saved view.
### 5. Format and emit
Render to the layout in `references/2-sample-digest.md`. The output is a single Slack-mrkdwn block plus a fallback plaintext copy. Do not include tables, attachments, or threads — the digest must be scannable in the channel without expanding anything.
## Output format
```markdown
*Daily churn-risk digest — {YYYY-MM-DD}*
*Red ({n_red})* — act this week
- *{Account name}* — ${ARR}k ARR · owner @{owner_handle} · renewal {renewal_date}
Driver: {one-line driver from evidence}
Action: {verb + named artifact}
- ...
*Amber ({n_amber})* — review by Friday
- *{Account name}* — ${ARR}k ARR · owner @{owner_handle} · renewal {renewal_date}
Driver: {one-line driver}
Action: {verb + named artifact, or `needs human review`}
- ...
*Watch ({n_watch})* — no action required, tracking only.
{count summary, no per-account detail}
_Filtered out: {n_below_floor} below ${min_arr}k · {n_below_segment} outside segment._
_Capped at {cap} of {n_red + n_amber} qualifying. Full list: {gainsight_saved_view_url}_
```
## Watch-outs
- **Alert fatigue.** If the digest carries more than ~15 accounts day after day, owners stop opening it. Guard: enforce `cap` strictly, and if Red exceeds cap on three consecutive runs, prepend a `_Threshold may be too loose — last 3 runs averaged {n} Red. Consider raising the red threshold in references/3-escalation-criteria-thresholds.md._` warning. Do not silently truncate without flagging.
- **False-positive flooding.** Any single event type producing more than 30% of Red accounts in a week is a signal that its weight is miscalibrated. Guard: at the end of the digest, include a one-line diagnostic — `_Event-type mix this week: usage_drop 18%, support_escalation 22%, ..._` — so the team can spot one signal dominating before it erodes trust in the digest.
- **Signal weighting drift.** Weights in the references file go stale as the product and customer base change. Guard: include the SHA-256 (first 7 chars) of `references/1-risk-signal-weights.md` in the digest footer. If the footer hash hasn't changed in 90 days, the digest prepends `_Weights file last touched 90+ days ago. Time to recalibrate._`
- **Owner staleness.** If `owner_email` is empty or maps to a former employee, the ping goes to the wrong person and the action does not happen. Guard: any account whose owner handle cannot be resolved gets surfaced under `*Ownership broken ({n})*` instead of Red/Amber, with a link to the Gainsight account ownership editor.
- **Action specificity collapse.** Under load, the model defaults to generic "engage stakeholder" suggestions. Guard: post-process the Action field with a literal substring check — if the action contains any of `engage`, `reach out`, `touch base`, `align`, `socialize` without a named person or artifact, replace it with `needs human review`. Better silence than noise.
# Risk signal weights — TEMPLATE
> Replace these weights with values your CSM lead has signed off on.
> The skill multiplies `severity` (1-5) by the weight below to get
> per-event contribution to `signal_score`. Edit one number at a time
> and watch the next two digests before editing again.
## Per-event-type weights
| Event type | Weight | Notes |
|-------------------------|-------:|-----------------------------------------------------------------|
| `exec_disengagement` | 5.0 | Sponsor stops attending QBRs / unread emails for 30+ days |
| `sponsor_change` | 4.0 | Champion left or moved internally; new owner not yet onboarded |
| `contract_renegotiation`| 3.5 | Procurement opened a contract review outside the renewal window |
| `support_escalation` | 3.0 | P1 case open > 5 business days, or 3+ P2s in 14 days |
| `usage_drop` | 3.0 | Active seats / API calls / features-used down >25% over 14 days |
| `nps_detractor` | 2.0 | NPS <= 6 from any buying-committee role in the last 30 days |
| `qbr_missed` | 2.0 | Cancelled or no-show with no reschedule within 14 days |
## Always-escalate single signals
These trigger Red regardless of `signal_score`. The skill should treat them as a hard override, not a soft boost.
- `exec_disengagement` at severity 5
- `sponsor_change` at severity 4-5 when the renewal date is within 90 days
- `contract_renegotiation` at any severity when ARR > 250k
## Per-event contribution cap
A single event contributes at most 5.0 to `signal_score` regardless of `severity * weight`. This prevents one severity-5 sponsor change from single-handedly flooding the digest with one account at the expense of two genuinely declining ones.
## Weight calibration log
Append every change here so the next person editing this file can see why the numbers are what they are. Format: `YYYY-MM-DD — change — reason`.
- {YYYY-MM-DD} — initial weights — placeholder, replace with team-tuned values
## Last edited
{YYYY-MM-DD}
# Sample digest — worked example
> The skill emits this exact format, Slack-mrkdwn flavored. Treat this
> file as the contract: if you change the layout, change it here first
> and the skill follows. Do not let the model improvise structure.
## Worked example output
```
*Daily churn-risk digest — 2025-11-04*
*Red (3)* — act this week
- *Acme Robotics* — $480k ARR · owner @nadia · renewal 2025-12-18
Driver: VP Eng skipped two scheduled syncs and an automation pilot was paused on 10-29.
Action: Get Nadia a 30-min slot with the new VP Eng before Friday's renewal kickoff.
- *Northwind Logistics* — $310k ARR · owner @marcus · renewal 2026-01-09
Driver: Active seats fell from 142 to 89 over the last 7 days; finance opened a contract review.
Action: Open a discount-modeling thread with the deal desk before the procurement call on 11-07.
- *Globex Health* — $265k ARR · owner @priya · renewal 2026-02-22
Driver: P1 outage case open since 10-30 with no first-response SLA met.
Action: Escalate the open P1 to support leadership and brief Priya before her standing customer call.
*Amber (4)* — review by Friday
- *Initech* — $180k ARR · owner @marcus · renewal 2026-03-14
Driver: Sponsor moved to a new role last week; new owner has not been introduced.
Action: Send the QBR deck and offer a 15-min intro call with the new sponsor.
- *Vandelay Imports* — $140k ARR · owner @nadia · renewal 2026-01-30
Driver: Three P2 cases opened in the last 10 days, all on the reporting module.
Action: needs human review
- *Soylent Foods* — $115k ARR · owner @priya · renewal 2026-04-02
Driver: NPS dropped to 4 from the Director of Ops on 10-31.
Action: Forward the survey verbatim to Priya and request a follow-up 1:1 with the Director.
- *Pied Piper* — $95k ARR · owner @marcus · renewal 2026-02-11
Driver: QBR cancelled 10-28, no reschedule.
Action: Propose three slots for next week and copy the original sponsor.
*Watch (6)* — no action required, tracking only.
6 accounts crossed into the watch band; signal not strong enough for action this week.
_Filtered out: 2 below $50k · 0 outside segment._
_Capped at 13 of 13 qualifying. Full list: https://gainsight.example.com/views/churn-risk-trailing-24h_
_Event-type mix this week: usage_drop 24%, support_escalation 28%, sponsor_change 14%, qbr_missed 12%, nps_detractor 10%, exec_disengagement 6%, contract_renegotiation 6%._
_Weights file: 1-risk-signal-weights.md @ a3f9c12_
```
## Notes for the skill
- Account names are bolded with single asterisks (Slack-mrkdwn).
- The `Action:` line is the *only* place where a generic phrase (`engage`, `align`, `socialize`, `reach out`, `touch base`) is rejected and replaced with `needs human review`.
- The Watch bucket never emits per-account lines. Only the count.
- The footer carries three diagnostics — filter counts, event-type mix, and the weights-file hash — that exist to make miscalibration visible early. Do not drop them to save lines.
# Escalation criteria thresholds — TEMPLATE
> Replace these defaults with the values your CSM lead has signed off
> on. Aim for 5-15 total Red+Amber accounts per daily run; if you are
> consistently above or below, the thresholds are wrong, not the team.
## Bucket definitions
An account lands in exactly one bucket per run. The skill evaluates in this order and stops at the first match.
### Red — act this week
ANY of the following:
- `risk_score_current` crossed the explicit churn-risk line set in Gainsight (e.g. moved from "Medium" to "High", or numeric score crossed below 40), AND `risk_score_prior` was on the safe side of that line within the trailing window.
- `signal_score` >= **12.0** (sum of `severity * weight` across trailing-window events, per `1-risk-signal-weights.md`).
- A single event matches the always-escalate list in `1-risk-signal-weights.md` ("Always-escalate single signals").
### Amber — review by Friday
ANY of the following, and NOT already Red:
- `health_score_current - health_score_prior` <= **-15** in the trailing window.
- `signal_score` between **6.0** and **12.0**.
### Watch — count only
Crossed into the band (any negative movement on either score) but does not meet Amber criteria. Surfaced as a count only.
## Filters
Applied before bucketing. Filtered accounts are excluded from all buckets and reported in the footer.
- `min_arr` — drop accounts with `arr` < this value. Default **0**. Most teams that send to a channel set this to **50** (k$) to keep the digest scannable.
- `segments` — restrict to a list of `segment` values. Default: all segments. Most teams running a daily digest restrict to `["enterprise", "mid-market"]` and run a separate weekly digest for SMB to keep the daily list focused on accounts that justify a human-touch action.
## Cap and overflow
`cap` = **15** by default. After Red+Amber are sorted by ARR descending, anything beyond the cap goes into the footer count + a link to the saved Gainsight view. Do not silently drop accounts — the count must always be honest.
## Self-tuning trigger
If Red exceeds `cap` on three consecutive runs, the skill prepends a warning to the digest noting the threshold may be too loose. The skill does NOT auto-edit this file. Threshold edits are a human decision; the skill only surfaces the signal.
## Threshold change log
Append every change so the next person editing this file can see why the numbers are what they are. Format: `YYYY-MM-DD — change — reason`.
- {YYYY-MM-DD} — initial thresholds — placeholder, replace with team-tuned values
## Last edited
{YYYY-MM-DD}