The business problem
B2B sales teams spend 50% of their time on leads that never convert. With average deal sizes of $25K-$500K and sales cycles of 3-12 months, misallocating rep time is extraordinarily expensive. A good lead scoring model routes the right leads to the right reps at the right time, directly increasing pipeline velocity and close rates.
Traditional lead scoring assigns points based on individual attributes: job title (+10), company size >1000 (+15), downloaded whitepaper (+5). These rules are static, manually maintained, and ignore the relational context. A VP of Engineering at a company where three other employees already use your product is fundamentally different from a VP at a greenfield account, but both score the same.
Why flat ML fails
- No account context: A lead is part of a buying committee. Multiple engaged contacts at the same account is a much stronger signal than one engaged contact. Flat models see each lead independently.
- No industry signal: When a category leader adopts your product, their competitors often follow. This peer-influence pattern is a graph signal.
- No champion detection: The “champion” (internal advocate) is often not the decision-maker. GNNs can identify champion patterns by looking at engagement depth across the account graph.
- Temporal blindness: Engagement velocity (3 meetings in 2 weeks vs 3 meetings in 6 months) is a critical signal that flat features compress into a single count.
The relational schema
Node types:
Contact (id, title, department, seniority)
Company (id, industry, size, revenue, tech_stack)
Activity (id, type, timestamp, duration, sentiment)
Opportunity (id, stage, amount, close_date)
Edge types:
Contact --[works_at]--> Company
Contact --[had_activity]--> Activity (timestamp)
Activity --[related_to]--> Opportunity
Company --[industry_peer]--> Company
Company --[similar_to]--> Company (tech_overlap)Four node types capture the full B2B context: who the lead is, where they work, what they've done, and how their company relates to your existing customers.
PyG architecture: HeteroConv for CRM data
import torch
import torch.nn.functional as F
from torch_geometric.nn import HeteroConv, SAGEConv, Linear
class LeadScoringGNN(torch.nn.Module):
def __init__(self, hidden_dim=64):
super().__init__()
self.contact_lin = Linear(-1, hidden_dim)
self.company_lin = Linear(-1, hidden_dim)
self.activity_lin = Linear(-1, hidden_dim)
self.opp_lin = Linear(-1, hidden_dim)
self.conv1 = HeteroConv({
('contact', 'works_at', 'company'): SAGEConv(
hidden_dim, hidden_dim),
('contact', 'had_activity', 'activity'): SAGEConv(
hidden_dim, hidden_dim),
('activity', 'related_to', 'opportunity'): SAGEConv(
hidden_dim, hidden_dim),
('company', 'industry_peer', 'company'): SAGEConv(
hidden_dim, hidden_dim),
}, aggr='sum')
self.conv2 = HeteroConv({
('contact', 'works_at', 'company'): SAGEConv(
hidden_dim, hidden_dim),
('contact', 'had_activity', 'activity'): SAGEConv(
hidden_dim, hidden_dim),
('activity', 'related_to', 'opportunity'): SAGEConv(
hidden_dim, hidden_dim),
('company', 'industry_peer', 'company'): SAGEConv(
hidden_dim, hidden_dim),
}, aggr='sum')
self.classifier = torch.nn.Sequential(
Linear(hidden_dim, 32),
torch.nn.ReLU(),
Linear(32, 1),
)
def forward(self, x_dict, edge_index_dict):
x_dict['contact'] = self.contact_lin(x_dict['contact'])
x_dict['company'] = self.company_lin(x_dict['company'])
x_dict['activity'] = self.activity_lin(x_dict['activity'])
x_dict['opportunity'] = self.opp_lin(x_dict['opportunity'])
x_dict = {k: F.relu(v) for k, v in
self.conv1(x_dict, edge_index_dict).items()}
x_dict = self.conv2(x_dict, edge_index_dict)
return torch.sigmoid(
self.classifier(x_dict['contact']).squeeze(-1))HeteroConv with SAGEConv per edge type. Two hops let each contact aggregate company context, industry signals, and engagement patterns from colleagues.
Training considerations
- Label definition: Define conversion as “opportunity created within 90 days” or “closed-won within 180 days.” The choice depends on your sales cycle length.
- CRM data quality: CRM data is notoriously incomplete. Missing activities, outdated contacts, and duplicate companies degrade model performance. Clean data before building the graph.
- Temporal leakage: Only use activities and opportunities that occurred before the prediction date. Future engagement is not available at scoring time.
- Class balance: B2B conversion rates are typically 1-5%. Use focal loss or weighted sampling to handle the imbalance.
Expected performance
- Rule-based scoring: ~50 AUROC
- LightGBM (flat-table): 62.44 AUROC
- GNN (HeteroConv): 75.83 AUROC
- KumoRFM (zero-shot): 76.71 AUROC
Or use KumoRFM in one line
PREDICT will_convert FOR contact
USING contact, company, activity, opportunityOne PQL query. KumoRFM connects to your CRM, constructs the heterogeneous graph, and scores every lead with account-level context.
KumoRFM replaces graph construction, feature engineering, model training, and CRM data cleaning with a single query. It captures account-level buying signals, industry peer influence, and engagement velocity automatically.