Перейти к основному содержимому

Движок конфигуратора

Движок конфигуратора — ядро системы автоматического подбора контент-стратегии. На вход получает данные продавца и аналитику SalesFinder, на выходе — персонализированные рекомендации с бюджетом и приоритизацией SKU.

1. Архитектура движка

Конфигуратор реализован как последовательный pipeline из шести модулей.

flowchart LR
A["Seller Profile"] --> B["Category Detector"]
B --> C["Archetype Classifier"]
C --> D["Strategy Selector"]
D --> E["Budget Allocator"]
E --> F["Output"]

style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#fff3e0
style D fill:#e8f5e9
style E fill:#fce4ec
style F fill:#f1f8e9

Принципы:

  • Каждый модуль — отдельный класс с единственной ответственностью
  • Данные передаются через Pydantic-модели (валидация на каждом шаге)
  • Любой модуль можно заменить или переопределить через overrides
  • Все вычисления детерминированы — одинаковый вход всегда даёт одинаковый выход
class ConfiguratorPipeline:
"""Оркестратор конфигуратора."""

def __init__(self, sf_client: SalesFinderClient):
self.data_provider = SalesFinderDataProvider(sf_client)
self.category_detector = CategoryDetector()
self.archetype_classifier = ArchetypeClassifier()
self.strategy_selector = StrategySelector()
self.budget_calculator = ContentBudgetCalculator()
self.sku_prioritizer = SKUPrioritizer()

async def analyze(
self, seller_id: str, overrides: ConfigOverrides | None = None
) -> StrategyRecommendation:
profile = await self.data_provider.build_profile(seller_id)
profile = self.category_detector.enrich(profile)

archetype = self.archetype_classifier.classify(profile)
if overrides and overrides.force_archetype:
archetype = overrides.force_archetype

preset = self.strategy_selector.select(archetype, profile)
budget = self.budget_calculator.calculate(profile, preset, overrides)

sku_plan = self.sku_prioritizer.prioritize(
await self.data_provider.get_sku_analytics(seller_id), budget,
)

return StrategyRecommendation(
seller_id=seller_id, profile=profile, archetype=archetype,
preset=preset, budget=budget, sku_plan=sku_plan,
)

2. Seller Profile Model

Профиль продавца — центральная модель, агрегирующая все данные для принятия решений.

from pydantic import BaseModel, Field
from enum import Enum

class Marketplace(str, Enum):
WB = "wildberries"
OZON = "ozon"
BOTH = "both"

class ContentState(str, Enum):
NONE = "none" # Нет контента
BASIC = "basic" # Только фото телефоном
STANDARD = "standard" # Базовая студийная съёмка
PROFESSIONAL = "pro" # Профессиональный контент
PREMIUM = "premium" # Полный пакет (видео, 3D, Rich)

class SellerProfile(BaseModel):
"""Профиль продавца для конфигуратора."""

seller_id: str
marketplace: Marketplace
sku_count: int = Field(ge=1)
monthly_revenue: float = Field(ge=0, description="Месячная выручка, руб.")
avg_margin: float = Field(ge=0, le=1)

# Категории и ниша
categories: list[str] = Field(default_factory=list)
primary_category: str | None = None
is_fashion: bool = False
niche: str | None = None

# Бизнес-модель
has_own_brand: bool = False
is_reseller: bool = False
brand_count: int = 1

# Ценовые параметры
avg_price: float = 0
min_price: float = 0
max_price: float = 0

# Сезонность и возвраты
seasonal_coefficient: float = Field(default=1.0, ge=0.1, le=5.0)
return_rate: float = Field(default=0.05, ge=0, le=1)

# Текущее состояние контента
content_state: ContentState = ContentState.NONE
has_video: bool = False
has_infographics: bool = False
has_rich_content: bool = False

@property
def revenue_per_sku(self) -> float:
return self.monthly_revenue / self.sku_count if self.sku_count else 0

@property
def investment_capacity(self) -> float:
"""Инвестиционная ёмкость = выручка * маржа * 0.15."""
return self.monthly_revenue * self.avg_margin * 0.15
Откуда берутся данные

Поля sku_count, monthly_revenue, categories, avg_price заполняются автоматически из SalesFinder API. Поля has_own_brand, is_reseller, content_state — из анкеты продавца при первом подключении.


3. Category Detection

