Score Leads
Tier leads as Hot / Warm / Cold with a response-time SLA tuned to the use case. Calls get_gtm_context(detailed: true) unconditionally, resolves leads by email (deterministic) or name+company (surface ambiguity), scores on four axes, and presents a scannable per-lead output with a specific "why now" reasoning snippet so the rep can trust the tier.
The bar
- Tier and SLA are the first thing the rep sees — not buried under TL;DR or component breakdown.
- Resolution accuracy 100% — every input bucketed; email typos fail loudly, never silent fallback to name search.
- Every Hot lead carries verified contact data — phone + accuracy score visible. Bad data on a Hot lead = dial-the-wrong-number failure.
- Every tier comes with a concrete next action — "Direct dial 555-1234. Lead with [signal]." Not "engage promptly."
- Every lead carries a "why now" reasoning snippet — citing the specific axis driver (person seat × source × fresh trigger / intent / prior engagement). Never the composite restated; never generic ("strong fit"). Same trust discipline as
score-accounts. - Output scannable in <30 seconds per row. Component breakdown below the fold.
Scope
Scores individual leads, not accounts. Use score-accounts for company-level prioritization. For Hot leads, chain to personalize-email.
Input
- Leads (required) — list of ZI person IDs / emails / name+company rows / mixed CSV.
- Source (recommended) —
demo_request,pricing_inquiry,free_trial,product_signup,content_download_high_intent,content_download_low_intent,webinar_attended,webinar_registered,newsletter_subscribe,cold_inbound,unknown. If missing, ask once then default tounknown(source = 50, flagged). - Use case (default
inbound_routing) —inbound_routing,event_followup,pql_triage,content_follow_up. Drives SLA tuning. - Weight overrides (optional) —
{person, account, source, trigger}summing to 100. - Tier thresholds (optional) —
{Hot, Warm}. Cold is the remainder.
Four-axis framework
Weights overridable. Each axis 0–100; composite is the weighted sum.
Tier + SLA (varies by use case)
SLA defaults below. inbound_routing is the live-triage motion where speed-to-lead dominates; other motions relax accordingly. Pick what fits — don't manufacture urgency the motion doesn't need.
For high-intent sources (demo_request, pricing_inquiry, free_trial) in live-triage mode, fast response materially lifts qualification rate. Outside live-triage, the right SLA is longer.
Resolution (four-bucket, lead-specific)
- Auto-resolved — high confidence; score immediately.
- Verified — match found with caveats (common name at large co); surface verification note.
- Ambiguous — multiple plausible matches, no clear winner; pause scoring.
- Failed — no match. Never silently fall back to alternate identifier paths.
Routing by type:
- Numeric person ID → auto-resolved.
- Email →
enrich_contacts(email). Email is a unique identifier. Match → auto-resolved. No match → failed. Do NOT auto-route to name search — a typo'd email (e.g.,[email protected]) must not silently resolve to a different real person. - Name + company →
enrich_contacts(firstName/lastName/companyName). Single high-accuracy match → auto-resolved. Multiple plausible → verified with note. No match → failed. - Free-text "John Smith at Acme" → parse and route to name+company path.
100% resolution accuracy is the gate.
Workflow
1. Pull GTM context (always)
get_gtm_context(detailed: true). Capture personas, ICP, strategic priorities (for intent-topic curation).
2. Honor input data first
Use user-supplied source / weights / thresholds / use case. If source is missing on a multi-row list, ask once then default to unknown (50, flagged).
3. Resolve identifiers
Per the four-bucket rules. Batch in groups of ≤10 concurrent.
3.5. Relationship-context pre-flight (mandatory)
Tag each lead's company against GTM context:
competitor⚔️ — inget_gtm_context.competitors. Hard-warn — most inbound from competitors is talent or competitive intel.customer🤝 — inget_gtm_context.customers/proof_bank. Reroute toexpansion/discovery_follow_up.partner🔗 — inget_gtm_context.partners. Co-sell framing.prospect— default.
The relationship tag appears in the headline before the tier emoji.
For Hot leads at customer or competitor companies: pause before pushing to cold-outbound AE; surface the routing question first.
4. Define the intent relevance set (only if trigger weight > 0)
From get_gtm_context.strategicPriorities and offerings, derive 5–10 themes you sell into. enrich_company_signals returns each employer's active intent topics directly — no topic lookup or pre-query — so these themes are the match set for the intent portion of the trigger axis in step 6: a returned topic counts only if it maps to one.
5. Fetch data per lead (parallel, batched ≤10; chunked for large lists)
enrich_contacts(personId, fields: jobTitle, managementLevel, department, contactAccuracyScore, hasDirectPhone, hasMobilePhone, hasEmail, directPhone, mobilePhone, email).enrich_companies(zoominfoCompanyId, fit-scoring fields).enrich_company_signals(zoominfoCompanyIds: [unique employer IDs], signalTypes: ["NEWS", "SCOOP", "INTENT"])for the employers — only if trigger weight > 0. One batched call (≤10 company IDs) covers news, scoops, and intent; dedupe employers across leads so a shared company is fetched once. No pre-filtering on score, category, topic, or date — applied in step 6.
Batch + context-window discipline. Process in chunks of ~25 leads end-to-end (resolve → fetch → score → compose row → write chunk → discard raw payloads) before moving to the next chunk. Don't accumulate full raw enrichment payloads for hundreds of leads in working context — once per-axis scores + the winning signal/topic strings are captured per lead, drop the rest. For >50-lead lists, summarize completed chunks into running totals (tier distribution, top-Hot list, missing-axes counts) and discard their per-lead breakdowns from context.
6. Score each axis
Person fit (0–100) — compare enrich_contacts to get_gtm_context.buyerPersonas:
Account fit (0–100) — industry 30 · employee band 25 · revenue band 20 · geo 15 · business model 10.
Source signal (0–100):
Trigger / intent (0–100) — same logic as score-accounts (news, scoops, and intent come from enrich_company_signals, filtered to the last 90 days by each signal's date), with seat-fit modifier:
event_score = signal_type_weight × recency_factor × seat_fit, wheresignal_type_weightmaps from each signal's newscategoryor scoopscoopType.- Signal weights: 95 (M&A, funding, C-suite hire) · 75 (product launch, hiring surge, earnings) · 55 (partnership, new facility) · 45 (pain-point scoop, other PERSON moves) · 25 (generic press).
- Recency: 0-14d = 1.0 · 14-30d = 0.7 · 30-60d = 0.4 · 60-90d = 0.2 · >90d = 0.
- Seat fit: if event maps to the lead's seat (new CFO → CFO seat; product launch → CRO/CMO seat; hiring surge in dept X → leader of dept X) → 1.0. Otherwise 0.5. Prevents company-level triggers from inflating irrelevant leads.
- Intent: from the
enrich_company_signalsintent topics that map to the relevance set (step 4) withsignalScore≥ 60 in roughly the last 30 days (use each signal'sdate) — matchingscore-accounts—max(signalScore × audienceStrengthFactor). A=1.0 · B=0.85 · C=0.7 · D=0.55 · E=0.4 (fromaudienceStrength). - Take max(trigger event, intent). Cap 100.
7. Compute composite + assign tier
Per Hot/Warm/Cold thresholds.
8. Compose the per-lead row
First 30 seconds of read must contain, in order:
- Relationship tag (if non-default): ⚔️ / 🤝 / 🔗.
- Tier emoji + label.
- SLA — tuned to the use case (see Tier + SLA table).
- Quality flags inline with SLA:
⚠️ verify title (record Xmo old)— whenlastUpdatedDate>6mo.📱 mobile onlyvs☎️ direct line.⚠️ acc <85— low contact-accuracy.
- "Why now" reasoning snippet — one line, anchored on the strongest specific signal:
- Strong person + source + trigger → "VP-Sales seat × demo request 3h ago × Series B closed 8d ago."
- High-source-only → "Pricing inquiry from VP at perfect-ICP company; no fresh trigger."
- Trigger-anchored → "Fresh CFO appointment 5d ago × CFO-seat lead — trigger × seat = direct match."
- Intent-driven → "Spiked on '[topic]' (score 92, audience A) over 14d."
- Engagement-driven (from
account_research) → "Open opp at this account; named champion engaged 6d ago." - Low signal across axes → "Low signal — monitor only." Never restate the composite. Never use generic phrasing ("strong fit and engagement") — that applies to every Hot lead and tells the rep nothing.
- Recommended next action — concrete, with phone number / channel.
- Contact data line — email · phone · accuracy.
Example (stale-but-high-accuracy Hot lead, mobile only, inbound_routing):
Component breakdown shown BELOW THE FOLD.
9. Self-check before output
- ☑ Tier + SLA (use-case-appropriate) visible in the first row of every output.
- ☑ Hot leads have verified phone + accuracy ≥85, or flag fires.
- ☑ "Why now" snippet on every lead — specific axis driver, never composite restated, never generic.
- ☑ Recommended next action is concrete with channel + signal.
- ☑ Component breakdown below the fold.
- ☑ Every input bucketed (auto-resolved / verified / ambiguous / failed) — none silently dropped.
- ☑ Failed emails NOT silently routed to name search.
- ☑ Source missing → flagged in caveats, not silently defaulted.
- ☑ Each row readable in <30s.
- ☑ Batch chunked when N > 25; intermediate payloads dropped from context.
- ☑ Iteration options offered.
10. Present + offer iteration
- Accept — chain to
personalize-emailper Hot. - Adjust weights.
- Tighten / loosen thresholds.
- Refilter — show only Hot, exclude seats.
- Drill into a lead.
- Add leads.
- Backfill source for unknowns.
Re-execute step 5 only when lead list changes; otherwise recompute from cached axis scores.
Anti-patterns — fail-fast checklist
- Long preamble before the tier label.
- Silent email→name fallback on typo'd email.
- Source defaulted without flagging.
- Generic "why now" — "strong fit and engagement" applies to every Hot lead. Each row must cite the specific driver.
- Composite-as-rationale. Re-stating the score number instead of the axis driver.
- Tier without SLA.
- Hot lead with low-accuracy unflagged.
- Component breakdown above the fold.
- No drill-down to
personalize-emailfor Hot. - Auto-accepting ambiguous matches (e.g., 8 same-named contacts at a large enterprise).
- Forcing the live-triage SLA onto a non-live-triage use case. Event follow-up, PQL triage, and content nurture motions have their own SLA bands; using the inbound-routing 5-min framing on them burns rep capacity on the wrong leads.
Fallback rules
get_gtm_contextempty → use user-supplied personas/ICP if any; flag.- Source missing → ask once; else 50 with flag.
- Email no match → failed; do NOT fall back to name search. If domain edit-distance ≤2 from a known-company domain (from GTM context or batch's resolved set), suggest the closest (e.g.,
[email protected]→ "did you mean[email protected]?"). - Name + company multi-match → verified with note OR ambiguous.
enrich_company_signalsreturns no relevant intent and no news/scoops for the employer → trigger = 0; don't pad (absence of trigger is a real score, not a weight-redistribution case).- Contact accuracy <75 → flag on the row; recommend verification before dialing.
Never block tiering on a single missing axis. Never invent contact data or source.
Output Format
TL;DR — Lead Scoring · N leads · Pass [M]
Use case: [restate]. SLA band: [restate]. Weights · Thresholds Hot≥[X] · Warm[Y–Z].
Tier distribution: 🔥 Hot: X · 🌤 Warm: Y · ❄️ Cold: Z · ❌ Unresolved: W.
🔥 Hot leads (SLA per use case):
🔥 Jordan Smith · VP Sales at Acme Corp · [SLA] · Why now: demo request × VP-Sales seat × fresh CEO hire 8d ago · 📞 555-123-4567 · ✉ [email protected] · acc 98 🔥 [next Hot lead...]
Hot listed first.
Resolution Summary
Status legend: ✅ Auto-resolved · 🔍 Verified · ⚠️ Ambiguous · ❌ Failed.
Ranked Lead List
Hot first → Warm → Cold. Each row <30s read.
Component Breakdown (below the fold)
Weights & Axes Used
Recommended Actions per Tier
SLAs adapt to the use case (see Tier + SLA table).
- 🔥 Hot — SLA per use case. Direct dial / personal outreach. Chain to
personalize-email. Verify phone if acc <85. - 🌤 Warm. SDR sequence; multi-touch cadence sized to use case.
- ❄️ Cold. Nurture; content drip; do not call.
Iteration Options
- Accept → chain to
personalize-email. - Adjust weights.
- Tighten thresholds.
- Backfill source for unknowns.
- Drill into a lead.
- Add leads.
Caveats (when relevant)
- Source missing on N leads — defaulted to 50; backfill for precision.
- Failed resolutions — N unresolved; review.
- Low contact accuracy on Hot leads — N have acc <85; verify phone before dialing.
- Signal depth —
enrich_company_signalsreturns the most recent signals per type, so the trigger axis reflects the most recent window for very active employers. - GTM-context gap — personas sparse; person-fit reduced.
- Stale records — N leads' records >12mo old; current title may have changed.
Final Filter + Weight Set (on accept)
Chain Targets
personalize-emailper Hot lead → drafts grounded in the same axis driver that tiered the lead.score-accountson the leads' companies → company-level prioritization alignment.find-similaron a Hot lead → lookalike prospects at same / similar companies.

