Sales Research
--- name: sales-research description: This skill provides methodology and best practices for researching sales prospects. --- # Sales Research ## Overview This skill provides methodology and best p
Description
name: sales-research description: This skill provides methodology and best practices for researching sales prospects.
Sales Research
Overview
This skill provides methodology and best practices for researching sales prospects. It covers company research, contact profiling, and signal detection to surface actionable intelligence.
Usage
The company-researcher and contact-researcher sub-agents reference this skill when:
- Researching new prospects
- Finding company information
- Profiling individual contacts
- Detecting buying signals
Research Methodology
Company Research Checklist
-
Basic Profile
- Company name, industry, size (employees, revenue)
- Headquarters and key locations
- Founded date, growth stage
-
Recent Developments
- Funding announcements (last 12 months)
- M&A activity
- Leadership changes
- Product launches
-
Tech Stack
- Known technologies (BuiltWith, StackShare)
- Job postings mentioning tools
- Integration partnerships
-
Signals
- Job postings (scaling = opportunity)
- Glassdoor reviews (pain points)
- News mentions (context)
- Social media activity
Contact Research Checklist
-
Professional Background
- Current role and tenure
- Previous companies and roles
- Education
-
Influence Indicators
- Reporting structure
- Decision-making authority
- Budget ownership
-
Engagement Hooks
- Recent LinkedIn posts
- Published articles
- Speaking engagements
- Mutual connections
Resources
resources/signal-indicators.md- Taxonomy of buying signalsresources/research-checklist.md- Complete research checklist
Scripts
scripts/company-enricher.py- Aggregate company data from multiple sourcesscripts/linkedin-parser.py- Structure LinkedIn profile data FILE:company-enricher.py #!/usr/bin/env python3 """ company-enricher.py - Aggregate company data from multiple sources
Inputs:
- company_name: string
- domain: string (optional)
Outputs:
- profile: name: string industry: string size: string funding: string tech_stack: [string] recent_news: [news items]
Dependencies:
- requests, beautifulsoup4 """
Requirements: requests, beautifulsoup4
import json from typing import Any from dataclasses import dataclass, asdict from datetime import datetime
@dataclass class NewsItem: title: str date: str source: str url: str summary: str
@dataclass class CompanyProfile: name: str domain: str industry: str size: str location: str founded: str funding: str tech_stack: list[str] recent_news: list[dict] competitors: list[str] description: str
def search_company_info(company_name: str, domain: str = None) -> dict: """ Search for basic company information. In production, this would call APIs like Clearbit, Crunchbase, etc. """ # TODO: Implement actual API calls # Placeholder return structure return { "name": company_name, "domain": domain or f"{company_name.lower().replace(' ', '')}.com", "industry": "Technology", # Would come from API "size": "Unknown", "location": "Unknown", "founded": "Unknown", "description": f"Information about {company_name}" }
def search_funding_info(company_name: str) -> dict: """ Search for funding information. In production, would call Crunchbase, PitchBook, etc. """ # TODO: Implement actual API calls return { "total_funding": "Unknown", "last_round": "Unknown", "last_round_date": "Unknown", "investors": [] }
def search_tech_stack(domain: str) -> list[str]: """ Detect technology stack. In production, would call BuiltWith, Wappalyzer, etc. """ # TODO: Implement actual API calls return []
def search_recent_news(company_name: str, days: int = 90) -> list[dict]: """ Search for recent news about the company. In production, would call news APIs. """ # TODO: Implement actual API calls return []
def main( company_name: str, domain: str = None ) -> dict[str, Any]: """ Aggregate company data from multiple sources.
Args:
company_name: Company name to research
domain: Company domain (optional, will be inferred)
Returns:
dict with company profile including industry, size, funding, tech stack, news
"""
# Get basic company info
basic_info = search_company_info(company_name, domain)
# Get funding information
funding_info = search_funding_info(company_name)
# Detect tech stack
company_domain = basic_info.get("domain", domain)
tech_stack = search_tech_stack(company_domain) if company_domain else []
# Get recent news
news = search_recent_news(company_name)
# Compile profile
profile = CompanyProfile(
name=basic_info["name"],
domain=basic_info["domain"],
industry=basic_info["industry"],
size=basic_info["size"],
location=basic_info["location"],
founded=basic_info["founded"],
funding=funding_info.get("total_funding", "Unknown"),
tech_stack=tech_stack,
recent_news=news,
competitors=[], # Would be enriched from industry analysis
description=basic_info["description"]
)
return {
"profile": asdict(profile),
"funding_details": funding_info,
"enriched_at": datetime.now().isoformat(),
"sources_checked": ["company_info", "funding", "tech_stack", "news"]
}
if name == "main": import sys
# Example usage
result = main(
company_name="DataFlow Systems",
domain="dataflow.io"
)
print(json.dumps(result, indent=2))
FILE:linkedin-parser.py #!/usr/bin/env python3 """ linkedin-parser.py - Structure LinkedIn profile data
Inputs:
- profile_url: string
- or name + company: strings
Outputs:
- contact: name: string title: string tenure: string previous_roles: [role objects] mutual_connections: [string] recent_activity: [post summaries]
Dependencies:
- requests """
Requirements: requests
import json from typing import Any from dataclasses import dataclass, asdict from datetime import datetime
@dataclass class PreviousRole: title: str company: str duration: str description: str
@dataclass class RecentPost: date: str content_preview: str engagement: int topic: str
@dataclass class ContactProfile: name: str title: str company: str location: str tenure: str previous_roles: list[dict] education: list[str] mutual_connections: list[str] recent_activity: list[dict] profile_url: str headline: str
def search_linkedin_profile(name: str = None, company: str = None, profile_url: str = None) -> dict: """ Search for LinkedIn profile information. In production, would use LinkedIn API or Sales Navigator. """ # TODO: Implement actual LinkedIn API integration # Note: LinkedIn's API has strict terms of service
return {
"found": False,
"name": name or "Unknown",
"title": "Unknown",
"company": company or "Unknown",
"location": "Unknown",
"headline": "",
"tenure": "Unknown",
"profile_url": profile_url or ""
}
def get_career_history(profile_data: dict) -> list[dict]: """ Extract career history from profile. """ # TODO: Implement career extraction return []
def get_mutual_connections(profile_data: dict, user_network: list = None) -> list[str]: """ Find mutual connections. """ # TODO: Implement mutual connection detection return []
def get_recent_activity(profile_data: dict, days: int = 30) -> list[dict]: """ Get recent posts and activity. """ # TODO: Implement activity extraction return []
def main( name: str = None, company: str = None, profile_url: str = None ) -> dict[str, Any]: """ Structure LinkedIn profile data for sales prep.
Args:
name: Person's name
company: Company they work at
profile_url: Direct LinkedIn profile URL
Returns:
dict with structured contact profile
"""
if not profile_url and not (name and company):
return {"error": "Provide either profile_url or name + company"}
# Search for profile
profile_data = search_linkedin_profile(
name=name,
company=company,
profile_url=profile_url
)
if not profile_data.get("found"):
return {
"found": False,
"name": name or "Unknown",
"company": company or "Unknown",
"message": "Profile not found or limited access",
"suggestions": [
"Try searching directly on LinkedIn",
"Check for alternative spellings",
"Verify the person still works at this company"
]
}
# Get career history
previous_roles = get_career_history(profile_data)
# Find mutual connections
mutual_connections = get_mutual_connections(profile_data)
# Get recent activity
recent_activity = get_recent_activity(profile_data)
# Compile contact profile
contact = ContactProfile(
name=profile_data["name"],
title=profile_data["title"],
company=profile_data["company"],
location=profile_data["location"],
tenure=profile_data["tenure"],
previous_roles=previous_roles,
education=[], # Would be extracted from profile
mutual_connections=mutual_connections,
recent_activity=recent_activity,
profile_url=profile_data["profile_url"],
headline=profile_data["headline"]
)
return {
"found": True,
"contact": asdict(contact),
"research_date": datetime.now().isoformat(),
"data_completeness": calculate_completeness(contact)
}
def calculate_completeness(contact: ContactProfile) -> dict: """Calculate how complete the profile data is.""" fields = { "basic_info": bool(contact.name and contact.title and contact.company), "career_history": len(contact.previous_roles) > 0, "mutual_connections": len(contact.mutual_connections) > 0, "recent_activity": len(contact.recent_activity) > 0, "education": len(contact.education) > 0 }
complete_count = sum(fields.values())
return {
"fields": fields,
"score": f"{complete_count}/{len(fields)}",
"percentage": int((complete_count / len(fields)) * 100)
}
if name == "main": import sys
# Example usage
result = main(
name="Sarah Chen",
company="DataFlow Systems"
)
print(json.dumps(result, indent=2))
FILE:priority-scorer.py #!/usr/bin/env python3 """ priority-scorer.py - Calculate and rank prospect priorities
Inputs:
- prospects: [prospect objects with signals]
- weights: {deal_size, timing, warmth, signals}
Outputs:
- ranked: [prospects with scores and reasoning]
Dependencies:
- (none - pure Python) """
import json from typing import Any from dataclasses import dataclass
Default scoring weights
DEFAULT_WEIGHTS = { "deal_size": 0.25, "timing": 0.30, "warmth": 0.20, "signals": 0.25 }
Signal score mapping
SIGNAL_SCORES = { # High-intent signals "recent_funding": 10, "leadership_change": 8, "job_postings_relevant": 9, "expansion_news": 7, "competitor_mention": 6,
# Medium-intent signals
"general_hiring": 4,
"industry_event": 3,
"content_engagement": 3,
# Relationship signals
"mutual_connection": 5,
"previous_contact": 6,
"referred_lead": 8,
# Negative signals
"recent_layoffs": -3,
"budget_freeze_mentioned": -5,
"competitor_selected": -7,
}
@dataclass class ScoredProspect: company: str contact: str call_time: str raw_score: float normalized_score: int priority_rank: int score_breakdown: dict reasoning: str is_followup: bool
def score_deal_size(prospect: dict) -> tuple[float, str]: """Score based on estimated deal size.""" size_indicators = prospect.get("size_indicators", {})
employee_count = size_indicators.get("employees", 0)
revenue_estimate = size_indicators.get("revenue", 0)
# Simple scoring based on company size
if employee_count > 1000 or revenue_estimate > 100_000_000:
return 10.0, "Enterprise-scale opportunity"
elif employee_count > 200 or revenue_estimate > 20_000_000:
return 7.0, "Mid-market opportunity"
elif employee_count > 50:
return 5.0, "SMB opportunity"
else:
return 3.0, "Small business"
def score_timing(prospect: dict) -> tuple[float, str]: """Score based on timing signals.""" timing_signals = prospect.get("timing_signals", [])
score = 5.0 # Base score
reasons = []
for signal in timing_signals:
if signal == "budget_cycle_q4":
score += 3
reasons.append("Q4 budget planning")
elif signal == "contract_expiring":
score += 4
reasons.append("Contract expiring soon")
elif signal == "active_evaluation":
score += 5
reasons.append("Actively evaluating")
elif signal == "just_funded":
score += 3
reasons.append("Recently funded")
return min(score, 10.0), "; ".join(reasons) if reasons else "Standard timing"
def score_warmth(prospect: dict) -> tuple[float, str]: """Score based on relationship warmth.""" relationship = prospect.get("relationship", {})
if relationship.get("is_followup"):
last_outcome = relationship.get("last_outcome", "neutral")
if last_outcome == "positive":
return 9.0, "Warm follow-up (positive last contact)"
elif last_outcome == "neutral":
return 7.0, "Follow-up (neutral last contact)"
else:
return 5.0, "Follow-up (needs re-engagement)"
if relationship.get("referred"):
return 8.0, "Referred lead"
if relationship.get("mutual_connections", 0) > 0:
return 6.0, f"{relationship['mutual_connections']} mutual connections"
if relationship.get("inbound"):
return 7.0, "Inbound interest"
return 4.0, "Cold outreach"
def score_signals(prospect: dict) -> tuple[float, str]: """Score based on buying signals detected.""" signals = prospect.get("signals", [])
total_score = 0
signal_reasons = []
for signal in signals:
signal_score = SIGNAL_SCORES.get(signal, 0)
total_score += signal_score
if signal_score > 0:
signal_reasons.append(signal.replace("_", " "))
# Normalize to 0-10 scale
normalized = min(max(total_score / 2, 0), 10)
reason = f"Signals: {', '.join(signal_reasons)}" if signal_reasons else "No strong signals"
return normalized, reason
def calculate_priority_score( prospect: dict, weights: dict = None ) -> ScoredProspect: """Calculate overall priority score for a prospect.""" weights = weights or DEFAULT_WEIGHTS
# Calculate component scores
deal_score, deal_reason = score_deal_size(prospect)
timing_score, timing_reason = score_timing(prospect)
warmth_score, warmth_reason = score_warmth(prospect)
signal_score, signal_reason = score_signals(prospect)
# Weighted total
raw_score = (
deal_score * weights["deal_size"] +
timing_score * weights["timing"] +
warmth_score * weights["warmth"] +
signal_score * weights["signals"]
)
# Compile reasoning
reasons = []
if timing_score >= 8:
reasons.append(timing_reason)
if signal_score >= 7:
reasons.append(signal_reason)
if warmth_score >= 7:
reasons.append(warmth_reason)
if deal_score >= 8:
reasons.append(deal_reason)
return ScoredProspect(
company=prospect.get("company", "Unknown"),
contact=prospect.get("contact", "Unknown"),
call_time=prospect.get("call_time", "Unknown"),
raw_score=round(raw_score, 2),
normalized_score=int(raw_score * 10),
priority_rank=0, # Will be set after sorting
score_breakdown={
"deal_size": {"score": deal_score, "reason": deal_reason},
"timing": {"score": timing_score, "reason": timing_reason},
"warmth": {"score": warmth_score, "reason": warmth_reason},
"signals": {"score": signal_score, "reason": signal_reason}
},
reasoning="; ".join(reasons) if reasons else "Standard priority",
is_followup=prospect.get("relationship", {}).get("is_followup", False)
)
def main( prospects: list[dict], weights: dict = None ) -> dict[str, Any]: """ Calculate and rank prospect priorities.
Args:
prospects: List of prospect objects with signals
weights: Optional custom weights for scoring components
Returns:
dict with ranked prospects and scoring details
"""
weights = weights or DEFAULT_WEIGHTS
# Score all prospects
scored = [calculate_priority_score(p, weights) for p in prospects]
# Sort by raw score descending
scored.sort(key=lambda x: x.raw_score, reverse=True)
# Assign ranks
for i, prospect in enumerate(scored, 1):
prospect.priority_rank = i
# Convert to dicts for JSON serialization
ranked = []
for s in scored:
ranked.append({
"company": s.company,
"contact": s.contact,
"call_time": s.call_time,
"priority_rank": s.priority_rank,
"score": s.normalized_score,
"reasoning": s.reasoning,
"is_followup": s.is_followup,
"breakdown": s.score_breakdown
})
return {
"ranked": ranked,
"weights_used": weights,
"total_prospects": len(prospects)
}
if name == "main": import sys
# Example usage
example_prospects = [
{
"company": "DataFlow Systems",
"contact": "Sarah Chen",
"call_time": "2pm",
"size_indicators": {"employees": 200, "revenue": 25_000_000},
"timing_signals": ["just_funded", "active_evaluation"],
"signals": ["recent_funding", "job_postings_relevant"],
"relationship": {"is_followup": False, "mutual_connections": 2}
},
{
"company": "Acme Manufacturing",
"contact": "Tom Bradley",
"call_time": "10am",
"size_indicators": {"employees": 500},
"timing_signals": ["contract_expiring"],
"signals": [],
"relationship": {"is_followup": True, "last_outcome": "neutral"}
},
{
"company": "FirstRate Financial",
"contact": "Linda Thompson",
"call_time": "4pm",
"size_indicators": {"employees": 300},
"timing_signals": [],
"signals": [],
"relationship": {"is_followup": False}
}
]
result = main(prospects=example_prospects)
print(json.dumps(result, indent=2))
FILE:research-checklist.md
Prospect Research Checklist
Company Research
Basic Information
- Company name (verify spelling)
- Industry/vertical
- Headquarters location
- Employee count (LinkedIn, website)
- Revenue estimate (if available)
- Founded date
- Funding stage/history
Recent News (Last 90 Days)
- Funding announcements
- Acquisitions or mergers
- Leadership changes
- Product launches
- Major customer wins
- Press mentions
- Earnings/financial news
Digital Footprint
- Website review
- Blog/content topics
- Social media presence
- Job postings (careers page + LinkedIn)
- Tech stack (BuiltWith, job postings)
Competitive Landscape
- Known competitors
- Market position
- Differentiators claimed
- Recent competitive moves
Pain Point Indicators
- Glassdoor reviews (themes)
- G2/Capterra reviews (if B2B)
- Social media complaints
- Job posting patterns
Contact Research
Professional Profile
- Current title
- Time in role
- Time at company
- Previous companies
- Previous roles
- Education
Decision Authority
- Reports to whom
- Team size (if manager)
- Budget authority (inferred)
- Buying involvement history
Engagement Hooks
- Recent LinkedIn posts
- Published articles
- Podcast appearances
- Conference talks
- Mutual connections
- Shared interests/groups
Communication Style
- Post tone (formal/casual)
- Topics they engage with
- Response patterns
CRM Check (If Available)
- Any prior touchpoints
- Previous opportunities
- Related contacts at company
- Notes from colleagues
- Email engagement history
Time-Based Research Depth
| Time Available | Research Depth |
|---|---|
| 5 minutes | Company basics + contact title only |
| 15 minutes | + Recent news + LinkedIn profile |
| 30 minutes | + Pain point signals + engagement hooks |
| 60 minutes | Full checklist + competitive analysis |
| FILE:signal-indicators.md |
Signal Indicators Reference
High-Intent Signals
Job Postings
- 3+ relevant roles posted = Active initiative, budget allocated
- Senior hire in your domain = Strategic priority
- Urgency language ("ASAP", "immediate") = Pain is acute
- Specific tool mentioned = Competitor or category awareness
Financial Events
- Series B+ funding = Growth capital, buying power
- IPO preparation = Operational maturity needed
- Acquisition announced = Integration challenges coming
- Revenue milestone PR = Budget available
Leadership Changes
- New CXO in your domain = 90-day priority setting
- New CRO/CMO = Tech stack evaluation likely
- Founder transition to CEO = Professionalizing operations
Medium-Intent Signals
Expansion Signals
- New office opening = Infrastructure needs
- International expansion = Localization, compliance
- New product launch = Scaling challenges
- Major customer win = Delivery pressure
Technology Signals
- RFP published = Active buying process
- Vendor review mentioned = Comparison shopping
- Tech stack change = Integration opportunity
- Legacy system complaints = Modernization need
Content Signals
- Blog post on your topic = Educating themselves
- Webinar attendance = Interest confirmed
- Whitepaper download = Problem awareness
- Conference speaking = Thought leadership, visibility
Low-Intent Signals (Nurture)
General Activity
- Industry event attendance = Market participant
- Generic hiring = Company growing
- Positive press = Healthy company
- Social media activity = Engaged leadership
Signal Scoring
| Signal Type | Score | Action |
|---|---|---|
| Job posting (relevant) | +3 | Prioritize outreach |
| Recent funding | +3 | Reference in conversation |
| Leadership change | +2 | Time-sensitive opportunity |
| Expansion news | +2 | Growth angle |
| Negative reviews | +2 | Pain point angle |
| Content engagement | +1 | Nurture track |
| No signals | 0 | Discovery focus |
Reviews (0)
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!