CategoryDetector определяет вертикаль продавца (Fashion / Goods) и уточняет нишу.

class CategoryDetector:
"""Детектор категорий по данным маркетплейса."""

FASHION_CATEGORIES: set[str] = {
"Одежда", "Обувь", "Аксессуары", "Нижнее бельё",
"Спортивная одежда", "Детская одежда", "Верхняя одежда",
"Платья", "Костюмы", "Джинсы", "Сумки",
"Женская одежда", "Мужская одежда", # WB
"Женщинам", "Мужчинам", "Детям", # Ozon
}

GOODS_NICHES: dict[str, list[str]] = {
"electronics": ["Электроника", "Смартфоны", "Ноутбуки", "Наушники"],
"home": ["Дом", "Мебель", "Декор", "Текстиль для дома", "Кухня"],
"beauty": ["Красота", "Косметика", "Уход за кожей", "Парфюмерия"],
"kids": ["Игрушки", "Детские товары", "Коляски"],
"sport": ["Спорт", "Фитнес", "Туризм"],
"food": ["Продукты", "Чай", "Кофе", "БАДы"],
"auto": ["Автотовары", "Автоаксессуары", "Автозапчасти"],
"garden": ["Сад", "Дача", "Инструменты"],
"pets": ["Зоотовары", "Корма"],
}

def enrich(self, profile: SellerProfile) -> SellerProfile:
"""Обогатить профиль: определить вертикаль и нишу."""
fashion_score = sum(
1 for cat in profile.categories if cat in self.FASHION_CATEGORIES
)
total = len(profile.categories) or 1
profile.is_fashion = (fashion_score / total) >= 0.5

if not profile.is_fashion:
profile.niche = self._detect_niche(profile.categories)
return profile

def _detect_niche(self, categories: list[str]) -> str | None:
"""Определить товарную нишу по категориям."""
scores: dict[str, int] = {}
for niche, keywords in self.GOODS_NICHES.items():
scores[niche] = sum(1 for cat in categories if cat in keywords)
if not scores:
return None
best = max(scores, key=scores.get)
return best if scores[best] > 0 else None
Гибридные продавцы

Если продавец торгует и одеждой, и товарами (fashion_score ~ 50%), система создаёт два плана: Fashion-часть по вертикали Fashion, товарная — по Goods. Бюджет делится пропорционально выручке.


4. Archetype Classification

ArchetypeClassifier — дерево решений, определяющее один из 8 архетипов продавца.

class SellerArchetype(str, Enum):
STARTER = "starter" # <30 SKU, <500K/мес
BRAND_LAUNCHER = "brand_launcher" # Свой бренд, старт
GROWING_BRAND = "growing_brand" # Свой бренд, рост
RESELLER = "reseller" # Перепродажа
MULTI_BRAND = "multi_brand" # Мульти-бренд портфель
NICHE_EXPERT = "niche_expert" # Узкая ниша, высокая маржа
ENTERPRISE = "enterprise" # 500+ SKU или 10M+/мес
FASHION_BRAND = "fashion_brand" # Fashion с собственным брендом

class ArchetypeClassifier:
"""Классификатор архетипов продавца."""

def classify(self, profile: SellerProfile) -> SellerArchetype:
# Enterprise — крупный бизнес (приоритетная проверка)
if profile.sku_count >= 500 or profile.monthly_revenue >= 10_000_000:
return SellerArchetype.ENTERPRISE

# Fashion Brand — fashion + свой бренд
if profile.is_fashion and profile.has_own_brand:
return SellerArchetype.FASHION_BRAND

# Starter / Brand Launcher — начинающий
if profile.sku_count < 30 and profile.monthly_revenue < 500_000:
if profile.has_own_brand:
return SellerArchetype.BRAND_LAUNCHER
return SellerArchetype.STARTER

# Reseller — перепродажа без бренда
if profile.is_reseller and not profile.has_own_brand:
return SellerArchetype.RESELLER

# Multi-Brand — несколько брендов
if profile.brand_count >= 3:
return SellerArchetype.MULTI_BRAND

# Niche Expert — высокая маржа, узкий ассортимент
if profile.avg_margin >= 0.4 and profile.sku_count < 100:
return SellerArchetype.NICHE_EXPERT

# Growing Brand — свой бренд в стадии роста
if profile.has_own_brand:
return SellerArchetype.GROWING_BRAND

