#CRM-аналитика: воронка

📊 Сложность: beginner | Скоупы: crm | Стек: Python, requests

#Что делаем

Пишем Python-скрипт, который выгружает все сделки из CRM через Вайбкод Entity API, группирует их по стадиям воронки и рассчитывает конверсию между этапами и средний чек. Результат выводится в консоль в виде наглядной таблицы. Полезно для быстрого анализа эффективности продаж без сторонних BI-инструментов.

#Необходимо

  • API-ключ Вайбкод с правами crm
  • Python 3.8+
  • Пакет: requests

#Полный код

Python
# crm_analytics.py
# Анализ воронки продаж: конверсия по стадиям и средний чек

import os
import requests
from collections import defaultdict

API_KEY = os.environ["VIBE_API_KEY"]
BASE_URL = os.environ["VIBE_BASE_URL"]  # например https://vibecode.bitrix24.tech

HEADERS = {
    "X-Api-Key": API_KEY,
}

# Названия стадий воронки (стандартные для Bitrix24)
# Примечание: на порталах с несколькими воронками стадии имеют C-префикс:
# C2:WON, C4:NEW и т.д. Функция get_stage_name() обрабатывает оба формата.
STAGE_NAMES = {
    "NEW": "Новая",
    "PREPARATION": "Подготовка",
    "PREPAYMENT_INVOICE": "Счёт на предоплату",
    "EXECUTING": "В работе",
    "FINAL_INVOICE": "Финальный счёт",
    "WON": "Успешная",
    "LOSE": "Проигранная",
}


def get_stage_name(stage_id):
    """Получает название стадии, обрабатывая C-префиксы множественных воронок"""
    # C2:WON → WON, C4:NEW → NEW
    base = stage_id.split(":")[-1] if ":" in stage_id else stage_id
    return STAGE_NAMES.get(base, stage_id)


def fetch_all_deals():
    """Загружаем все сделки через Entity API с авто-пагинацией"""
    all_deals = []
    offset = 0
    limit = 200

    while True:
        response = requests.get(
            f"{BASE_URL}/v1/deals",
            headers=HEADERS,
            params={
                "select": "id,stageId,amount,currency",
                "limit": limit,
                "offset": offset,
            },
        )
        data = response.json()
        deals = data.get("data", [])
        all_deals.extend(deals)

        # Если вернулось меньше limit — всё загружено
        if len(deals) < limit:
            break
        offset += limit

    return all_deals


def analyze_funnel(deals):
    """Группируем сделки по стадиям и считаем метрики"""
    stages = defaultdict(lambda: {"count": 0, "total_amount": 0})

    for deal in deals:
        stage = deal.get("stageId", "UNKNOWN")
        amount = float(deal.get("amount", 0) or 0)

        stages[stage]["count"] += 1
        stages[stage]["total_amount"] += amount

    return stages


def print_funnel_report(stages, total_deals):
    """Выводим отчёт по воронке"""
    print("\n" + "=" * 65)
    print("  АНАЛИЗ ВОРОНКИ ПРОДАЖ")
    print("=" * 65)
    print(f"\n  Всего сделок: {total_deals}\n")

    # Порядок стадий для отображения
    stage_order = [
        "NEW", "PREPARATION", "PREPAYMENT_INVOICE",
        "EXECUTING", "FINAL_INVOICE", "WON", "LOSE",
    ]

    print(f"  {'Стадия':<25} {'Кол-во':>8} {'Конверсия':>10} {'Ср. чек':>12}")
    print("  " + "-" * 58)

    for stage_id in stage_order:
        if stage_id not in stages:
            continue

        info = stages[stage_id]
        count = info["count"]
        name = get_stage_name(stage_id)

        # Конверсия относительно общего количества
        conversion = (count / total_deals * 100) if total_deals > 0 else 0

        # Средний чек
        avg_check = info["total_amount"] / count if count > 0 else 0

        # Визуальная шкала
        bar = "█" * int(conversion / 3)

        print(f"  {name:<25} {count:>8} {conversion:>9.1f}% {avg_check:>11,.0f}₽")
        print(f"  {bar}")

    # Итоговые метрики (суммируем все WON/LOSE стадии, включая C-префиксы)
    won = {"count": 0, "total_amount": 0}
    lost = {"count": 0, "total_amount": 0}
    for sid, info in stages.items():
        base = sid.split(":")[-1] if ":" in sid else sid
        if base == "WON":
            won["count"] += info["count"]
            won["total_amount"] += info["total_amount"]
        elif base == "LOSE":
            lost["count"] += info["count"]
            lost["total_amount"] += info["total_amount"]
    closed = won["count"] + lost["count"]

    print("\n" + "-" * 65)
    print(f"  Общая выручка (WON):    {won['total_amount']:>15,.0f}₽")

    if closed > 0:
        win_rate = won["count"] / closed * 100
        print(f"  Win Rate:                {win_rate:>14.1f}%")

    if won["count"] > 0:
        avg_won = won["total_amount"] / won["count"]
        print(f"  Средний чек (WON):      {avg_won:>15,.0f}₽")

    print("=" * 65)


def main():
    print("📊 Загружаем сделки из CRM...")
    deals = fetch_all_deals()
    print(f"   Загружено: {len(deals)} сделок")

    stages = analyze_funnel(deals)
    print_funnel_report(stages, len(deals))


if __name__ == "__main__":
    main()

#Как это работает

  1. Скрипт загружает все сделки из CRM через Entity API (GET /v1/deals) с авто-пагинацией через limit/offset.
  2. Каждая сделка группируется по стадии (stageId), подсчитывается количество и суммируется бюджет.
  3. Для каждой стадии рассчитывается конверсия относительно общего числа сделок и средний чек.
  4. Результаты выводятся в виде текстовой таблицы с визуальными шкалами.
  5. Отдельно рассчитываются итоговые метрики: общая выручка, Win Rate и средний чек успешных сделок.

#Что можно улучшить

  • Использовать POST /v1/deals/aggregate с groupBy: ["stageId"] вместо загрузки всех записей — быстрее и экономнее
  • Добавить фильтрацию по периоду (месяц, квартал, год) для сравнения динамики
  • Экспортировать результат в CSV или Excel для дальнейшей работы
  • Визуализировать воронку с помощью matplotlib или plotly