Lead and event scoring
Score how ready an account is to buy from its recent product events, and pick the next sales action.
Score how ready an account is to buy from its recent product events, and pick the next sales action. Pattern: confidence_routing. Model: jev-1.13.0. Outage rule: review.
When to use it
- Product analytics shows what trial or free accounts do, and sales wants to know who to call.
- A salesperson can read an account's recent events and say "call them" or "not yet" in a few seconds.
When not to use it
- You need a personalized outreach email. That is text generation; use this set to decide who gets one.
- Your scoring rule is arithmetic, such as points per event with a cutoff. Keep it in code.
- You want to count events or compare dates. Do that in code and pass the results in the state.
Questions
| Id | Type | Asks | Answers | Gating |
|---|---|---|---|---|
buying_intent | score | How strongly do the recent product events of this account show that it intends to buy or upgrade? | 4 levels | no |
next_action | choice | Given the recent product events of this account, which sales action fits best right now? | sales_call, send_docs, nurture_email, none_needed | yes |
Reading the result
- Pass a short, pre-filtered list of recent events. Drop page views and noise in code first.
- Pass counts and time windows your code already computed (for example
seats_invited) as fields, instead of asking the model to count. - Act on
next_actiononly whenoverallActionisauto.buying_intentdoes not gate; use it to sort lists. - Replace the
next_actionoptions with the plays your team runs, and keepnone_needed.
Spec
Save it as bandwise/sets/lead-event-scoring.json and edit it for your data.
{
"schemaVersion": 1,
"model": "jev-1.13.0",
"input": {
"schema": {
"type": "object",
"required": [
"account",
"events"
],
"properties": {
"account": {
"type": "object",
"required": [
"plan"
],
"properties": {
"company": {
"type": "string"
},
"plan": {
"type": "string",
"enum": [
"free",
"trial",
"paid"
]
},
"seats_invited": {
"type": "integer",
"minimum": 0
}
}
},
"events": {
"type": "array",
"maxItems": 50,
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"detail": {
"type": "string"
}
}
}
}
}
}
},
"stages": [
{
"id": "score",
"questions": {
"buying_intent": {
"type": "score",
"instructions": "How strongly do the recent product `events` of this `account` show that it intends to buy or upgrade?",
"criteria": [
"None. Casual or one-off use.",
"Exploring. Trying features, no sign of a team or a budget.",
"Evaluating. Several people involved, or they looked at pricing, security or limits.",
"Ready. Clear buying signals such as hitting plan limits, asking for a quote, or starting checkout."
],
"meta": {
"label": "Buying intent"
}
},
"next_action": {
"type": "choice",
"instructions": "Given the recent product `events` of this `account`, which sales action fits best right now?",
"criteria": {
"sales_call": "The account shows strong intent and would benefit from talking to a person now.",
"send_docs": "The account is evaluating and has a specific open question, such as security, pricing or limits.",
"nurture_email": "The account is active but early. A helpful email keeps it moving.",
"none_needed": "Nothing in the events calls for outreach yet."
},
"meta": {
"label": "Next action"
}
}
}
}
],
"policies": {
"buying_intent": {
"type": "score",
"gating": false,
"thresholds": {
"high": 0.6,
"medium": 0.35
},
"actions": {
"high": {
"kind": "auto"
},
"medium": {
"kind": "auto"
},
"low": {
"kind": "auto"
}
}
},
"next_action": {
"type": "choice",
"gating": true,
"thresholds": {
"high": 0.6,
"medium": 0.35
},
"perOption": {
"sales_call": {
"high": 0.7,
"medium": 0.45
}
},
"actions": {
"high": {
"kind": "auto"
},
"medium": {
"kind": "review"
},
"low": {
"kind": "fallback",
"config": {
"kind": "value",
"value": "none_needed"
}
}
}
}
},
"routes": [
{
"when": {
"q": "next_action",
"eq": "sales_call"
},
"output": "sales_call"
},
{
"when": {
"q": "next_action",
"eq": "send_docs"
},
"output": "send_docs"
},
{
"when": {
"q": "next_action",
"eq": "nurture_email"
},
"output": "nurture_email"
}
],
"defaultRoute": "no_action",
"savings": {
"comparatorModel": "claude-haiku-4-5",
"estOutputTokensPerQuestion": 60,
"kind": "decision"
},
"onUnavailable": "review"
}Example states
The expected outcome is what a person would decide. It is not a recorded model answer.
Trial account hitting limits
Expected: High intent, sales_call.
{
"account": {
"company": "Northwind Analytics",
"plan": "trial",
"seats_invited": 7
},
"events": [
{
"name": "plan_limit_reached",
"detail": "projects"
},
{
"name": "pricing_page_viewed"
},
{
"name": "sso_settings_opened"
},
{
"name": "checkout_started",
"detail": "team plan"
}
]
}Single user poking around
Expected: Low intent, none_needed or nurture_email.
{
"account": {
"plan": "free",
"seats_invited": 0
},
"events": [
{
"name": "signed_up"
},
{
"name": "sample_project_opened"
}
]
}Borderline cases
One case near the line for each question. Use them to test your wording before you trust the thresholds.
buying_intent
Heavy usage by one person on the free plan: engaged, but no sign of a team or a budget.
{
"account": {
"plan": "free",
"seats_invited": 0
},
"events": [
{
"name": "project_created"
},
{
"name": "export_run",
"detail": "csv"
},
{
"name": "api_key_created"
},
{
"name": "project_created"
}
]
}next_action
They opened the security page and invited teammates: send docs or call?
{
"account": {
"company": "Contoso Health",
"plan": "trial",
"seats_invited": 3
},
"events": [
{
"name": "security_page_viewed"
},
{
"name": "teammate_invited"
},
{
"name": "dpa_downloaded"
}
]
}Try it
Save an example state as state.json, then run the spec locally. Local mode makes no network call and needs no key; answers are synthetic unless a recorded fixture matches.
pnpm bandwise run --local bandwise/sets/lead-event-scoring.json state.jsonBandwise is an independent product built on TypeSafe's System One models. It is not TypeSafe's documentation. For the System One models themselves, see docs.typesafe.ai.