return SellerArchetype.RESELLER # Fallback

Дополнительно explain(profile) возвращает скоринг по всем 8 архетипам (0-10 баллов каждый) для прозрачности классификации. Факторы скоринга: SKU count, revenue, margin, brand ownership, fashion flag, return rate.


5. Strategy Preset System

Каждому архетипу соответствует пресет стратегии — набор процентных аллокаций бюджета по типам контента.

class StrategyPreset(BaseModel):
name: str
archetype: SellerArchetype
description: str

# Аллокация бюджета (%, сумма = 100)
photo_pct: float = 0
video_pct: float = 0
infographic_pct: float = 0
seo_pct: float = 0
ugc_pct: float = 0
rich_pct: float = 0

priority_photo: int = 1
priority_video: int = 2
priority_infographic: int = 3
refresh_cycle_months: int = 6

Примеры пресетов

ПараметрBrand LauncherResellerEnterprise
Photo35%25%20%
Video20%10%25%
Infographic25%40%15%
SEO10%15%15%
UGC5%5%10%
Rich5%5%15%
Цикл обновления4 мес6 мес3 мес
PRESETS: dict[SellerArchetype, StrategyPreset] = {
SellerArchetype.BRAND_LAUNCHER: StrategyPreset(
name="Brand Launcher", archetype=SellerArchetype.BRAND_LAUNCHER,
description="Максимум визуала для запуска бренда",
photo_pct=35, video_pct=20, infographic_pct=25,
seo_pct=10, ugc_pct=5, rich_pct=5, refresh_cycle_months=4,
),
SellerArchetype.RESELLER: StrategyPreset(
name="Reseller Efficient", archetype=SellerArchetype.RESELLER,
description="Инфографика + SEO для максимума конверсий",
photo_pct=25, video_pct=10, infographic_pct=40,
seo_pct=15, ugc_pct=5, rich_pct=5, refresh_cycle_months=6,
),
SellerArchetype.ENTERPRISE: StrategyPreset(
name="Enterprise Full Stack", archetype=SellerArchetype.ENTERPRISE,
description="Все типы контента, упор на видео и Rich",
photo_pct=20, video_pct=25, infographic_pct=15,
seo_pct=15, ugc_pct=10, rich_pct=15, refresh_cycle_months=3,
),
# ... остальные 5 пресетов аналогично
}

class StrategySelector:
def select(self, archetype: SellerArchetype, profile: SellerProfile) -> StrategyPreset:
preset = PRESETS[archetype].model_copy()
if profile.is_fashion: # Fashion: +10% photo, -10% seo
preset.photo_pct += 10
preset.seo_pct = max(0, preset.seo_pct - 10)
if profile.return_rate > 0.15: # Высокий возврат: +5% video
preset.video_pct += 5
preset.infographic_pct = max(0, preset.infographic_pct - 5)
return preset
Сумма аллокаций

После коррекций сумма процентов может отклоняться от 100%. BudgetCalculator нормализует значения перед расчётом абсолютных сумм.


6. ABC-анализ для приоритизации SKU

SKUPrioritizer распределяет SKU по группам A/B/C и назначает уровень контента.

flowchart TD
A["Все SKU продавца"] --> B["Сортировка по выручке"]
B --> C{"Кумулятивная доля"}
C -->|"до 80%"| D["Группа A\nПолный контент"]
C -->|"80-95%"| E["Группа B\nСтандартный"]
C -->|"от 95%"| F["Группа C\nМинимум / AI"]

style D fill:#c8e6c9
style E fill:#fff9c4
style F fill:#ffcdd2
@dataclass
class SKUContentPlan:
sku_id: str
name: str
monthly_revenue: float
abc_group: str # "A", "B", "C"
content_level: str # "full", "standard", "minimal"
estimated_cost: float
priority_order: int

class SKUPrioritizer:
A_THRESHOLD = 0.80 # Топ-20% SKU дают 80% выручки
B_THRESHOLD = 0.95 # Следующие 30% дают ещё 15%
COST_MULTIPLIER = {"A": 1.0, "B": 0.5, "C": 0.15}

def prioritize(self, sku_list: list[dict], budget: "ContentBudget") -> list[SKUContentPlan]:
sorted_skus = sorted(sku_list, key=lambda x: x["revenue"], reverse=True)
total_revenue = sum(s["revenue"] for s in sorted_skus) or 1

