Skip to content
TheThinkLoop

Template - Customer health

Score

Account health

How healthy a customer account is, from written account notes.

A score decision that turns free-text account notes and call summaries into a consistent three-level health rating.

The decision

ScoreA position on the ordered levels (0 = first level), a confidence value, and a probability for every level.

How healthy is this customer account?

Levels, lowest to highest

  1. 0At risk: Declining usage, lost champion, payment problems, or stated dissatisfaction.
  2. 1Needs attention: Mixed signals, or too little information to tell.
  3. 2Healthy: Growing or stable usage, engaged champion, renewals on track.

Evidence checks (asked in the same request)

  • Usage declining: Product usage is declining or low.
  • Cancellation intent: The customer says they intend to cancel or not renew.

Expected output: A score from 0 to 2, a probability for each level, and a confidence value, plus one probability per evidence check.

Input

The Decisions API reads one input. Combine these fields into it as labeled text so the model sees the facts the decision depends on.

Input fields
FieldTypeRequiredDescription
notesstringYesAccount notes, a QBR summary, or a call recap.
usage_summarystringNoA sentence of usage data, such as "weekly active users down 20% in 60 days".

Worked examples

  1. Growing usage and a signed renewal

    QBR notes: usage is up 30% quarter over quarter, they added 15 seats in May, and their champion is now VP of Support. Renewal signed for two years.

    A careful reviewer would say: Healthy. Growth, expansion, a promoted champion, and a signed renewal.

    Run this example
  2. Falling usage and a lost champion

    Account notes: usage has dropped since April, only a few logins last month, their champion left the company, and there's an overdue invoice.

    A careful reviewer would say: At risk. Every signal is negative: usage, engagement, sponsorship, and payment.

    Run this example

Expected answers are human judgments written for this template, not model output.

How it works

Health scores built from usage metrics miss what account managers write down after calls. This template scores that text, so qualitative signals can sit beside the quantitative ones.

The middle level explicitly includes "too little information to tell". Thin notes should land there, not get a confident rating.

Human review and thresholds

Recommended review policy

Use the score to prioritize attention, not to take action on an account. A change from Healthy to At risk should prompt a person to look.

Choosing thresholds

Because no automated action follows, thresholds here decide what appears on someone's list. Set them by how many accounts your team can meaningfully review each week.

Starting automation threshold
80%
Starting don’t-decide threshold
50%

Starting points for the playground, not recommendations. The right values depend on your costs and on how the model’s confidence tracks accuracy on your data. Calibrate them.

Limitations

  • Notes reflect the author's optimism. An account manager who writes "great call!" about every account will produce healthy scores.
  • This rates the text, not the account. Combine it with usage and billing data before acting.
  • Health levels are not forecasts. Do not present them as renewal probabilities.

Use it in your code

The template definition is plain JSON: the question, the evidence checks, the input fields, and the starting policy. The SDK call below sends the same question and checks to the Decisions API from your server.

account-health.jsonjson
{
  "$format": "thethinkloop.template/v1",
  "slug": "account-health",
  "title": "Account health",
  "decides": "How healthy a customer account is, from written account notes.",
  "question": {
    "name": "decision",
    "type": "score",
    "instructions": "How healthy is this customer account?",
    "levels": [
      {
        "label": "At risk",
        "description": "Declining usage, lost champion, payment problems, or stated dissatisfaction."
      },
      {
        "label": "Needs attention",
        "description": "Mixed signals, or too little information to tell."
      },
      {
        "label": "Healthy",
        "description": "Growing or stable usage, engaged champion, renewals on track."
      }
    ]
  },
  "evidenceChecks": [
    {
      "name": "factor_1",
      "label": "Usage declining",
      "type": "predicate",
      "instructions": "Product usage is declining or low."
    },
    {
      "name": "factor_2",
      "label": "Cancellation intent",
      "type": "predicate",
      "instructions": "The customer says they intend to cancel or not renew."
    }
  ],
  "inputs": [
    {
      "name": "notes",
      "type": "string",
      "required": true,
      "description": "Account notes, a QBR summary, or a call recap."
    },
    {
      "name": "usage_summary",
      "type": "string",
      "required": false,
      "description": "A sentence of usage data, such as \"weekly active users down 20% in 60 days\"."
    }
  ],
  "expectedOutput": "A score from 0 to 2, a probability for each level, and a confidence value, plus one probability per evidence check.",
  "examples": [
    {
      "title": "Growing usage and a signed renewal",
      "input": "QBR notes: usage is up 30% quarter over quarter, they added 15 seats in May, and their champion is now VP of Support. Renewal signed for two years.",
      "expected": "Healthy"
    },
    {
      "title": "Falling usage and a lost champion",
      "input": "Account notes: usage has dropped since April, only a few logins last month, their champion left the company, and there's an overdue invoice.",
      "expected": "At risk"
    }
  ],
  "startingPolicy": {
    "automateAt": 0.8,
    "abstainBelow": 0.5,
    "note": "A starting point for exploration, not a recommendation. Calibrate against your own labeled examples."
  }
}
Equivalent SDK calljavascript
import OpenAI from "openai"; // openai >= 7.30.0

const client = new OpenAI(); // reads OPENAI_API_KEY on the server

const decision = await client.decisions.create({
  model: "gpt-6-luna",
  input: "QBR notes: usage is up 30% quarter over quarter, they added 15 seats in May, and their champion is now VP of Support. Renewal signed for two years.",
  questions: [
    {
      type: "score",
      name: "decision",
      instructions: "How healthy is this customer account?",
      levels: [
        {
          label: "At risk",
          description: "Declining usage, lost champion, payment problems, or stated dissatisfaction."
        },
        {
          label: "Needs attention",
          description: "Mixed signals, or too little information to tell."
        },
        {
          label: "Healthy",
          description: "Growing or stable usage, engaged champion, renewals on track."
        }
      ]
    },
    {
      type: "predicate",
      name: "factor_1",
      instructions: "Product usage is declining or low."
    },
    {
      type: "predicate",
      name: "factor_2",
      instructions: "The customer says they intend to cancel or not renew."
    }
  ],
});

// Answers come back in question order. A question can also be refused.
const [main, ...checks] = decision.answers;
if (main.type === "refusal") throw new Error("The model declined to answer.");
if (main.type === "score") {
  console.log(main.score, main.confidence, main.probabilities);
}

Download it as account-health.json, or browse all developer resources.

Need this decision tuned to your policy?

We are running a small number of pilots with B2B teams: your policy, your examples, your thresholds. Request early access.