Исследование потенциальных клиентов
Проверочные списки для изучения компаний и контактных лиц, сигналы готовности к покупке и способы их оценки.
В навыке 6 файлов. Их можно скопировать или скачать по отдельности, сохранив указанные имена и папки.
SKILL.md
--- name: sales-research description: Методика и рекомендации по изучению потенциальных клиентов. --- # Исследование потенциальных клиентов ## Обзор Этот навык помогает изучать потенциальных клиентов: собирать сведения о компаниях и контактных лицах, замечать сигналы готовности к покупке и получать данные, на основе которых можно действовать. ## Применение Субагенты company-researcher и contact-researcher обращаются к этому навыку, когда нужно: - изучить нового потенциального клиента; - найти сведения о компании; - составить профиль контактного лица; - обнаружить сигналы готовности к покупке. ## Методика исследования ### Проверочный список для компании 1. **Основные сведения** - Название, отрасль, размер компании (число сотрудников, выручка). - Штаб-квартира и основные офисы. - Дата основания, этап роста. 2. **Недавние события** - Объявления о финансировании за последние 12 месяцев. - Слияния и поглощения. - Изменения в руководстве. - Запуск продуктов. 3. **Технологический стек** - Известные технологии (BuiltWith, StackShare). - Упоминания инструментов в вакансиях. - Партнёрские интеграции. 4. **Сигналы** - Вакансии: расширение команды может означать возможность для продажи. - Отзывы на Glassdoor: возможные проблемы. - Упоминания в новостях: контекст. - Активность в социальных сетях. ### Проверочный список для контактного лица 1. **Профессиональный опыт** - Текущая должность и срок работы на ней. - Предыдущие компании и должности. - Образование. 2. **Признаки влияния** - Место в структуре подчинения. - Полномочия по принятию решений. - Ответственность за бюджет. 3. **Поводы для первого контакта** - Недавние публикации в LinkedIn. - Опубликованные статьи. - Выступления. - Общие знакомые. ## Ресурсы - `resources/signal-indicators.md` — классификация сигналов готовности к покупке. - `resources/research-checklist.md` — полный проверочный список для исследования. ## Скрипты - `scripts/company-enricher.py` — объединение сведений о компании из нескольких источников. - `scripts/linkedin-parser.py` — структурирование данных профиля LinkedIn.
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))
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))
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))
research-checklist.md
# Проверочный список для исследования потенциального клиента ## Исследование компании ### Основные сведения - [ ] Название компании (проверьте написание). - [ ] Отрасль или рыночная ниша. - [ ] Расположение штаб-квартиры. - [ ] Число сотрудников (LinkedIn, сайт компании). - [ ] Оценка выручки, если доступна. - [ ] Дата основания. - [ ] Этап и история финансирования. ### Недавние новости (последние 90 дней) - [ ] Объявления о финансировании. - [ ] Поглощения или слияния. - [ ] Изменения в руководстве. - [ ] Запуск продуктов. - [ ] Крупные новые клиенты. - [ ] Упоминания в прессе. - [ ] Отчётность и финансовые новости. ### Цифровое присутствие - [ ] Изучение сайта. - [ ] Темы блога и публикаций. - [ ] Присутствие в социальных сетях. - [ ] Вакансии (карьерный раздел сайта и LinkedIn). - [ ] Технологический стек (BuiltWith, вакансии). ### Конкурентная среда - [ ] Известные конкуренты. - [ ] Позиция на рынке. - [ ] Преимущества, о которых заявляет компания. - [ ] Недавние действия конкурентов. ### Признаки проблем - [ ] Темы отзывов на Glassdoor. - [ ] Отзывы на G2/Capterra, если речь о B2B. - [ ] Жалобы в социальных сетях. - [ ] Закономерности в вакансиях. ## Исследование контактного лица ### Профессиональный профиль - [ ] Текущая должность. - [ ] Срок на этой должности. - [ ] Срок работы в компании. - [ ] Предыдущие компании. - [ ] Предыдущие должности. - [ ] Образование. ### Полномочия по принятию решений - [ ] Кому подчиняется человек. - [ ] Размер команды, если это руководитель. - [ ] Предполагаемые полномочия по бюджету. - [ ] Участие в прошлых закупках. ### Поводы для первого контакта - [ ] Недавние публикации в LinkedIn. - [ ] Опубликованные статьи. - [ ] Участие в подкастах. - [ ] Выступления на конференциях. - [ ] Общие знакомые. - [ ] Общие интересы и сообщества. ### Стиль общения - [ ] Тон публикаций (формальный или свободный). - [ ] Темы, в обсуждениях которых участвует человек. - [ ] Особенности ответов. ## Проверка CRM, если она доступна - [ ] Были ли контакты раньше. - [ ] Предыдущие возможности для сделки. - [ ] Связанные контакты в компании. - [ ] Заметки коллег. - [ ] Взаимодействие по электронной почте. ## Глубина исследования с учётом времени | Доступное время | Глубина исследования | |----------------|----------------| | 5 минут | Только основные сведения о компании и должность контактного лица | | 15 минут | Плюс недавние новости и профиль LinkedIn | | 30 минут | Плюс признаки проблем и поводы для первого контакта | | 60 минут | Полный список и анализ конкурентов |
signal-indicators.md
# Справочник по сигналам готовности к покупке ## Сильные сигналы намерения ### Вакансии - **Опубликовано не меньше трёх подходящих вакансий** = начата работа по направлению, выделен бюджет. - **Наём старшего специалиста в вашей области** = стратегический приоритет. - **Слова о срочности («ASAP», «немедленно»)** = проблема требует быстрого решения. - **Упомянут конкретный инструмент** = компания знает конкурента или категорию решений. ### Финансовые события - **Финансирование раунда B или позже** = капитал для роста и возможность закупок. - **Подготовка к IPO** = потребность в зрелых операционных процессах. - **Объявлено о поглощении** = впереди задачи по интеграции. - **Сообщение о достижении порога выручки** = бюджет может быть доступен. ### Изменения в руководстве - **Новый топ-менеджер в вашей области** = определение приоритетов на первые 90 дней. - **Новый CRO или CMO** = вероятна переоценка технологического стека. - **Основатель становится CEO** = переход к более формальным процессам. ## Сигналы среднего уровня ### Расширение - **Открытие нового офиса** = потребность в инфраструктуре. - **Выход в другие страны** = локализация и соблюдение требований. - **Запуск нового продукта** = задачи масштабирования. - **Получен крупный клиент** = возросшая нагрузка на выполнение обязательств. ### Технологии - **Опубликован запрос предложений (RFP)** = идёт процесс закупки. - **Упомянут обзор поставщиков** = компания сравнивает предложения. - **Изменение технологического стека** = возможность интеграции. - **Жалобы на устаревшую систему** = потребность в обновлении. ### Интерес к материалам - **Публикация в блоге на вашу тему** = компания изучает предмет. - **Участие в вебинаре** = интерес подтверждён. - **Скачивание аналитического материала** = компания осознаёт проблему. - **Выступление на конференции** = экспертная позиция и публичность. ## Слабые сигналы (поддерживайте контакт) ### Общая активность - **Участие в отраслевом событии** = компания присутствует на рынке. - **Обычный набор сотрудников** = компания растёт. - **Положительная публикация в прессе** = дела компании идут хорошо. - **Активность в социальных сетях** = руководство вовлечено. ## Оценка сигналов | Тип сигнала | Баллы | Действие | |-------------|-------|--------| | Подходящая вакансия | +3 | Поставить обращение в приоритет | | Недавнее финансирование | +3 | Упомянуть в разговоре | | Изменение в руководстве | +2 | Использовать момент, пока вопрос актуален | | Новость о расширении | +2 | Обсудить задачи роста | | Отрицательные отзывы | +2 | Обсудить выявленную проблему | | Интерес к материалам | +1 | Продолжать знакомить с решением | | Сигналов нет | 0 | Сосредоточиться на выяснении потребностей |
Текст доступен бесплатно по CC0 1.0. Источники и лицензии.
Как использовать навык
Прочитайте инструкцию и проверьте, какие файлы, инструменты и подключения ей нужны. Перенесите навык в совместимое приложение для AI-агентов или используйте подходящие шаги в чате. Если навык состоит из нескольких файлов, сохраните их структуру.