plans, cumulative = [], 0.0
for i, sku in enumerate(sorted_skus):
cumulative += sku["revenue"] / total_revenue
if cumulative <= self.A_THRESHOLD:
group, level = "A", "full"
elif cumulative <= self.B_THRESHOLD:
group, level = "B", "standard"
else:
group, level = "C", "minimal"

plans.append(SKUContentPlan(
sku_id=sku["id"], name=sku["name"],
monthly_revenue=sku["revenue"], abc_group=group,
content_level=level, priority_order=i + 1,
estimated_cost=budget.cost_per_sku * self.COST_MULTIPLIER[group],
))
return plans
Что получает каждая группа
  • Группа A (full): студийное фото, видео-обзор, инфографика, SEO, Rich-контент
  • Группа B (standard): студийное фото, инфографика, SEO-текст
  • Группа C (minimal): AI-фон, базовый текст, шаблонная инфографика

7. Budget Calculator

ContentBudgetCalculator преобразует пресет в абсолютные суммы с учётом ценовых уровней и ниши.

@dataclass
class ContentBudget:
total: float
per_type: dict[str, float] # photo, video, infographic, seo, ugc, rich
cost_per_sku: float
investment_level: int # 0-4
fashion_multiplier: float
monthly_capacity: int # SKU/месяц

class ContentBudgetCalculator:
# Уровни инвестиций (стоимость за 1 SKU, руб.)
INVESTMENT_LEVELS = {
0: {"name": "Минимальный", "cost_range": (500, 1_500), "desc": "AI + шаблоны"},
1: {"name": "Базовый", "cost_range": (1_500, 5_000), "desc": "Фото + инфографика"},
2: {"name": "Стандартный", "cost_range": (5_000, 15_000), "desc": "+ видео + SEO"},
3: {"name": "Продвинутый", "cost_range": (15_000, 40_000), "desc": "+ UGC + Rich"},
4: {"name": "Премиум", "cost_range": (40_000, 100_000),"desc": "Полный пакет"},
}
FASHION_MULTIPLIER = 2.5

def calculate(self, profile: SellerProfile, preset: StrategyPreset,
overrides: "ConfigOverrides | None" = None) -> ContentBudget:
level = self._detect_level(profile.revenue_per_sku)
cost_range = self.INVESTMENT_LEVELS[level]["cost_range"]
base_cost = (cost_range[0] + cost_range[1]) / 2

multiplier = self.FASHION_MULTIPLIER if profile.is_fashion else 1.0
cost_per_sku = base_cost * multiplier * profile.seasonal_coefficient
total = cost_per_sku * profile.sku_count

if overrides and overrides.budget_limit:
total = min(total, overrides.budget_limit)
cost_per_sku = total / profile.sku_count

# Аллокация по типам (с нормализацией)
alloc = {"photo": preset.photo_pct, "video": preset.video_pct,
"infographic": preset.infographic_pct, "seo": preset.seo_pct,
"ugc": preset.ugc_pct, "rich": preset.rich_pct}

if overrides and overrides.exclude_content_types:
for ct in overrides.exclude_content_types:
alloc.pop(ct, None)

pct_sum = sum(alloc.values()) or 1
per_type = {k: round(total * v / pct_sum, 2) for k, v in alloc.items()}

return ContentBudget(
total=round(total, 2), per_type=per_type,
cost_per_sku=round(cost_per_sku, 2), investment_level=level,
fashion_multiplier=multiplier,
monthly_capacity=max(1, int(total / cost_per_sku / 4)),
)

def _detect_level(self, revenue_per_sku: float) -> int:
if revenue_per_sku < 10_000: return 0
if revenue_per_sku < 50_000: return 1
if revenue_per_sku < 200_000: return 2
if revenue_per_sku < 500_000: return 3
return 4
Логика fashion-множителя

Fashion-контент стоит в 2.5x дороже: больше фотографий на SKU (5-8 vs 3-4), обязательное видео с моделями, сезонные пересъёмки каждые 3 месяца.


8. SalesFinder API Integration

SalesFinderDataProvider собирает данные из нескольких эндпоинтов для построения профиля.

flowchart TD
SF["SalesFinder API"] --> A["/sellers/{id}/summary"]
SF --> B["/sellers/{id}/products"]
SF --> C["/analytics/revenue"]
SF --> D["/analytics/returns"]
A & B & C & D --> P["SellerProfile"]

