Ozon AI Аналитика (OpenRouter, advanced)

Мощный AI-аналитик продаж на Ozon с автоматическим выявлением проблемных товаров и рекомендациями

Size

46.9 KB

Version

1.1.1

Created

Dec 4, 2025

Updated

9 days ago

1// ==UserScript==
2// @name		Ozon AI Аналитика (OpenRouter, advanced)
3// @description		Мощный AI-аналитик продаж на Ozon с автоматическим выявлением проблемных товаров и рекомендациями
4// @version		1.1.1
5// @match		https://*.seller.ozon.ru/*
6// @icon		https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('🚀 Ozon AI Аналитика запущена');
12
13    // Утилита для задержки
14    function delay(ms) {
15        return new Promise(resolve => setTimeout(resolve, ms));
16    }
17
18    // Утилита для debounce
19    function debounce(func, wait) {
20        let timeout;
21        return function executedFunction(...args) {
22            const later = () => {
23                clearTimeout(timeout);
24                func(...args);
25            };
26            clearTimeout(timeout);
27            timeout = setTimeout(later, wait);
28        };
29    }
30
31    // Парсинг числа из строки (убираем пробелы, ₽, % и т.д.)
32    function parseNumber(str) {
33        if (!str || str === '—' || str === '') return null;
34        const cleaned = str.replace(/[^\d.,-]/g, '').replace(',', '.');
35        const num = parseFloat(cleaned);
36        return isNaN(num) ? null : num;
37    }
38
39    // Парсинг процента изменения
40    function parsePercentChange(str) {
41        if (!str || str === '—') return null;
42        const match = str.match(/([+-]?\d+)%/);
43        return match ? parseInt(match[1]) : null;
44    }
45
46    // Извлечение значения и процента изменения
47    function parseValueWithChange(text) {
48        if (!text || text === '—') return { value: null, change: null };
49        
50        // Разделяем значение и процент
51        const parts = text.split(/([+-]\d+%)/);
52        const value = parseNumber(parts[0]);
53        const change = parts[1] ? parsePercentChange(parts[1]) : null;
54        
55        return { value, change };
56    }
57
58    // Функция автоматической загрузки всех товаров
59    async function loadAllProducts() {
60        console.log('📦 Начинаем загрузку всех товаров...');
61        
62        let clickCount = 0;
63        const maxClicks = 100; // Защита от бесконечного цикла
64        
65        while (clickCount < maxClicks) {
66            const loadMoreButton = document.querySelector('button.styles_loadMoreButton_2RI3D');
67            
68            if (!loadMoreButton) {
69                console.log('✅ Все товары загружены! Всего кликов:', clickCount);
70                break;
71            }
72            
73            console.log(`🔄 Загружаем еще товары... (клик ${clickCount + 1})`);
74            loadMoreButton.click();
75            clickCount++;
76            
77            // Ждем загрузки новых товаров
78            await delay(2000);
79        }
80        
81        return clickCount;
82    }
83
84    // Парсинг данных товаров из таблицы
85    function parseProductsData() {
86        console.log('📊 Парсим данные товаров...');
87        
88        const products = [];
89        const rows = document.querySelectorAll('tr.ct590-c0.ct590-b9');
90        
91        console.log(`Найдено строк товаров: ${rows.length}`);
92        
93        rows.forEach((row, index) => {
94            try {
95                const cells = row.querySelectorAll('td');
96                
97                // Название товара и артикул
98                const productCell = cells[0];
99                const productLink = productCell.querySelector('a[href*="ozon.ru/product"]');
100                const productName = productLink ? productLink.getAttribute('title') : '';
101                const articleMatch = productCell.textContent.match(/Арт\.\s*(\d+)/);
102                const article = articleMatch ? articleMatch[1] : '';
103                
104                // Получаем текст из всех ячеек
105                const cellTexts = Array.from(cells).map(cell => cell.textContent.trim());
106                
107                // Парсим основные показатели
108                const revenue = parseValueWithChange(cellTexts[2]); // Заказано на сумму
109                const ordersCount = parseValueWithChange(cellTexts[4]); // Количество заказов
110                const views = parseValueWithChange(cellTexts[5]); // Показы
111                const uniqueVisitors = parseValueWithChange(cellTexts[6]); // Уникальные посетители
112                const cardViews = parseValueWithChange(cellTexts[8]); // Просмотры карточки
113                
114                // Конверсии
115                const conversionSearchToCard = parseValueWithChange(cellTexts[12]); // Конверсия из поиска в карточку
116                const stock = parseValueWithChange(cellTexts[13]); // Остаток
117                const conversionCardToCart = parseValueWithChange(cellTexts[15]); // Конверсия из карточки в корзину
118                const addToCart = parseValueWithChange(cellTexts[16]); // Добавлено в корзину
119                const cartViews = parseValueWithChange(cellTexts[18]); // Просмотры корзины
120                const ordersFromCart = parseValueWithChange(cellTexts[20]); // Заказы из корзины
121                const ordersTotal = parseValueWithChange(cellTexts[23]); // Всего заказов
122                
123                // Цена и ДРР
124                const avgPrice = parseValueWithChange(cellTexts[28]); // Средняя цена
125                
126                // Ищем ДРР в других ячейках
127                let drr = { value: null, change: null };
128                for (let i = 30; i < cellTexts.length; i++) {
129                    if (cellTexts[i].includes('%') && !cellTexts[i].includes('₽')) {
130                        const parsed = parseValueWithChange(cellTexts[i]);
131                        if (parsed.value !== null && parsed.value > 0 && parsed.value < 100) {
132                            drr = parsed;
133                            break;
134                        }
135                    }
136                }
137                
138                const product = {
139                    name: productName,
140                    article: article,
141                    revenue: revenue.value,
142                    revenueChange: revenue.change,
143                    ordersCount: ordersCount.value,
144                    ordersCountChange: ordersCount.change,
145                    views: views.value,
146                    viewsChange: views.change,
147                    uniqueVisitors: uniqueVisitors.value,
148                    uniqueVisitorsChange: uniqueVisitors.change,
149                    cardViews: cardViews.value,
150                    cardViewsChange: cardViews.change,
151                    conversionSearchToCard: conversionSearchToCard.value,
152                    conversionSearchToCardChange: conversionSearchToCard.change,
153                    conversionCardToCart: conversionCardToCart.value,
154                    conversionCardToCartChange: conversionCardToCart.change,
155                    stock: stock.value,
156                    stockChange: stock.change,
157                    avgPrice: avgPrice.value,
158                    avgPriceChange: avgPrice.change,
159                    drr: drr.value,
160                    drrChange: drr.change,
161                    addToCart: addToCart.value,
162                    addToCartChange: addToCart.change,
163                    ordersTotal: ordersTotal.value,
164                    ordersTotalChange: ordersTotal.change
165                };
166                
167                products.push(product);
168                
169            } catch (error) {
170                console.error(`Ошибка парсинга товара ${index}:`, error);
171            }
172        });
173        
174        console.log(`✅ Распарсено товаров: ${products.length}`);
175        return products;
176    }
177
178    // AI анализ товаров
179    async function analyzeProductsWithAI(products) {
180        console.log('🤖 Запускаем AI анализ товаров...');
181        
182        // Фильтруем товары с проблемами
183        const problematicProducts = products.filter(p => {
184            // Проверяем критерии проблем
185            const hasRevenueDrop = p.revenueChange !== null && p.revenueChange < -20;
186            const hasOrdersDrop = p.ordersCountChange !== null && p.ordersCountChange < -20;
187            const hasViewsDrop = p.viewsChange !== null && p.viewsChange < -30;
188            const hasConversionDrop = p.conversionCardToCartChange !== null && p.conversionCardToCartChange < -15;
189            const hasHighDRR = p.drr !== null && p.drr > 30;
190            const hasStockIssue = p.stock !== null && p.ordersCount !== null && (p.ordersCount * 7 < p.stock);
191            const hasLowStock = p.stock !== null && p.stock < 10;
192            
193            return hasRevenueDrop || hasOrdersDrop || hasViewsDrop || hasConversionDrop || hasHighDRR || hasStockIssue || hasLowStock;
194        });
195        
196        // Фильтруем успешные товары (значительный рост)
197        const successfulProducts = products.filter(p => {
198            const hasRevenueGrowth = p.revenueChange !== null && p.revenueChange > 30;
199            const hasOrdersGrowth = p.ordersCountChange !== null && p.ordersCountChange > 30;
200            const hasViewsGrowth = p.viewsChange !== null && p.viewsChange > 40;
201            
202            return hasRevenueGrowth || hasOrdersGrowth || hasViewsGrowth;
203        });
204        
205        console.log(`⚠️ Найдено проблемных товаров: ${problematicProducts.length} из ${products.length}`);
206        console.log(`✅ Найдено успешных товаров: ${successfulProducts.length} из ${products.length}`);
207        
208        if (problematicProducts.length === 0 && successfulProducts.length === 0) {
209            return {
210                summary: 'Все товары показывают стабильные показатели. Нет критических проблем и значительных изменений.',
211                problems: [],
212                successes: []
213            };
214        }
215        
216        // Берем топ-20 самых проблемных товаров для AI анализа
217        const topProblematic = problematicProducts.slice(0, 20);
218        
219        // Берем топ-10 самых успешных товаров для AI анализа
220        const topSuccessful = successfulProducts.slice(0, 10);
221        
222        // Формируем промпт для AI
223        const prompt = `Ты - эксперт по аналитике продаж на маркетплейсе Ozon. Проанализируй данные по товарам.
224
225${topProblematic.length > 0 ? `ПРОБЛЕМНЫЕ ТОВАРЫ:
226${topProblematic.map((p, i) => `
227${i + 1}. ${p.name} (Арт. ${p.article})
228   - Выручка: ${p.revenue ? p.revenue.toLocaleString() + ' ₽' : 'н/д'} (${p.revenueChange !== null ? (p.revenueChange > 0 ? '+' : '') + p.revenueChange + '%' : 'н/д'})
229   - Заказы: ${p.ordersCount || 'н/д'} (${p.ordersCountChange !== null ? (p.ordersCountChange > 0 ? '+' : '') + p.ordersCountChange + '%' : 'н/д'})
230   - Показы: ${p.views || 'н/д'} (${p.viewsChange !== null ? (p.viewsChange > 0 ? '+' : '') + p.viewsChange + '%' : 'н/д'})
231   - Просмотры карточки: ${p.cardViews || 'н/д'} (${p.cardViewsChange !== null ? (p.cardViewsChange > 0 ? '+' : '') + p.cardViewsChange + '%' : 'н/д'})
232   - Конверсия поиск→карточка: ${p.conversionSearchToCard !== null ? p.conversionSearchToCard + '%' : 'н/д'} (${p.conversionSearchToCardChange !== null ? (p.conversionSearchToCardChange > 0 ? '+' : '') + p.conversionSearchToCardChange + 'п.п.' : 'н/д'})
233   - Конверсия карточка→корзина: ${p.conversionCardToCart !== null ? p.conversionCardToCart + '%' : 'н/д'} (${p.conversionCardToCartChange !== null ? (p.conversionCardToCartChange > 0 ? '+' : '') + p.conversionCardToCartChange + 'п.п.' : 'н/д'})
234   - ДРР: ${p.drr !== null ? p.drr + '%' : 'н/д'} (${p.drrChange !== null ? (p.drrChange > 0 ? '+' : '') + p.drrChange + 'п.п.' : 'н/д'})
235   - Средняя цена: ${p.avgPrice ? p.avgPrice.toLocaleString() + ' ₽' : 'н/д'} (${p.avgPriceChange !== null ? (p.avgPriceChange > 0 ? '+' : '') + p.avgPriceChange + '%' : 'н/д'})
236`).join('\n')}` : ''}
237
238${topSuccessful.length > 0 ? `
239УСПЕШНЫЕ ТОВАРЫ (значительный рост):
240${topSuccessful.map((p, i) => `
241${i + 1}. ${p.name} (Арт. ${p.article})
242   - Выручка: ${p.revenue ? p.revenue.toLocaleString() + ' ₽' : 'н/д'} (${p.revenueChange !== null ? (p.revenueChange > 0 ? '+' : '') + p.revenueChange + '%' : 'н/д'})
243   - Заказы: ${p.ordersCount || 'н/д'} (${p.ordersCountChange !== null ? (p.ordersCountChange > 0 ? '+' : '') + p.ordersCountChange + '%' : 'н/д'})
244   - Показы: ${p.views || 'н/д'} (${p.viewsChange !== null ? (p.viewsChange > 0 ? '+' : '') + p.viewsChange + '%' : 'н/д'})
245   - Просмотры карточки: ${p.cardViews || 'н/д'} (${p.cardViewsChange !== null ? (p.cardViewsChange > 0 ? '+' : '') + p.cardViewsChange + '%' : 'н/д'})
246   - Конверсия поиск→карточка: ${p.conversionSearchToCard !== null ? p.conversionSearchToCard + '%' : 'н/д'} (${p.conversionSearchToCardChange !== null ? (p.conversionSearchToCardChange > 0 ? '+' : '') + p.conversionSearchToCardChange + 'п.п.' : 'н/д'})
247   - Конверсия карточка→корзина: ${p.conversionCardToCart !== null ? p.conversionCardToCart + '%' : 'н/д'} (${p.conversionCardToCartChange !== null ? (p.conversionCardToCartChange > 0 ? '+' : '') + p.conversionCardToCartChange + 'п.п.' : 'н/д'})
248   - ДРР: ${p.drr !== null ? p.drr + '%' : 'н/д'} (${p.drrChange !== null ? (p.drrChange > 0 ? '+' : '') + p.drrChange + 'п.п.' : 'н/д'})
249   - Средняя цена: ${p.avgPrice ? p.avgPrice.toLocaleString() + ' ₽' : 'н/д'} (${p.avgPriceChange !== null ? (p.avgPriceChange > 0 ? '+' : '') + p.avgPriceChange + '%' : 'н/д'})
250`).join('\n')}` : ''}
251
252Проанализируй:
2531. Для проблемных товаров: уровень критичности (critical/high/medium/low), проблемы и рекомендации
2542. Для успешных товаров: причины роста и как масштабировать успех на другие товары
2553. Общий вывод`;
256
257        try {
258            const aiResponse = await RM.aiCall(prompt, {
259                type: 'json_schema',
260                json_schema: {
261                    name: 'products_analysis',
262                    schema: {
263                        type: 'object',
264                        properties: {
265                            summary: {
266                                type: 'string',
267                                description: 'Общий вывод по всем товарам'
268                            },
269                            problems: {
270                                type: 'array',
271                                items: {
272                                    type: 'object',
273                                    properties: {
274                                        article: { type: 'string' },
275                                        severity: { 
276                                            type: 'string',
277                                            enum: ['critical', 'high', 'medium', 'low']
278                                        },
279                                        problems: {
280                                            type: 'array',
281                                            items: { type: 'string' }
282                                        },
283                                        recommendations: {
284                                            type: 'array',
285                                            items: { type: 'string' }
286                                        }
287                                    },
288                                    required: ['article', 'severity', 'problems', 'recommendations']
289                                }
290                            },
291                            successes: {
292                                type: 'array',
293                                items: {
294                                    type: 'object',
295                                    properties: {
296                                        article: { type: 'string' },
297                                        reasons: {
298                                            type: 'array',
299                                            items: { type: 'string' },
300                                            description: 'Причины успеха товара'
301                                        },
302                                        scalingTips: {
303                                            type: 'array',
304                                            items: { type: 'string' },
305                                            description: 'Как масштабировать успех на другие товары'
306                                        }
307                                    },
308                                    required: ['article', 'reasons', 'scalingTips']
309                                }
310                            }
311                        },
312                        required: ['summary', 'problems', 'successes']
313                    }
314                }
315            });
316            
317            console.log('✅ AI анализ завершен');
318            return aiResponse;
319            
320        } catch (error) {
321            console.error('❌ Ошибка AI анализа:', error);
322            return {
323                summary: 'Не удалось выполнить AI анализ. Попробуйте позже.',
324                problems: [],
325                successes: []
326            };
327        }
328    }
329
330    // Создание UI панели с результатами
331    function createAnalysisPanel(productsData, aiAnalysis) {
332        console.log('🎨 Создаем панель с результатами...');
333        
334        // Удаляем старую панель если есть
335        const oldPanel = document.getElementById('ozon-ai-analysis-panel');
336        if (oldPanel) oldPanel.remove();
337        
338        // Состояние фильтра
339        let currentFilter = 'all'; // all, critical, high, medium, low
340        
341        // Создаем панель
342        const panel = document.createElement('div');
343        panel.id = 'ozon-ai-analysis-panel';
344        panel.style.cssText = `
345            position: fixed;
346            top: 80px;
347            right: 20px;
348            width: 450px;
349            max-height: 80vh;
350            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
351            border-radius: 16px;
352            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
353            z-index: 10000;
354            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
355            overflow: hidden;
356            animation: slideIn 0.4s ease-out;
357        `;
358        
359        // Заголовок
360        const header = document.createElement('div');
361        header.style.cssText = `
362            background: rgba(255,255,255,0.15);
363            backdrop-filter: blur(10px);
364            padding: 20px;
365            display: flex;
366            justify-content: space-between;
367            align-items: center;
368            border-bottom: 1px solid rgba(255,255,255,0.2);
369        `;
370        header.innerHTML = `
371            <div style="display: flex; align-items: center; gap: 12px;">
372                <span style="font-size: 28px;">🤖</span>
373                <div>
374                    <h3 style="margin: 0; color: white; font-size: 18px; font-weight: 600;">AI Аналитика Ozon</h3>
375                    <p style="margin: 4px 0 0 0; color: rgba(255,255,255,0.8); font-size: 12px;">Проанализировано товаров: ${productsData.length}</p>
376                </div>
377            </div>
378            <button id="close-analysis-panel" style="
379                background: rgba(255,255,255,0.2);
380                border: none;
381                color: white;
382                width: 32px;
383                height: 32px;
384                border-radius: 8px;
385                cursor: pointer;
386                font-size: 20px;
387                display: flex;
388                align-items: center;
389                justify-content: center;
390                transition: all 0.2s;
391            ">×</button>
392        `;
393        
394        // Контент
395        const content = document.createElement('div');
396        content.style.cssText = `
397            padding: 20px;
398            max-height: calc(80vh - 100px);
399            overflow-y: auto;
400            background: white;
401        `;
402        
403        // Общий вывод
404        const summary = document.createElement('div');
405        summary.style.cssText = `
406            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
407            color: white;
408            padding: 16px;
409            border-radius: 12px;
410            margin-bottom: 20px;
411            font-size: 14px;
412            line-height: 1.6;
413            box-shadow: 0 4px 12px rgba(245,87,108,0.3);
414        `;
415        summary.innerHTML = `
416            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
417                <span style="font-size: 20px;">📊</span>
418                <strong style="font-size: 15px;">Общий вывод</strong>
419            </div>
420            <p style="margin: 0;">${aiAnalysis.summary}</p>
421        `;
422        content.appendChild(summary);
423        
424        // Статистика
425        const stats = document.createElement('div');
426        stats.style.cssText = `
427            display: grid;
428            grid-template-columns: repeat(2, 1fr);
429            gap: 12px;
430            margin-bottom: 20px;
431        `;
432        
433        const criticalCount = aiAnalysis.products.filter(p => p.severity === 'critical').length;
434        const highCount = aiAnalysis.products.filter(p => p.severity === 'high').length;
435        const mediumCount = aiAnalysis.products.filter(p => p.severity === 'medium').length;
436        const lowCount = aiAnalysis.products.filter(p => p.severity === 'low').length;
437        
438        stats.innerHTML = `
439            <div data-filter="critical" style="background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
440                <div style="font-size: 24px; font-weight: bold;">${criticalCount}</div>
441                <div style="font-size: 12px; opacity: 0.9;">Критичных</div>
442            </div>
443            <div data-filter="high" style="background: linear-gradient(135deg, #ffa502 0%, #ff7f50 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
444                <div style="font-size: 24px; font-weight: bold;">${highCount}</div>
445                <div style="font-size: 12px; opacity: 0.9;">Высокий приоритет</div>
446            </div>
447            <div data-filter="medium" style="background: linear-gradient(135deg, #ffd32a 0%, #ffb142 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
448                <div style="font-size: 24px; font-weight: bold;">${mediumCount}</div>
449                <div style="font-size: 12px; opacity: 0.9;">Средний приоритет</div>
450            </div>
451            <div data-filter="all" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
452                <div style="font-size: 24px; font-weight: bold;">${productsData.length}</div>
453                <div style="font-size: 12px; opacity: 0.9;">Всего товаров</div>
454            </div>
455        `;
456        content.appendChild(stats);
457        
458        // Контейнер для списка товаров
459        const productsContainer = document.createElement('div');
460        productsContainer.id = 'products-container';
461        
462        // Функция отрисовки товаров
463        function renderProducts(filter) {
464            currentFilter = filter;
465            productsContainer.innerHTML = '';
466            
467            // Обновляем стили кнопок фильтра
468            stats.querySelectorAll('[data-filter]').forEach(btn => {
469                if (btn.dataset.filter === filter) {
470                    btn.style.transform = 'scale(1.05)';
471                    btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
472                } else {
473                    btn.style.transform = 'scale(1)';
474                    btn.style.boxShadow = 'none';
475                }
476            });
477            
478            // Фильтруем товары
479            let filteredProducts = aiAnalysis.products;
480            if (filter !== 'all') {
481                filteredProducts = aiAnalysis.products.filter(p => p.severity === filter);
482            }
483            
484            if (filteredProducts.length === 0) {
485                productsContainer.innerHTML = '<p style="text-align: center; color: #666; padding: 20px;">Нет товаров с таким уровнем проблем</p>';
486                return;
487            }
488            
489            const productsTitle = document.createElement('h4');
490            productsTitle.textContent = '⚠️ Проблемные товары';
491            productsTitle.style.cssText = `
492                margin: 0 0 16px 0;
493                color: #333;
494                font-size: 16px;
495                font-weight: 600;
496            `;
497            productsContainer.appendChild(productsTitle);
498            
499            filteredProducts.forEach(aiProduct => {
500                const productData = productsData.find(p => p.article === aiProduct.article);
501                if (!productData) return;
502                
503                const severityColors = {
504                    critical: { bg: '#ffe0e0', border: '#ff6b6b', icon: '🔴' },
505                    high: { bg: '#fff4e0', border: '#ffa502', icon: '🟠' },
506                    medium: { bg: '#fff9e0', border: '#ffd32a', icon: '🟡' },
507                    low: { bg: '#e0f7ff', border: '#4facfe', icon: '🔵' }
508                };
509                
510                const colors = severityColors[aiProduct.severity];
511                
512                // Расчет дополнительных метрик
513                const daysOfStock = productData.ordersCount > 0 ? Math.round(productData.stock / (productData.ordersCount / 7)) : 0;
514                const ctr = productData.views > 0 && productData.cardViews > 0 ? ((productData.cardViews / productData.views) * 100).toFixed(2) : 0;
515                const crl = productData.cardViews > 0 && productData.addToCart > 0 ? ((productData.addToCart / productData.cardViews) * 100).toFixed(2) : 0;
516                const cr = productData.cardViews > 0 && productData.ordersCount > 0 ? ((productData.ordersCount / productData.cardViews) * 100).toFixed(2) : 0;
517                
518                const productCard = document.createElement('div');
519                productCard.style.cssText = `
520                    background: ${colors.bg};
521                    border-left: 4px solid ${colors.border};
522                    padding: 16px;
523                    border-radius: 10px;
524                    margin-bottom: 12px;
525                    transition: all 0.3s;
526                    cursor: pointer;
527                `;
528                
529                // Улучшенное отображение проблем с процентами
530                const problemsWithPercents = aiProduct.problems.map(problem => {
531                    let problemText = problem;
532                    
533                    // Добавляем проценты изменения к проблемам
534                    if (problem.toLowerCase().includes('выручк') && productData.revenueChange !== null) {
535                        problemText += ` (${productData.revenueChange > 0 ? '+' : ''}${productData.revenueChange}%)`;
536                    }
537                    if (problem.toLowerCase().includes('заказ') && productData.ordersCountChange !== null) {
538                        problemText += ` (${productData.ordersCountChange > 0 ? '+' : ''}${productData.ordersCountChange}%)`;
539                    }
540                    if (problem.toLowerCase().includes('показ') && productData.viewsChange !== null) {
541                        problemText += ` (${productData.viewsChange > 0 ? '+' : ''}${productData.viewsChange}%)`;
542                    }
543                    if (problem.toLowerCase().includes('конверси') && productData.conversionCardToCartChange !== null) {
544                        problemText += ` (${productData.conversionCardToCartChange > 0 ? '+' : ''}${productData.conversionCardToCartChange}%)`;
545                    }
546                    if (problem.toLowerCase().includes('дрр') && productData.drrChange !== null) {
547                        problemText += ` (${productData.drrChange > 0 ? '+' : ''}${productData.drrChange}%)`;
548                    }
549                    if (problem.toLowerCase().includes('цен') && productData.avgPriceChange !== null) {
550                        problemText += ` (${productData.avgPriceChange > 0 ? '+' : ''}${productData.avgPriceChange}%)`;
551                    }
552                    
553                    return problemText;
554                });
555                
556                productCard.innerHTML = `
557                    <div style="display: flex; align-items: start; gap: 10px; margin-bottom: 12px;">
558                        <span style="font-size: 20px;">${colors.icon}</span>
559                        <div style="flex: 1;">
560                            <div style="font-weight: 600; color: #333; font-size: 14px; margin-bottom: 4px;">
561                                ${productData.name.substring(0, 60)}${productData.name.length > 60 ? '...' : ''}
562                            </div>
563                            <div style="color: #666; font-size: 12px;">Арт. ${productData.article}</div>
564                        </div>
565                    </div>
566                    
567                    <div style="background: white; padding: 12px; border-radius: 8px; margin-bottom: 12px;">
568                        <div style="font-size: 12px; color: #666; margin-bottom: 8px; font-weight: 600;">📉 Проблемы:</div>
569                        ${problemsWithPercents.map(p => `
570                            <div style="font-size: 12px; color: #333; margin-bottom: 4px; padding-left: 8px; border-left: 2px solid ${colors.border};">
571${p}
572                            </div>
573                        `).join('')}
574                    </div>
575                    
576                    <div style="background: white; padding: 12px; border-radius: 8px; margin-bottom: 12px;">
577                        <div style="font-size: 12px; color: #666; margin-bottom: 8px; font-weight: 600;">💡 Рекомендации:</div>
578                        ${aiProduct.recommendations.map(r => `
579                            <div style="font-size: 12px; color: #333; margin-bottom: 4px; padding-left: 8px; border-left: 2px solid #4caf50;">
580${r}
581                            </div>
582                        `).join('')}
583                    </div>
584                    
585                    <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; font-size: 11px;">
586                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
587                            <div style="color: #666;">Заказы</div>
588                            <div style="font-weight: 600; color: ${productData.ordersCountChange < 0 ? '#ff6b6b' : '#4caf50'};">
589                                ${productData.ordersCount || 'н/д'} ${productData.ordersCountChange !== null ? (productData.ordersCountChange > 0 ? '↑' : '↓') + Math.abs(productData.ordersCountChange) + '%' : ''}
590                            </div>
591                        </div>
592                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
593                            <div style="color: #666;">Показы</div>
594                            <div style="font-weight: 600; color: ${productData.viewsChange < 0 ? '#ff6b6b' : '#4caf50'};">
595                                ${productData.views || 'н/д'} ${productData.viewsChange !== null ? (productData.viewsChange > 0 ? '↑' : '↓') + Math.abs(productData.viewsChange) + '%' : ''}
596                            </div>
597                        </div>
598                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
599                            <div style="color: #666;">Остаток</div>
600                            <div style="font-weight: 600; color: #333;">
601                                ${productData.stock || 'н/д'}
602                            </div>
603                        </div>
604                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
605                            <div style="color: #666;">Дней остатка</div>
606                            <div style="font-weight: 600; color: ${daysOfStock < 7 ? '#ff6b6b' : daysOfStock < 14 ? '#ffa502' : '#4caf50'};">
607                                ${daysOfStock > 0 ? daysOfStock : 'н/д'}
608                            </div>
609                        </div>
610                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
611                            <div style="color: #666;">Клики</div>
612                            <div style="font-weight: 600; color: #333;">
613                                ${productData.cardViews || 'н/д'}
614                            </div>
615                        </div>
616                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
617                            <div style="color: #666;">CTR</div>
618                            <div style="font-weight: 600; color: #333;">
619                                ${ctr}%
620                            </div>
621                        </div>
622                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
623                            <div style="color: #666;">CRL</div>
624                            <div style="font-weight: 600; color: #333;">
625                                ${crl}%
626                            </div>
627                        </div>
628                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
629                            <div style="color: #666;">CR</div>
630                            <div style="font-weight: 600; color: #333;">
631                                ${cr}%
632                            </div>
633                        </div>
634                        <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
635                            <div style="color: #666;">ДРР</div>
636                            <div style="font-weight: 600; color: ${productData.drr > 30 ? '#ff6b6b' : productData.drr > 20 ? '#ffa502' : '#4caf50'};">
637                                ${productData.drr !== null ? productData.drr + '%' : 'н/д'}
638                            </div>
639                        </div>
640                    </div>
641                `;
642                
643                // Hover эффект
644                productCard.addEventListener('mouseenter', () => {
645                    productCard.style.transform = 'translateX(-4px)';
646                    productCard.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
647                });
648                productCard.addEventListener('mouseleave', () => {
649                    productCard.style.transform = 'translateX(0)';
650                    productCard.style.boxShadow = 'none';
651                });
652                
653                // Клик по товару - фильтрация по артикулу
654                productCard.addEventListener('click', () => {
655                    console.log('🔍 Фильтруем по артикулу:', productData.article);
656                    filterByArticle(productData.article);
657                });
658                
659                productsContainer.appendChild(productCard);
660            });
661        }
662        
663        // Добавляем обработчики на кнопки фильтра
664        stats.querySelectorAll('[data-filter]').forEach(btn => {
665            btn.addEventListener('click', () => {
666                renderProducts(btn.dataset.filter);
667            });
668            
669            // Hover эффект
670            btn.addEventListener('mouseenter', () => {
671                if (btn.dataset.filter !== currentFilter) {
672                    btn.style.transform = 'scale(1.05)';
673                }
674            });
675            btn.addEventListener('mouseleave', () => {
676                if (btn.dataset.filter !== currentFilter) {
677                    btn.style.transform = 'scale(1)';
678                }
679            });
680        });
681        
682        content.appendChild(productsContainer);
683        
684        // Изначально показываем все товары
685        renderProducts('all');
686        
687        panel.appendChild(header);
688        panel.appendChild(content);
689        document.body.appendChild(panel);
690        
691        // Обработчик закрытия
692        document.getElementById('close-analysis-panel').addEventListener('click', () => {
693            panel.style.animation = 'slideOut 0.3s ease-in';
694            setTimeout(() => panel.remove(), 300);
695        });
696        
697        // Стили для анимации
698        if (!document.getElementById('ozon-ai-styles')) {
699            const styles = document.createElement('style');
700            styles.id = 'ozon-ai-styles';
701            styles.textContent = `
702                @keyframes slideIn {
703                    from {
704                        transform: translateX(500px);
705                        opacity: 0;
706                    }
707                    to {
708                        transform: translateX(0);
709                        opacity: 1;
710                    }
711                }
712                @keyframes slideOut {
713                    from {
714                        transform: translateX(0);
715                        opacity: 1;
716                    }
717                    to {
718                        transform: translateX(500px);
719                        opacity: 0;
720                    }
721                }
722                #close-analysis-panel:hover {
723                    background: rgba(255,255,255,0.3) !important;
724                    transform: scale(1.1);
725                }
726                #ozon-ai-analysis-panel::-webkit-scrollbar {
727                    width: 8px;
728                }
729                #ozon-ai-analysis-panel::-webkit-scrollbar-track {
730                    background: #f1f1f1;
731                    border-radius: 10px;
732                }
733                #ozon-ai-analysis-panel::-webkit-scrollbar-thumb {
734                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
735                    border-radius: 10px;
736                }
737            `;
738            document.head.appendChild(styles);
739        }
740        
741        console.log('✅ Панель создана');
742    }
743
744    // Функция фильтрации по артикулу на странице
745    function filterByArticle(article) {
746        console.log('🔍 Применяем фильтр по артикулу:', article);
747        
748        // Ищем кнопку "Артикул" в фильтрах
749        const filterButtons = document.querySelectorAll('button');
750        let articleFilterButton = null;
751        
752        filterButtons.forEach(btn => {
753            if (btn.textContent.trim().toLowerCase().includes('артикул')) {
754                articleFilterButton = btn;
755            }
756        });
757        
758        if (articleFilterButton) {
759            console.log('✅ Найдена кнопка фильтра "Артикул"');
760            
761            // Кликаем на кнопку "Артикул"
762            articleFilterButton.click();
763            
764            // Ждем появления поля ввода
765            setTimeout(() => {
766                // Ищем поле ввода артикула
767                const articleInput = document.querySelector('input[placeholder*="артикул" i], input[name*="article" i], input[placeholder*="Артикул" i]');
768                
769                if (articleInput) {
770                    console.log('✅ Найдено поле ввода артикула');
771                    
772                    // Очищаем и вводим артикул
773                    articleInput.value = '';
774                    articleInput.focus();
775                    articleInput.value = article;
776                    
777                    // Триггерим события
778                    articleInput.dispatchEvent(new Event('input', { bubbles: true }));
779                    articleInput.dispatchEvent(new Event('change', { bubbles: true }));
780                    articleInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
781                    
782                    // Ищем кнопку "Применить"
783                    setTimeout(() => {
784                        const applyButtons = document.querySelectorAll('button');
785                        let applyButton = null;
786                        
787                        applyButtons.forEach(btn => {
788                            const text = btn.textContent.trim().toLowerCase();
789                            if (text === 'применить' || text === 'apply') {
790                                applyButton = btn;
791                            }
792                        });
793                        
794                        if (applyButton) {
795                            console.log('✅ Найдена кнопка "Применить"');
796                            applyButton.click();
797                            console.log('✅ Фильтр применен');
798                        } else {
799                            console.log('⚠️ Кнопка "Применить" не найдена');
800                        }
801                    }, 500);
802                } else {
803                    console.log('⚠️ Поле ввода артикула не найдено');
804                }
805            }, 500);
806        } else {
807            console.log('⚠️ Кнопка фильтра "Артикул" не найдена');
808        }
809    }
810
811    // Создание кнопки запуска анализа
812    function createAnalyzeButton() {
813        // Удаляем старую кнопку если есть
814        const oldButton = document.getElementById('ozon-ai-analyze-btn');
815        if (oldButton) oldButton.remove();
816        
817        const button = document.createElement('button');
818        button.id = 'ozon-ai-analyze-btn';
819        button.innerHTML = '🤖 AI Анализ';
820        button.style.cssText = `
821            position: fixed;
822            top: 20px;
823            right: 20px;
824            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
825            color: white;
826            border: none;
827            padding: 14px 24px;
828            border-radius: 12px;
829            font-size: 15px;
830            font-weight: 600;
831            cursor: pointer;
832            z-index: 9999;
833            box-shadow: 0 8px 24px rgba(102,126,234,0.4);
834            transition: all 0.3s;
835            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
836        `;
837        
838        button.addEventListener('mouseenter', () => {
839            button.style.transform = 'translateY(-2px)';
840            button.style.boxShadow = '0 12px 32px rgba(102,126,234,0.5)';
841        });
842        
843        button.addEventListener('mouseleave', () => {
844            button.style.transform = 'translateY(0)';
845            button.style.boxShadow = '0 8px 24px rgba(102,126,234,0.4)';
846        });
847        
848        button.addEventListener('click', async () => {
849            button.disabled = true;
850            button.innerHTML = '⏳ Загрузка...';
851            
852            try {
853                // Загружаем все товары
854                await loadAllProducts();
855                
856                button.innerHTML = '📊 Парсинг...';
857                await delay(500);
858                
859                // Парсим данные
860                const products = parseProductsData();
861                
862                if (products.length === 0) {
863                    alert('Не удалось найти товары на странице');
864                    button.disabled = false;
865                    button.innerHTML = '🤖 AI Анализ';
866                    return;
867                }
868                
869                button.innerHTML = '🤖 AI анализ...';
870                
871                // AI анализ
872                const aiAnalysis = await analyzeProductsWithAI(products);
873                
874                // Создаем панель с результатами
875                createAnalysisPanel(products, aiAnalysis);
876                
877                button.innerHTML = '✅ Готово!';
878                await delay(2000);
879                button.innerHTML = '🤖 AI Анализ';
880                button.disabled = false;
881                
882            } catch (error) {
883                console.error('Ошибка анализа:', error);
884                alert('Произошла ошибка при анализе. Проверьте консоль.');
885                button.innerHTML = '🤖 AI Анализ';
886                button.disabled = false;
887            }
888        });
889        
890        document.body.appendChild(button);
891    }
892
893    // Инициализация
894    function init() {
895        console.log('🎯 Инициализация Ozon AI Аналитика...');
896        
897        // Ждем загрузки страницы
898        if (document.readyState === 'loading') {
899            document.addEventListener('DOMContentLoaded', () => {
900                setTimeout(createAnalyzeButton, 1000);
901            });
902        } else {
903            setTimeout(createAnalyzeButton, 1000);
904        }
905        
906        // Пересоздаем кнопку при изменении DOM (навигация в SPA)
907        const observer = new MutationObserver(debounce(() => {
908            if (!document.getElementById('ozon-ai-analyze-btn')) {
909                createAnalyzeButton();
910            }
911        }, 1000));
912        
913        observer.observe(document.body, {
914            childList: true,
915            subtree: true
916        });
917    }
918
919    init();
920
921})();
Ozon AI Аналитика (OpenRouter, advanced) | Robomonkey