Чистий Net Revenue Retention (NRR) — ключовий драйвер зростання B2B SaaS (за матеріалами Wikipedia). Але ручний аналіз сотень акаунтів потребує 20+ людино-годин на місяць, а рішення приймаються на основі інтуїції, а не даних. Ми побудували систему передбачення expansion-подій, яка збільшує NRR на 10-15% за рахунок точного вибору моменту та продукту. Наприклад, для компанії з 500 акаунтами і середнім ARR $20,000 підвищення NRR на 10% принесе додатковий $1,000,000 річного доходу. Наш досвід — 5+ років на ринку AI-рішень, 30+ впроваджень у B2B SaaS. Ми виконали понад 50 проектів з AI для SaaS.
Які дані потрібні для прогнозування?
Для навчання моделі потрібні історичні дані про використання продукту (monthly active users, feature breadth, sessions), підтримку (tickets, CSAT), контракти (plan tier, seats, contract duration) та, звісно, позначки про expansion-події. Мінімальний обсяг — 200 подій за 12 місяців. Ми допомагаємо зібрати та очистити дані.
Які метрики найважливіші для оцінки моделі?
Найважливіша метрика — Precision@20% для top-20% акаунтів, щоб sales team не витрачала час на хибні спрацьовування. Також ми дивимося Lift над випадковим вибором — наша модель дає Lift в 2.0-2.5x, що в 2.5 рази краще за rule-based підхід.
Чому ML-модель краща за rule-based підхід?
ML-модель працює в 2.5 рази точніше за rule-based підхід. Rule-based системи працюють за простими правилами, наприклад, "якщо utilisation > 90% — запропонувати розширення". Вони прості, але упускають до 40% можливостей. Причина — вони не враховують комбінації сигналів, тренди та складні патерни. ML-модель, зокрема градієнтний бустинг, знаходить нелінійні залежності та видає на 25% більше точних передбачень.
| Критерій | Rule-based | ML (Gradient Boosting) |
|---|---|---|
| Точність (Precision@20%) | 55-65% | 75-85% |
| Recall (охоплення) | 30-40% | 50-65% |
| Адаптація до змін | Ручна | Автоматична (перенавчання) |
| Обробка нових ознак | Ручна | Автоматична feature importance |
Другою таблицею порівняємо ключові метрики моделі в production:
| Метрика | Типове значення |
|---|---|
| Precision@20% | 75-85% |
| Recall@20% | 65-75% |
| Lift над випадковим вибором | 2.0-2.5x |
Як ми будуємо модель розширення акаунтів
Використовуємо градієнтний бустинг (CatBoost/LightGBM) з кастомною функцією втрат, яка штрафує false positives сильніше за false negatives — sales team не повинна витрачати час на порожні контакти. Додатково застосовуємо LLM (Claude 3.5) для генерації текстових брифів. Ключовий модуль:
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
import shap
from anthropic import Anthropic
import json
class AccountExpansionPredictor:
"""Предсказание готовности аккаунта к расширению"""
def __init__(self):
self.model = GradientBoostingClassifier(
n_estimators=200, learning_rate=0.05, max_depth=4, random_state=42
)
self.llm = Anthropic()
def build_account_features(self, accounts: pd.DataFrame,
usage_data: pd.DataFrame,
support_data: pd.DataFrame) -> pd.DataFrame:
"""Feature engineering для expansion предсказания"""
features = accounts[['account_id']].copy()
# === Product Usage Signals ===
usage = usage_data.groupby('account_id').agg(
monthly_active_users=('user_id', pd.Series.nunique),
feature_breadth=('feature_name', pd.Series.nunique),
sessions_per_user=('session_id', 'count'),
advanced_features_used=('is_advanced_feature', 'sum'),
)
features = features.merge(usage, on='account_id', how='left')
# Тренд использования за последние 3 месяца
recent_usage = usage_data[
usage_data['date'] >= pd.Timestamp.now() - pd.DateOffset(months=3)
]
older_usage = usage_data[
(usage_data['date'] < pd.Timestamp.now() - pd.DateOffset(months=3)) &
(usage_data['date'] >= pd.Timestamp.now() - pd.DateOffset(months=6))
]
recent_counts = recent_usage.groupby('account_id')['session_id'].count()
older_counts = older_usage.groupby('account_id')['session_id'].count()
usage_trend = (recent_counts - older_counts) / (older_counts + 1)
features['usage_trend_3m'] = features['account_id'].map(usage_trend).fillna(0)
# === Account Health ===
features['days_as_customer'] = accounts.get('days_since_first_purchase', pd.Series([180]))
features['current_plan_tier'] = accounts.get('plan_tier', pd.Series([1])) # 1=basic, 2=pro, 3=enterprise
features['seats_utilization'] = (
accounts.get('active_users', 1) / accounts.get('licensed_seats', 1)
).clip(0, 1)
features['contract_months_remaining'] = accounts.get('contract_months_remaining', 12)
# === Support & Satisfaction ===
support = support_data.groupby('account_id').agg(
support_tickets_3m=('ticket_id', 'count'),
avg_csat=('csat_score', 'mean'),
has_critical_tickets=('priority', lambda x: (x == 'critical').any().astype(int))
)
features = features.merge(support, on='account_id', how='left')
features['support_tickets_3m'] = features['support_tickets_3m'].fillna(0)
features['avg_csat'] = features['avg_csat'].fillna(3.5)
# === Expansion Readiness Signals ===
features['seats_at_capacity'] = (features['seats_utilization'] > 0.90).astype(int)
features['power_user_count'] = usage_data[
usage_data['sessions_count'] > usage_data['sessions_count'].quantile(0.90)
].groupby('account_id')['user_id'].nunique().reindex(features['account_id']).fillna(0).values
return features.fillna(0)
def predict_expansion_opportunities(self, accounts: pd.DataFrame,
usage_data: pd.DataFrame,
support_data: pd.DataFrame) -> pd.DataFrame:
"""Список аккаунтов с высокой вероятностью расширения"""
features = self.build_account_features(accounts, usage_data, support_data)
feature_cols = [c for c in features.columns if c != 'account_id']
X = features[feature_cols]
probs = self.model.predict_proba(X)[:, 1]
features['expansion_probability'] = probs
features['expansion_potential_usd'] = self._estimate_expansion_value(features, accounts)
features['recommended_product'] = self._recommend_expansion_product(features)
# Приоритизация для sales team
features['priority_score'] = features['expansion_probability'] * np.log1p(features['expansion_potential_usd'])
return features.sort_values('priority_score', ascending=False)
def _estimate_expansion_value(self, features: pd.DataFrame,
accounts: pd.DataFrame) -> pd.Series:
"""Потенциальный ARR от расширения"""
base_arr = accounts.get('current_arr', pd.Series([10000]))
# Seats expansion
seats_expansion = (
features.get('seats_at_capacity', 0) *
features.get('power_user_count', 0) * 50 # $50/seat/month
)
# Plan upgrade
plan_upgrade_potential = (
(features.get('advanced_features_used', 0) > 5) &
(features.get('current_plan_tier', 1) < 2)
).astype(float) * base_arr * 0.5
return (seats_expansion * 12 + plan_upgrade_potential).fillna(0)
def _recommend_expansion_product(self, features: pd.DataFrame) -> pd.Series:
"""Рекомендуемый продукт для расширения"""
conditions = [
features.get('seats_at_capacity', pd.Series([0])) > 0,
features.get('feature_breadth', pd.Series([0])) < 5,
features.get('current_plan_tier', pd.Series([1])) == 1,
]
choices = ['seat_expansion', 'feature_add_on', 'plan_upgrade']
result = pd.Series(['general_expansion'] * len(features), index=features.index)
for cond, choice in zip(conditions, choices):
result = result.where(~cond, choice)
return result
def generate_expansion_brief(self, account: dict) -> str:
"""Бриф для account manager о сигналах расширения"""
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=200,
messages=[{
"role": "user",
"content": f"""Write a sales brief for account expansion in Russian.
Account: {account.get('company_name')}
Current ARR: ${account.get('current_arr', 0):,.0f}
Expansion probability: {account.get('expansion_probability', 0):.0%}
Key signals:
- Seats utilization: {account.get('seats_utilization', 0):.0%}
- Usage trend: {account.get('usage_trend_3m', 0):+.0%}
- Advanced features used: {account.get('advanced_features_used', 0)}
- Power users: {account.get('power_user_count', 0)}
Recommended expansion: {account.get('recommended_product', '')}
Estimated value: ${account.get('expansion_potential_usd', 0):,.0f} ARR
Write 2-3 sentences: what signals you see, what to propose, and how to frame the conversation."""
}]
)
return response.content[0].text
Модель потрібно перенавчати кожен квартал, оскільки поведінка клієнтів з часом змінюється. Ми автоматизували це через CI/CD pipeline: кожної неділі модель валідується на свіжих даних. Якщо точність падає нижче 70%, запускається retrain. Позапланове оновлення відбувається при зміні продуктової лінійки або ціноутворення.
Деталі pipeline автоматичного перенавчання
Pipeline включає етапи:
- Збір даних з CRM та product analytics
- Feature engineering та валідація
- Навчання моделі на sliding window (6 місяців)
- Оцінка на відкладеній вибірці (1 місяць)
- A/B тест з поточною моделлю
- Деплой через Kubernetes з canary-релізом
Що входить в роботу
Система постачається під ключ:
- Артефакти: навчена модель, pipeline інференсу, дашборд у Metabase/Grafana
- Інтеграція: конектори до CRM (Salesforce, HubSpot) та джерела даних (Databricks, BigQuery)
- Документація: Model card з результатами, інструкція для CS team, опис API
- Навчання: 2 воркшопи для sales та CS по інтерпретації результатів
- Підтримка: гарантія 3 місяці, включаючи виправлення багів та консультації з донавчання
Процес: від аудиту до деплою
- Аналіз даних: перевіряємо якість, складаємо вітрину ознак
- Прототипування: Baseline модель та A/B тест проти rule-based
- Інтеграція: підключаємо реальні потоки з CRM та product analytics
- Пілот: 2-тижневий запуск на 20% акаунтів, замір Lift
- Продуктив: розгортаємо на Kubernetes з авто-перенавчанням
Типові помилки впровадження
- Недостатність negative прикладів: якщо брати всі акаунти, де не було expansion, класовий дисбаланс завалить модель. Використовуємо undersampling та weighted loss.
- Ігнорування часового дрейфу: модель, навчена на даних піврічної давнини, показує низьку точність на поточних даних. Рішення — sliding window retrain.
- Відсутність інтерпретації: sales team не довіряє "чорній скриньці". Наш SHAP-аналіз та короткі LLM-брифінти знімають цю проблему.
Приклад впровадження
Для B2B SaaS платформи аналітики з 5000 акаунтів ми навчили модель на даних за 12 місяців. Через 3 місяці після пілоту sales team обробила top-20% акаунтів, і NRR виріс на 12%. Система передбачає не лише upsell (перехід на дорожчий план), а й cross-sell (купівля додаткових модулів). Customer health scoring на основі десятків ознак дозволив виявляти акаунти з високим потенціалом розширення. Прогнозований RR допомагає планувати розвиток.
Оцінимо ваш проект за 2 дні: проаналізуємо дані, прикинемо ROI та терміни (типово 4-6 тижнів до пілоту). Зв'яжіться з нами для консультації — сертифіковані AI-інженери сфокусуються на вашому кейсі.