style SF fill:#e3f2fd
style P fill:#c8e6c9
class SalesFinderDataProvider:
def __init__(self, client: SalesFinderClient):
self.client = client

async def build_profile(self, seller_id: str) -> SellerProfile:
summary, products, revenue, returns = await asyncio.gather(
self.client.get(f"/sellers/{seller_id}/summary"),
self.client.get(f"/sellers/{seller_id}/products", params={"limit": 1000}),
self.client.get(f"/analytics/revenue", params={"seller_id": seller_id, "period": "30d"}),
self.client.get(f"/analytics/returns", params={"seller_id": seller_id, "period": "30d"}),
)

categories = list({p["category"] for p in products.get("items", [])})
prices = [p["price"] for p in products.get("items", []) if p.get("price")]

return SellerProfile(
seller_id=seller_id,
marketplace=self._detect_marketplace(summary),
sku_count=summary.get("active_products", 0),
monthly_revenue=revenue.get("total", 0),
avg_margin=summary.get("avg_margin", 0.25),
categories=categories,
avg_price=sum(prices) / len(prices) if prices else 0,
min_price=min(prices) if prices else 0,
max_price=max(prices) if prices else 0,
return_rate=returns.get("rate", 0.05),
brand_count=summary.get("brand_count", 1),
has_own_brand=summary.get("has_own_brand", False),
is_reseller=summary.get("is_reseller", False),
)

async def get_sku_analytics(self, seller_id: str) -> list[dict]:
"""Аналитика по SKU для ABC-анализа."""
data = await self.client.get(
f"/sellers/{seller_id}/products",
params={"limit": 5000, "fields": "id,name,revenue,orders,returns"},
)
return [{"id": p["id"], "name": p["name"], "revenue": p.get("revenue", 0)}
for p in data.get("items", [])]
ЭндпоинтДанныеНазначение
/sellers/{id}/summaryОбщая информацияSKU, маржа, бренды
/sellers/{id}/productsСписок товаровКатегории, цены, ABC
/analytics/revenueВыручка за периодmonthly_revenue
/analytics/returnsСтатистика возвратовreturn_rate
/categories/treeДерево категорийМаппинг в вертикали

9. Recommendation Output

Итоговая модель рекомендации — всё, что возвращает конфигуратор.

class StrategyRecommendation(BaseModel):
seller_id: str
generated_at: datetime = Field(default_factory=datetime.utcnow)

profile: SellerProfile
archetype: SellerArchetype
archetype_confidence: float = 0.0

preset: StrategyPreset
budget: ContentBudget
sku_plan: list[SKUContentPlan]
sku_summary: dict = Field(default_factory=dict)

highlights: list[str] = Field(default_factory=list) # Ключевые выводы
warnings: list[str] = Field(default_factory=list) # Предупреждения
next_steps: list[str] = Field(default_factory=list) # План действий

Пример: продавец электроники, 150 SKU

{
"seller_id": "sf_seller_48291",
"archetype": "growing_brand",
"profile": {
"marketplace": "wildberries", "sku_count": 150,
"monthly_revenue": 4500000, "avg_margin": 0.32,
"is_fashion": false, "niche": "electronics",
"has_own_brand": true, "avg_price": 3200, "return_rate": 0.08
},
"budget": {
"total": 487500,
"per_type": {
"photo": 146250, "video": 121875, "infographic": 97500,
"seo": 73125, "ugc": 24375, "rich": 24375
},
"cost_per_sku": 3250, "investment_level": 1
},
"sku_summary": {
"A": {"count": 30, "total_cost": 97500},
"B": {"count": 45, "total_cost": 73125},
"C": {"count": 75, "total_cost": 36562}
},
"highlights": [
"Свой бренд электроники — фокус на видео-обзорах",
"30 ключевых SKU (группа A) генерируют 80% выручки"
],
"next_steps": [
"1. Съёмка 30 товаров группы A (4 недели)",
"2. Инфографика для групп A+B (2 недели)",
"3. SEO-оптимизация всех 150 карточек (3 недели)",
"4. Видео-обзоры для топ-15 SKU (2 недели)"
]
}

10. Configuration Overrides

Продавец может переопределить любое решение конфигуратора.

