# Исследование потенциальных клиентов

## Файл: 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 | Сосредоточиться на выяснении потребностей |


---
Источник: prompts.chat. Текст: CC0 1.0 Universal. Русская версия: Kvantora.
