AI-система оптимізації paywall (конверсія в підписку)

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
AI-система оптимізації paywall (конверсія в підписку)
Середній
~2-4 тижні
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

AI-система конверсії у підписку та управління пейволлом

Медіа та SaaS з freemium-моделлю конвертують у підписку 2-5% користувачів. AI-оптимізація пейволла — показувати правильний CTA потрібному користувачу у потрібний момент — піднімає цей показник до 4-9%.

Модель намірення купити підписку

import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.calibration import CalibratedClassifierCV

class PaywallConversionPredictor:
    """Предсказання вероятності конверсії у підписку"""

    def __init__(self):
        base = GradientBoostingClassifier(
            n_estimators=200, learning_rate=0.05, max_depth=4, random_state=42
        )
        self.model = CalibratedClassifierCV(base, method='isotonic', cv=5)

    def build_features(self, user_sessions: pd.DataFrame) -> pd.DataFrame:
        """Поведінкові ознаки, що передбачають конверсію"""
        return pd.DataFrame({
            # Глибина залучення
            'articles_read_30d': user_sessions['articles_read_30d'],
            'paywall_hits_7d': user_sessions['paywall_hits_7d'],       # Ключовий сигнал
            'search_queries_7d': user_sessions['search_queries_7d'],
            'days_active_30d': user_sessions['days_active_30d'],
            'bookmarks_count': user_sessions['bookmarks_count'],

            # Глибина читання
            'avg_read_completion': user_sessions['avg_read_completion'],  # 0-1
            'premium_content_attempts': user_sessions['premium_content_attempts'],

            # Технічне
            'email_verified': user_sessions['email_verified'].astype(int),
            'newsletter_subscriber': user_sessions['newsletter_subscriber'].astype(int),
            'mobile_app_installed': user_sessions.get('has_app', pd.Series([0])).astype(int),

            # Джерело та канал
            'organic_traffic': user_sessions.get('organic_ratio', 0.5),
            'days_since_registration': user_sessions['days_since_registration'].clip(0, 365),

            # Контекстні
            'current_session_paywall_hit': user_sessions['current_session_paywall_hit'].astype(int),
            'referral_from_premium': user_sessions.get('from_premium_referral', 0).astype(int),
        }).fillna(0)

    def predict(self, users: pd.DataFrame) -> pd.DataFrame:
        X = self.build_features(users)
        probs = self.model.predict_proba(X)[:, 1]
        result = users[['user_id']].copy() if 'user_id' in users.columns else pd.DataFrame(index=users.index)
        result['conversion_probability'] = probs
        result['segment'] = pd.cut(probs, bins=[0, 0.15, 0.40, 0.70, 1.0],
                                    labels=['unlikely', 'potential', 'likely', 'hot'])
        return result


class DynamicPaywallStrategy:
    """Динамічна стратегія пейволла"""

    # Стратегії за сегментами
    STRATEGIES = {
        'hot': {
            'paywall_type': 'hard',
            'free_articles_remaining': 0,
            'offer': 'annual_plan_30_off',
            'urgency': True,
            'message': 'Ви читаєте нас активно — заощадьте 30% на річному плані'
        },
        'likely': {
            'paywall_type': 'metered',
            'free_articles_remaining': 2,
            'offer': 'monthly_first_month_free',
            'urgency': False,
            'message': 'Перший місяць безплатно'
        },
        'potential': {
            'paywall_type': 'soft',
            'free_articles_remaining': 5,
            'offer': 'newsletter_upsell',
            'urgency': False,
            'message': 'Підпишіться на розсилку найкращих матеріалів'
        },
        'unlikely': {
            'paywall_type': 'none',
            'free_articles_remaining': 10,
            'offer': None,
            'urgency': False,
            'message': ''
        }
    }

    def get_strategy(self, user_segment: str,
                      context: dict) -> dict:
        """Стратегія для користувача з урахуванням контексту"""
        strategy = dict(self.STRATEGIES.get(user_segment, self.STRATEGIES['unlikely']))

        # Контекстні модифікації
        if context.get('is_breaking_news') and user_segment in ['hot', 'likely']:
            strategy['paywall_type'] = 'hard'
            strategy['message'] = f"Експертиза: {context.get('article_title', 'Ця стаття')} тільки для підписників"

        if context.get('is_mobile') and strategy['offer']:
            strategy['offer'] = strategy['offer'] + '_mobile_checkout'

        if context.get('hour') in range(20, 24) and user_segment == 'hot':
            strategy['urgency_message'] = 'Пропозиція дійсна до кінця дня'

        return strategy

    def select_offer(self, user: dict,
                      available_offers: list[dict]) -> dict:
        """A/B тест офертів: вибір варіанту для користувача"""
        # Детермінований розподіл варіантів
        bucket = hash(user['user_id']) % 100
        offer_idx = min(bucket // (100 // len(available_offers)), len(available_offers) - 1)
        return available_offers[offer_idx]


class ChurnPreventionForSubscribers:
    """Утримання підписників перед відміною"""

    def predict_cancellation_risk(self, subscription_data: pd.DataFrame) -> pd.DataFrame:
        """Ризик скасування підписки до наступного поновлення"""
        df = subscription_data.copy()

        # Індикатори ризику
        df['risk_score'] = (
            (df['logins_last_month'] < 2).astype(float) * 0.30 +
            (df['days_since_last_read'] > 14).astype(float) * 0.25 +
            (df['opened_cancel_page']).astype(float) * 0.35 +
            (df['support_cancel_inquiry']).astype(float) * 0.10
        )

        df['churn_risk'] = pd.cut(
            df['risk_score'],
            bins=[0, 0.3, 0.6, 1.0],
            labels=['low', 'medium', 'high']
        )

        return df

    def generate_retention_offer(self, subscriber: dict) -> dict:
        """Персональна пропозиція для утримання"""
        months_subscribed = subscriber.get('months_subscribed', 1)
        plan = subscriber.get('plan', 'monthly')

        if months_subscribed > 12:
            return {
                'type': 'loyalty_discount',
                'discount_pct': 25,
                'message': f'Ви з нами {months_subscribed} місяців — отримайте 25% знижку на наступний рік'
            }
        elif plan == 'monthly':
            return {
                'type': 'plan_upgrade_offer',
                'offer': 'annual_plan_with_savings',
                'message': 'Перейдіть на річний план і заощадьте 40%'
            }
        else:
            return {
                'type': 'pause_option',
                'pause_weeks': 4,
                'message': 'Немає часу читати? Поставте підписку на паузу на 4 тижні'
            }

Правильна сегментація пейволла (різні стратегії для різних ймовірностей конверсії) збільшує subscription revenue на 20-35% без змін тарифів. Ключовий інсайт: занадто жорсткий пейволл для low-intent користувачів збільшує bounce, занадто м'який для high-intent — упускає гроші.