class ConfigOverrides(BaseModel):
force_archetype: SellerArchetype | None = None # Принудительный архетип
budget_limit: float | None = Field(None, gt=0) # Лимит бюджета
exclude_content_types: list[str] = Field(default_factory=list) # Исключить типы
priority_skus: list[str] = Field(default_factory=list) # Всегда группа A
force_fashion: bool | None = None # Принудительная вертикаль
seasonal_override: float | None = Field(None, ge=0.1, le=5.0) # Сезонность

Пример:

overrides = ConfigOverrides(
force_fashion=True,
budget_limit=500_000,
exclude_content_types=["ugc", "rich"],
priority_skus=["sku_001", "sku_002"],
)
result = await pipeline.analyze("seller_123", overrides=overrides)
Приоритет переопределений

Overrides применяются после автоматических расчётов. priority_skus всегда попадают в группу A вне зависимости от выручки. budget_limit масштабирует все суммы пропорционально.


11. REST API Endpoints

POST /api/v1/configurator/analyze     — Полный анализ продавца по seller_id
POST /api/v1/configurator/recommend — Рекомендация по готовому профилю (без SalesFinder)
GET /api/v1/configurator/presets — Список всех пресетов стратегий
POST /api/v1/configurator/override — Пересчёт с пользовательскими overrides
@router.post("/analyze", response_model=StrategyRecommendation)
async def analyze_seller(request: AnalyzeRequest,
pipeline: ConfiguratorPipeline = Depends(get_pipeline)):
return await pipeline.analyze(request.seller_id, request.overrides)

@router.post("/recommend", response_model=StrategyRecommendation)
async def recommend_from_profile(profile: SellerProfile,
overrides: ConfigOverrides | None = None,
pipeline: ConfiguratorPipeline = Depends(get_pipeline)):
profile = pipeline.category_detector.enrich(profile)
archetype = pipeline.archetype_classifier.classify(profile)
if overrides and overrides.force_archetype:
archetype = overrides.force_archetype
preset = pipeline.strategy_selector.select(archetype, profile)
budget = pipeline.budget_calculator.calculate(profile, preset, overrides)
return StrategyRecommendation(
seller_id=profile.seller_id, profile=profile,
archetype=archetype, preset=preset, budget=budget, sku_plan=[],
)

@router.get("/presets", response_model=list[StrategyPreset])
async def list_presets():
return list(PRESETS.values())

@router.post("/override", response_model=StrategyRecommendation)
async def apply_override(recommendation_id: str, overrides: ConfigOverrides,
pipeline: ConfiguratorPipeline = Depends(get_pipeline)):
saved = await load_recommendation(recommendation_id)
return await pipeline.analyze(saved.seller_id, overrides)

12. Full Pipeline Example

Полный путь от подключения продавца до готового отчёта.

sequenceDiagram
participant S as Продавец
participant API as REST API
participant SF as SalesFinder
participant P as Pipeline

S->>API: POST /analyze {seller_id}
API->>SF: 4 параллельных запроса
SF-->>API: Данные продавца
API->>P: CategoryDetector.enrich()
API->>P: ArchetypeClassifier.classify()
API->>P: StrategySelector.select()
API->>P: BudgetCalculator.calculate()
API->>P: SKUPrioritizer.prioritize()
API-->>S: StrategyRecommendation

Пошаговый разбор (продавец электроники, 150 SKU, 4.5M/мес)

ШагМодульРезультат
1SalesFinderDataProviderПрофиль: 150 SKU, маржа 32%, собственный бренд
2CategoryDetectoris_fashion=false, niche=electronics
3ArchetypeClassifiergrowing_brand (has_own_brand + revenue >= 500K)
4StrategySelectorПресет Growing Brand без коррекций
5BudgetCalculator487,500₽ (уровень 1, 3,250₽/SKU)
6SKUPrioritizerA: 30 SKU, B: 45 SKU, C: 75 SKU
7OutputJSON с рекомендациями и планом действий

Логика классификации на шаге 3:

  • Enterprise? Нет (150 < 500 SKU, 4.5M < 10M)
  • Fashion Brand? Нет (is_fashion=false)
  • Starter? Нет (150 >= 30 SKU)
  • Reseller? Нет (has_own_brand=true)
  • Growing Brand? Да (has_own_brand + revenue >= 500K)
Время выполнения

Полный pipeline: 1-3 секунды. Основное время — запросы к SalesFinder API. Вычисления конфигуратора занимают менее 50мс.