Реализация AI-системы управления ассортиментом
AI-управление ассортиментом определяет какие товары держать в каждой точке продаж (или категории сайта), в каком количестве и по какой цене. Цель: максимизировать выручку при ограниченном SKU-пространстве полок. Система анализирует продажи, сезонность, каннибализацию и рекомендует ввод/вывод позиций.
Оптимизация ассортимента
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from anthropic import Anthropic
class AssortmentOptimizer:
def __init__(self):
self.llm = Anthropic()
self.demand_model = None
self.cannibalization_matrix = None
def train_demand_model(self, sales_df: pd.DataFrame):
"""Модель прогноза спроса для новых позиций"""
features = ['price', 'category_encoded', 'brand_encoded',
'seasonality_index', 'days_available', 'marketing_spend']
available = [f for f in features if f in sales_df.columns]
X = sales_df[available].fillna(0)
y = sales_df['weekly_units_sold']
self.demand_model = GradientBoostingRegressor(
n_estimators=200, learning_rate=0.05, random_state=42
)
self.demand_model.fit(X, y)
def estimate_cannibalization(self, category_sales: pd.DataFrame) -> pd.DataFrame:
"""Матрица каннибализации между товарами одной категории"""
# Коэффициент каннибализации через correlation продаж
pivot = category_sales.pivot_table(
index='week', columns='sku', values='units_sold', fill_value=0
)
# Отрицательная корреляция = каннибализация
corr = pivot.corr()
cannibalization = pd.DataFrame(
np.where(corr < -0.3, abs(corr), 0),
index=corr.index, columns=corr.columns
)
self.cannibalization_matrix = cannibalization
return cannibalization
def recommend_assortment_changes(self, current_assortment: pd.DataFrame,
candidates: pd.DataFrame,
shelf_space: int) -> dict:
"""Рекомендации по изменению ассортимента"""
# Метрики текущего ассортимента
performance = current_assortment.copy()
performance['margin_per_sqft'] = (
performance['weekly_margin'] /
performance['shelf_space_sqft'].clip(0.1)
)
performance['sales_velocity'] = performance['weekly_units_sold']
# Слабые позиции
weak_threshold = performance['margin_per_sqft'].quantile(0.25)
to_remove = performance[
(performance['margin_per_sqft'] < weak_threshold) &
(performance['weeks_in_assortment'] > 8)
]['sku'].tolist()
# Сильные кандидаты для ввода
if self.demand_model is not None and not candidates.empty:
available_features = [f for f in self.demand_model.feature_names_in_
if f in candidates.columns]
X_cand = candidates[available_features].fillna(0)
candidates['predicted_demand'] = self.demand_model.predict(X_cand)
candidates['predicted_margin'] = (
candidates['predicted_demand'] * candidates['gross_margin']
)
to_add = candidates.nlargest(len(to_remove), 'predicted_margin')['sku'].tolist()
else:
to_add = []
# AI-объяснение рекомендаций
explanation = self._explain_recommendations(to_remove, to_add, performance)
return {
'remove': to_remove,
'add': to_add,
'expected_margin_lift': len(to_remove) * performance['margin_per_sqft'].quantile(0.75) * 0.1,
'explanation': explanation
}
def _explain_recommendations(self, to_remove: list, to_add: list,
performance: pd.DataFrame) -> str:
if to_remove:
removed_stats = performance[performance['sku'].isin(to_remove)][
['sku', 'margin_per_sqft', 'weeks_in_assortment']
].to_dict('records')
else:
removed_stats = []
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=200,
messages=[{
"role": "user",
"content": f"""Explain these assortment change recommendations to a category manager.
Remove {len(to_remove)} SKUs: {removed_stats[:3]}
Add {len(to_add)} SKUs: {to_add[:3]}
2-3 sentences: business rationale for the changes."""
}]
)
return response.content[0].text
Автоматическое управление ассортиментом типично увеличивает выручку на 4-8% и снижает markdown (уценки) на 15-25%. Частота пересмотра: еженедельно для быстрооборачиваемых категорий (FMCG), ежемесячно для fashion/electronics. Минимальный период наблюдения перед выводом SKU: 6-8 недель для стабилизации продаж.







