Ozon AI Analyzer 2.0

Мощный AI-аналитик для выявления проблем с продажами, анализа показателей и рекомендаций по улучшению

Size

71.3 KB

Version

1.1.18

Created

Dec 5, 2025

Updated

7 days ago

1// ==UserScript==
2// @name		Ozon AI Analyzer 2.0
3// @description		Мощный AI-аналитик для выявления проблем с продажами, анализа показателей и рекомендаций по улучшению
4// @version		1.1.18
5// @match		https://*.seller.ozon.ru/*
6// @icon		https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.xmlHttpRequest
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    console.log('🚀 Ozon AI Аналитик Продаж запущен');
15
16    // Утилита для задержки
17    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
18
19    // Парсинг числовых значений
20    function parseNumber(str) {
21        if (!str || str === '—' || str === '') return null;
22        // Извлекаем первое число ДО знака процента
23        const match = str.match(/^([\d\s,.]+)/);
24        if (!match) return null;
25        // Убираем пробелы (разделители тысяч), потом парсим
26        const cleaned = match[1].replace(/\s/g, '').replace(',', '.');
27        const num = parseFloat(cleaned);
28        return isNaN(num) ? null : num;
29    }
30
31    // Парсинг процентов
32    function parsePercent(str) {
33        if (!str || str === '—' || str === '') return null;
34        const match = str.match(/([+-]?\d+(?:\.\d+)?)\s*%/);
35        return match ? parseFloat(match[1]) : null;
36    }
37
38    // Парсинг цены (убираем пробелы между цифрами)
39    function parsePrice(str) {
40        if (!str || str === '—' || str === '') return null;
41        // Извлекаем первое число ДО знака процента
42        const match = str.match(/^([\d\s,.]+)/);
43        if (!match) return null;
44        // Убираем пробелы, затем все кроме цифр и точек
45        const cleaned = match[1].replace(/\s/g, '').replace(',', '.');
46        const num = parseFloat(cleaned);
47        return isNaN(num) ? null : num;
48    }
49
50    // Таблица с данными для расчета прибыли
51    const PRODUCT_COST_DATA = {
52        '72252': { cost: 158.4, commission: 0.27, delivery: 90 },
53        '71613': { cost: 108, commission: 0.27, delivery: 90 },
54        '73716': { cost: 126, commission: 0.27, delivery: 90 },
55        '80036': { cost: 103.2, commission: 0.27, delivery: 90 },
56        '73365': { cost: 166.8, commission: 0.27, delivery: 90 },
57        '74881': { cost: 135.6, commission: 0.27, delivery: 90 },
58        '73266': { cost: 708, commission: 0.27, delivery: 90 },
59        '73655': { cost: 219.6, commission: 0.27, delivery: 90 },
60        '75222': { cost: 103.2, commission: 0.27, delivery: 90 },
61        '73358': { cost: 163.2, commission: 0.27, delivery: 90 },
62        '73723': { cost: 116.4, commission: 0.27, delivery: 90 },
63        '74119': { cost: 110.4, commission: 0.27, delivery: 90 },
64        '72573': { cost: 146.4, commission: 0.27, delivery: 90 },
65        '72221': { cost: 90, commission: 0.27, delivery: 90 },
66        '75345': { cost: 111.6, commission: 0.27, delivery: 90 },
67        '73334': { cost: 104.4, commission: 0.27, delivery: 90 },
68        '72184': { cost: 177.6, commission: 0.27, delivery: 90 },
69        '75291': { cost: 100.8, commission: 0.27, delivery: 90 },
70        '73617': { cost: 163.2, commission: 0.27, delivery: 90 },
71        '73976': { cost: 170.4, commission: 0.27, delivery: 90 },
72        '75710': { cost: 172.8, commission: 0.27, delivery: 90 },
73        '76113': { cost: 115.2, commission: 0.27, delivery: 90 },
74        '75499': { cost: 96, commission: 0.27, delivery: 90 },
75        '71569': { cost: 117.6, commission: 0.27, delivery: 90 },
76        '72276': { cost: 286.8, commission: 0.27, delivery: 90 },
77        '75338': { cost: 90, commission: 0.27, delivery: 90 },
78        '75536': { cost: 87.6, commission: 0.27, delivery: 90 },
79        '76014': { cost: 135.6, commission: 0.27, delivery: 90 },
80        '73730': { cost: 121.2, commission: 0.27, delivery: 90 },
81        '75628': { cost: 124.8, commission: 0.27, delivery: 90 },
82        '74249': { cost: 312, commission: 0.27, delivery: 90 },
83        '75468': { cost: 138, commission: 0.27, delivery: 90 },
84        '73495': { cost: 120, commission: 0.27, delivery: 90 },
85        '74393': { cost: 126, commission: 0.27, delivery: 90 },
86        '74188': { cost: 174, commission: 0.27, delivery: 90 },
87        '73907': { cost: 155.376, commission: 0.27, delivery: 90 },
88        '73396': { cost: 112.8, commission: 0.27, delivery: 90 },
89        '71668': { cost: 96, commission: 0.27, delivery: 90 },
90        '73235': { cost: 114, commission: 0.27, delivery: 90 },
91        '75093': { cost: 96, commission: 0.27, delivery: 90 },
92        '73891': { cost: 100.8, commission: 0.27, delivery: 90 },
93        '75505': { cost: 91.2, commission: 0.27, delivery: 90 },
94        '71590': { cost: 100.8, commission: 0.27, delivery: 90 },
95        '73488': { cost: 150, commission: 0.27, delivery: 90 },
96        '75413': { cost: 128.4, commission: 0.27, delivery: 90 },
97        '76403': { cost: 352.8, commission: 0.27, delivery: 90 },
98        '74799': { cost: 162, commission: 0.27, delivery: 90 },
99        '75406': { cost: 117.6, commission: 0.27, delivery: 90 },
100        '75154': { cost: 123.6, commission: 0.27, delivery: 90 },
101        '75383': { cost: 82.8, commission: 0.27, delivery: 90 },
102        '80029': { cost: 67.416, commission: 0.27, delivery: 90 },
103        '76120': { cost: 264, commission: 0.27, delivery: 90 },
104        '72306': { cost: 186, commission: 0.27, delivery: 90 },
105        '75246': { cost: 88.8, commission: 0.27, delivery: 90 },
106        '73228': { cost: 133.476, commission: 0.27, delivery: 90 },
107        '73419': { cost: 165.6, commission: 0.27, delivery: 90 },
108        '74379': { cost: 175.2, commission: 0.27, delivery: 90 },
109        '83356': { cost: 229.2, commission: 0.27, delivery: 90 },
110        '75444': { cost: 123.6, commission: 0.27, delivery: 90 },
111        '79992': { cost: 127.2, commission: 0.27, delivery: 90 },
112        '73709': { cost: 218.4, commission: 0.27, delivery: 90 },
113        '73778': { cost: 144, commission: 0.27, delivery: 90 },
114        '72269': { cost: 194.4, commission: 0.27, delivery: 90 },
115        '73440': { cost: 118.8, commission: 0.27, delivery: 90 },
116        '74669': { cost: 192, commission: 0.27, delivery: 90 },
117        '77660': { cost: 0, commission: 0.27, delivery: 90 },
118        '77578': { cost: 0, commission: 0.27, delivery: 90 },
119        '71545': { cost: 124.8, commission: 0.27, delivery: 90 },
120        '75673': { cost: 97.2, commission: 0.27, delivery: 90 },
121        '76168': { cost: 80.4, commission: 0.27, delivery: 90 },
122        '75277': { cost: 217.2, commission: 0.27, delivery: 90 },
123        '75390': { cost: 108, commission: 0.27, delivery: 90 },
124        '74263': { cost: 160.8, commission: 0.27, delivery: 90 },
125        '74676': { cost: 176.4, commission: 0.27, delivery: 90 },
126        '75727': { cost: 121.2, commission: 0.27, delivery: 90 },
127        '74126': { cost: 111.6, commission: 0.27, delivery: 90 },
128        '74294': { cost: 145.2, commission: 0.27, delivery: 90 },
129        '76069': { cost: 220.8, commission: 0.27, delivery: 90 },
130        '71361': { cost: 204, commission: 0.27, delivery: 90 },
131        '73501': { cost: 114, commission: 0.27, delivery: 90 },
132        '72238': { cost: 102, commission: 0.27, delivery: 90 },
133        '75482': { cost: 218.4, commission: 0.27, delivery: 90 },
134        '76489': { cost: 216, commission: 0.27, delivery: 90 },
135        '76076': { cost: 103.2, commission: 0.27, delivery: 90 },
136        '75437': { cost: 69.6, commission: 0.27, delivery: 90 },
137        '75352': { cost: 72, commission: 0.27, delivery: 90 },
138        '75550': { cost: 112.8, commission: 0.27, delivery: 90 },
139        '75529': { cost: 114, commission: 0.27, delivery: 90 },
140        '76021': { cost: 243.6, commission: 0.27, delivery: 90 },
141        '73969': { cost: 91.2, commission: 0.27, delivery: 90 },
142        '73242': { cost: 133.488, commission: 0.27, delivery: 90 },
143        '80111': { cost: 58.8, commission: 0.27, delivery: 90 },
144        '73693': { cost: 159.6, commission: 0.27, delivery: 90 },
145        '75703': { cost: 208.8, commission: 0.27, delivery: 90 },
146        '74980': { cost: 158.4, commission: 0.27, delivery: 90 },
147        '76380': { cost: 145.6, commission: 0.27, delivery: 90 },
148        '77677': { cost: 129.948, commission: 0.27, delivery: 90 },
149        '75369': { cost: 103.2, commission: 0.27, delivery: 90 },
150        '74713': { cost: 180, commission: 0.27, delivery: 90 },
151        '75024': { cost: 114, commission: 0.27, delivery: 90 },
152        '77615': { cost: 0, commission: 0.27, delivery: 90 },
153        '73389': { cost: 136.8, commission: 0.27, delivery: 90 },
154        '74850': { cost: 169.2, commission: 0.27, delivery: 90 },
155        '75192': { cost: 180, commission: 0.27, delivery: 90 },
156        '73310': { cost: 140.4, commission: 0.27, delivery: 90 },
157        '73280': { cost: 81.6, commission: 0.27, delivery: 90 },
158        '75048': { cost: 122.4, commission: 0.27, delivery: 90 },
159        '74874': { cost: 140.4, commission: 0.27, delivery: 90 },
160        '71675': { cost: 105.6, commission: 0.27, delivery: 90 },
161        '74225': { cost: 153.6, commission: 0.27, delivery: 90 },
162        '74768': { cost: 117.6, commission: 0.27, delivery: 90 },
163        '73136': { cost: 163.2, commission: 0.27, delivery: 90 },
164        '74300': { cost: 134.4, commission: 0.27, delivery: 90 },
165        '76410': { cost: 328.8, commission: 0.27, delivery: 90 },
166        '74898': { cost: 139.2, commission: 0.27, delivery: 90 },
167        '73129': { cost: 159.6, commission: 0.27, delivery: 90 },
168        '75253': { cost: 117.6, commission: 0.27, delivery: 90 },
169        '75666': { cost: 92.4, commission: 0.27, delivery: 90 },
170        '73839': { cost: 112.8, commission: 0.27, delivery: 90 },
171        '75475': { cost: 115.2, commission: 0.27, delivery: 90 },
172        '76397': { cost: 0, commission: 0.27, delivery: 90 },
173        '76083': { cost: 103.2, commission: 0.27, delivery: 90 },
174        '72207': { cost: 123.6, commission: 0.27, delivery: 90 },
175        '76151': { cost: 340.8, commission: 0.27, delivery: 90 },
176        '74911': { cost: 127.2, commission: 0.27, delivery: 90 },
177        '74775': { cost: 141.6, commission: 0.27, delivery: 90 },
178        '74027': { cost: 182.4, commission: 0.27, delivery: 90 },
179        '72245': { cost: 94.8, commission: 0.27, delivery: 90 },
180        '71705': { cost: 112.8, commission: 0.27, delivery: 90 },
181        '75109': { cost: 124.8, commission: 0.27, delivery: 90 },
182        '75260': { cost: 144, commission: 0.27, delivery: 90 },
183        '74584': { cost: 141.6, commission: 0.27, delivery: 90 },
184        '74331': { cost: 128.4, commission: 0.27, delivery: 90 },
185        '75307': { cost: 224.4, commission: 0.27, delivery: 90 },
186        '72542': { cost: 104.4, commission: 0.27, delivery: 90 },
187        '75642': { cost: 144.54, commission: 0.27, delivery: 90 },
188        '75512': { cost: 88.8, commission: 0.27, delivery: 90 },
189        '70999': { cost: 164.4, commission: 0.27, delivery: 90 },
190        '76137': { cost: 103.788, commission: 0.27, delivery: 90 },
191        '74072': { cost: 148.8, commission: 0.27, delivery: 90 },
192        '73297': { cost: 85.2, commission: 0.27, delivery: 90 },
193        '76465': { cost: 301.452, commission: 0.27, delivery: 90 },
194        '71835': { cost: 73.2, commission: 0.27, delivery: 90 },
195        '74324': { cost: 129.6, commission: 0.27, delivery: 90 },
196        '71644': { cost: 132, commission: 0.27, delivery: 90 },
197        '75420': { cost: 106.8, commission: 0.27, delivery: 90 },
198        '74355': { cost: 182.4, commission: 0.27, delivery: 90 },
199        '71651': { cost: 174, commission: 0.27, delivery: 90 },
200        '74973': { cost: 144, commission: 0.27, delivery: 90 },
201        '73341': { cost: 130.8, commission: 0.27, delivery: 90 },
202        '75185': { cost: 157.2, commission: 0.27, delivery: 90 },
203        '74348': { cost: 132, commission: 0.27, delivery: 90 },
204        '75376': { cost: 69.6, commission: 0.27, delivery: 90 },
205        '74942': { cost: 159.6, commission: 0.27, delivery: 90 },
206        '77592': { cost: 0, commission: 0.27, delivery: 90 },
207        '74737': { cost: 186, commission: 0.27, delivery: 90 },
208        '76045': { cost: 235.2, commission: 0.27, delivery: 90 },
209        '74256': { cost: 186, commission: 0.27, delivery: 90 },
210        '75208': { cost: 200.4, commission: 0.27, delivery: 90 },
211        '76601': { cost: 313.2, commission: 0.27, delivery: 90 },
212        '75116': { cost: 346.8, commission: 0.27, delivery: 90 },
213        '73464': { cost: 258, commission: 0.27, delivery: 90 },
214        '74577': { cost: 134.4, commission: 0.27, delivery: 90 },
215        '73792': { cost: 120, commission: 0.27, delivery: 90 },
216        '74997': { cost: 159.6, commission: 0.27, delivery: 90 },
217        '75611': { cost: 150, commission: 0.27, delivery: 90 },
218        '74782': { cost: 145.2, commission: 0.27, delivery: 90 },
219        '75031': { cost: 87.6, commission: 0.27, delivery: 90 },
220        '74195': { cost: 171.6, commission: 0.27, delivery: 90 },
221        '75161': { cost: 96, commission: 0.27, delivery: 90 },
222        '74591': { cost: 132, commission: 0.27, delivery: 90 },
223        '20107': { cost: 283.752, commission: 0.27, delivery: 90 },
224        '74935': { cost: 331.2, commission: 0.27, delivery: 90 },
225        '75062': { cost: 99.6, commission: 0.27, delivery: 90 },
226        '74706': { cost: 130.8, commission: 0.27, delivery: 90 },
227        '75147': { cost: 470.4, commission: 0.27, delivery: 90 },
228        '73181': { cost: 234, commission: 0.27, delivery: 90 },
229        '74812': { cost: 366, commission: 0.27, delivery: 90 },
230        '74805': { cost: 421.2, commission: 0.27, delivery: 90 },
231        '74010': { cost: 112.8, commission: 0.27, delivery: 90 },
232        '73167': { cost: 98.4, commission: 0.27, delivery: 90 },
233        '74416': { cost: 91.2, commission: 0.27, delivery: 90 },
234        '75574': { cost: 1517, commission: 0.27, delivery: 90 },
235        '74546': { cost: 240, commission: 0.27, delivery: 90 },
236        '76199': { cost: 271.2, commission: 0.27, delivery: 90 },
237        '74829': { cost: 372, commission: 0.27, delivery: 90 },
238        '80173': { cost: 768, commission: 0.27, delivery: 90 },
239        '75567': { cost: 1497, commission: 0.27, delivery: 90 },
240        '73754': { cost: 392.4, commission: 0.27, delivery: 90 },
241        '76298': { cost: 637.2, commission: 0.27, delivery: 90 },
242        '74454': { cost: 88.8, commission: 0.27, delivery: 90 },
243        '76205': { cost: 273.6, commission: 0.27, delivery: 90 },
244        '76274': { cost: 662.4, commission: 0.27, delivery: 90 },
245        '76441': { cost: 124.8, commission: 0.27, delivery: 90 },
246        '76434': { cost: 145.2, commission: 0.27, delivery: 90 },
247        '80227': { cost: 768, commission: 0.27, delivery: 90 },
248        '76175': { cost: 285.6, commission: 0.27, delivery: 90 },
249        '76304': { cost: 324, commission: 0.27, delivery: 90 },
250        '71682': { cost: 99.6, commission: 0.27, delivery: 90 },
251        '74959': { cost: 183.6, commission: 0.27, delivery: 90 }
252    };
253
254    // Функция расчета прибыли
255    function calculateProfit(article, revenue, orders, drr) {
256        const costData = PRODUCT_COST_DATA[article];
257        if (!costData || !revenue || !orders) return null;
258        
259        // Расходы на рекламу = выручка * (ДРР / 100)
260        const adCost = drr ? (revenue * (drr / 100)) : 0;
261        
262        // Прибыль = Выручка - (заказы * себестоимость) - (заказы * доставка) - (выручка * комиссия) - расходы на рекламу
263        const profit = revenue - (orders * costData.cost) - (orders * costData.delivery) - (revenue * costData.commission) - adCost;
264        return Math.round(profit * 100) / 100; // Округляем до копеек
265    }
266
267    // Класс для сбора данных о товарах
268    class ProductDataCollector {
269        constructor() {
270            this.products = [];
271            this.isCollecting = false;
272        }
273
274        // Автоматическая подгрузка всех товаров
275        async loadAllProducts() {
276            console.log('📦 Начинаем загрузку всех товаров...');
277            this.isCollecting = true;
278
279            let previousCount = 0;
280            let stableCount = 0; // Счетчик стабильных попыток
281            let attempts = 0;
282            const maxAttempts = 300; // Увеличили максимум попыток до 300
283            const maxStableAttempts = 5; // Увеличили до 5 стабильных попыток
284
285            while (attempts < maxAttempts) {
286                const loadMoreBtn = document.querySelector('button.styles_loadMoreButton_2RI3D');
287                
288                if (!loadMoreBtn) {
289                    console.log('✅ Кнопка "Показать ещё" не найдена - все товары загружены');
290                    break;
291                }
292
293                // Проверяем, не отключена ли кнопка
294                if (loadMoreBtn.disabled || loadMoreBtn.classList.contains('disabled')) {
295                    console.log('✅ Кнопка "Показать ещё" отключена - все товары загружены');
296                    break;
297                }
298
299                // Проверяем, есть ли товары с нулевой выручкой (значит дошли до конца)
300                const rows = document.querySelectorAll('tr.ct590-c0.ct590-b9');
301                let hasZeroRevenue = false;
302                
303                for (const row of rows) {
304                    const cells = row.querySelectorAll('td');
305                    if (cells.length >= 3) {
306                        const revenueText = cells[2].textContent.trim();
307                        // Проверяем, есть ли "0 ₽" или "0₽" в тексте выручки
308                        if (revenueText.match(/^0\s*₽/) || revenueText === '0') {
309                            hasZeroRevenue = true;
310                            console.log('✅ Найден товар с нулевой выручкой - останавливаем загрузку');
311                            break;
312                        }
313                    }
314                }
315                
316                if (hasZeroRevenue) {
317                    console.log('✅ Достигнут конец списка активных товаров');
318                    break;
319                }
320
321                // Прокручиваем к кнопке, чтобы она была видна
322                loadMoreBtn.scrollIntoView({ behavior: 'smooth', block: 'center' });
323                await delay(800);
324
325                console.log(`🔄 Клик по кнопке "Показать ещё" (попытка ${attempts + 1})`);
326                loadMoreBtn.click();
327                
328                // Увеличили задержку до 4 секунд для полной загрузки данных
329                await delay(4000);
330
331                const currentCount = document.querySelectorAll('tr.ct590-c0.ct590-b9').length;
332                console.log(`📊 Загружено товаров: ${currentCount} (было: ${previousCount})`);
333
334                if (currentCount === previousCount) {
335                    stableCount++;
336                    console.log(`⏸️ Количество не изменилось (${stableCount}/${maxStableAttempts})`);
337                    
338                    if (stableCount >= maxStableAttempts) {
339                        console.log('✅ Количество товаров стабильно - загрузка завершена');
340                        break;
341                    }
342                } else {
343                    stableCount = 0; // Сбрасываем счетчик, если количество изменилось
344                }
345
346                previousCount = currentCount;
347                attempts++;
348            }
349
350            const finalCount = document.querySelectorAll('tr.ct590-c0.ct590-b9').length;
351            console.log(`✅ Загрузка завершена. Всего товаров: ${finalCount}`);
352            this.isCollecting = false;
353        }
354
355        // Сбор данных из таблицы
356        collectProductData() {
357            console.log('📊 Собираем данные о товарах...');
358            this.products = [];
359
360            const rows = document.querySelectorAll('tr.ct590-c0.ct590-b9');
361            console.log(`Найдено строк: ${rows.length}`);
362
363            rows.forEach((row, index) => {
364                try {
365                    const cells = row.querySelectorAll('td');
366                    if (cells.length < 10) return;
367
368                    // Извлекаем данные из ячеек
369                    const productData = this.extractProductData(cells);
370                    if (productData) {
371                        this.products.push(productData);
372                    }
373                } catch (error) {
374                    console.error(`Ошибка при обработке строки ${index}:`, error);
375                }
376            });
377
378            console.log(`✅ Собрано товаров: ${this.products.length}`);
379            return this.products;
380        }
381
382        // Извлечение данных о товаре из ячеек
383        extractProductData(cells) {
384            try {
385                // Название и артикул (первая ячейка)
386                const nameCell = cells[0];
387                const nameLink = nameCell.querySelector('a.styles_productName_2qRJi');
388                const captionEl = nameCell.querySelector('.styles_productCaption_7MqtH');
389                
390                const name = nameLink ? nameLink.textContent.trim() : '';
391                const articleMatch = captionEl ? captionEl.textContent.match(/Арт\.\s*(\d+)/) : null;
392                const article = articleMatch ? articleMatch[1] : '';
393
394                if (!name || !article) return null;
395
396                // Получаем текстовое содержимое всех ячеек
397                const cellTexts = Array.from(cells).map(cell => cell.textContent.trim());
398
399                // Парсим основные показатели по правильным индексам
400                // Выручка - индекс 2
401                const revenue = parseNumber(cellTexts[2]);
402                const revenueChange = parsePercent(cellTexts[2]);
403                
404                // Заказано товаров - индекс 20
405                const orders = parseNumber(cellTexts[20]);
406                const ordersChange = parsePercent(cellTexts[20]);
407                
408                // Показы всего - индекс 5
409                const impressions = parseNumber(cellTexts[5]);
410                const impressionsChange = parsePercent(cellTexts[5]);
411                
412                // Посещения карточки товара - индекс 13
413                const cardVisits = parseNumber(cellTexts[13]);
414                const cardVisitsChange = parsePercent(cellTexts[13]);
415                
416                // Конверсия из поиска и каталога в карточку (CTR) - индекс 12
417                const conversionCatalogToCard = parseNumber(cellTexts[12]);
418                const conversionCatalogToCardChange = parsePercent(cellTexts[12]);
419                
420                // Конверсия из карточки в корзину (CRL) - индекс 15
421                const conversionCardToCart = parseNumber(cellTexts[15]);
422                const conversionCardToCartChange = parsePercent(cellTexts[15]);
423                
424                // Добавления в корзину всего - индекс 18
425                const cartAdditions = parseNumber(cellTexts[18]);
426                const cartAdditionsChange = parsePercent(cellTexts[18]);
427                
428                // CR - высчитываем: Заказано товаров / Посещения карточки товаров
429                const cr = (orders && cardVisits && cardVisits > 0) ? parseFloat(((orders / cardVisits) * 100).toFixed(1)) : null;
430                const crChange = null; // Изменение CR нужно высчитывать отдельно
431                
432                // Общая ДРР - индекс 32 (парсим как процент, убираем знак %)
433                const drrText = cellTexts[32] || '';
434                const drrMatch = drrText.match(/(\d+(?:\.\d+)?)\s*%/);
435                const drr = drrMatch ? parseFloat(drrMatch[1]) : null;
436                const drrChange = parsePercent(cellTexts[32]);
437                
438                // Остаток на конец периода - индекс 35
439                const stockText = cellTexts[35] || '';
440                const stockMatch = stockText.match(/(\d+)/);
441                const stock = stockMatch ? parseInt(stockMatch[1]) : null;
442                
443                // Средняя цена - индекс 28 (используем parsePrice для корректного парсинга)
444                const avgPrice = parsePrice(cellTexts[28]);
445                const avgPriceChange = parsePercent(cellTexts[28]);
446                
447                // Среднее время доставки - индекс 37
448                const deliveryTime = cellTexts[37] || null;
449                
450                // Рассчитываем дни остатков: остаток / заказы (округляем до целых)
451                const daysOfStock = (stock && orders && orders > 0) ? Math.round(stock / orders) : null;
452                
453                // Рассчитываем прибыль
454                const profit = calculateProfit(article, revenue, orders, drr);
455                
456                // Рассчитываем прибыль в процентах от выручки
457                const profitPercent = (profit !== null && revenue && revenue > 0) ? 
458                    parseFloat(((profit / revenue) * 100).toFixed(1)) : null;
459
460                const product = {
461                    name,
462                    article,
463                    revenue,
464                    revenueChange,
465                    orders,
466                    ordersChange,
467                    impressions,
468                    impressionsChange,
469                    cardVisits,
470                    cardVisitsChange,
471                    conversionCatalogToCard,
472                    conversionCatalogToCardChange,
473                    conversionCardToCart,
474                    conversionCardToCartChange,
475                    cartAdditions,
476                    cartAdditionsChange,
477                    cr,
478                    crChange,
479                    avgPrice,
480                    avgPriceChange,
481                    drr,
482                    drrChange,
483                    stock,
484                    deliveryTime,
485                    daysOfStock,
486                    profit,
487                    profitPercent,
488                    rawData: cellTexts
489                };
490
491                return product;
492            } catch (error) {
493                console.error('Ошибка извлечения данных товара:', error);
494                return null;
495            }
496        }
497    }
498
499    // Класс для AI анализа
500    class AIAnalyzer {
501        // Батч-анализ товаров с умной фильтрацией
502        async analyzeProducts(products, onProgress) {
503            console.log('🤖 Начинаем AI анализ товаров...');
504            
505            // Сначала вычисляем средние показатели
506            const avgMetrics = this.calculateAverageMetrics(products);
507            console.log('📊 Средние показатели:', avgMetrics);
508            
509            // Разделяем товары на приоритетные и обычные
510            const priorityProducts = [];
511            const normalProducts = [];
512            
513            products.forEach(product => {
514                const needsAIAnalysis = this.needsDetailedAnalysis(product, avgMetrics);
515                if (needsAIAnalysis) {
516                    priorityProducts.push(product);
517                } else {
518                    normalProducts.push(product);
519                }
520            });
521            
522            console.log(`📊 Приоритетных товаров для AI анализа: ${priorityProducts.length}`);
523            console.log(`📊 Обычных товаров (базовый анализ): ${normalProducts.length}`);
524            
525            const analyzedProducts = [];
526            const batchSize = 10; // Увеличили до 10 товаров одновременно
527            
528            // Сначала быстро обрабатываем обычные товары (без AI)
529            normalProducts.forEach(product => {
530                analyzedProducts.push({
531                    ...product,
532                    analysis: this.basicAnalysis(product, avgMetrics)
533                });
534            });
535            
536            // Обновляем прогресс после базового анализа
537            if (onProgress) {
538                const percentage = Math.round((normalProducts.length / products.length) * 100);
539                const remaining = Math.ceil((priorityProducts.length / batchSize) * 2);
540                onProgress(normalProducts.length, products.length, percentage, remaining);
541            }
542            
543            // Анализируем приоритетные товары с AI
544            for (let i = 0; i < priorityProducts.length; i += batchSize) {
545                const batch = priorityProducts.slice(i, i + batchSize);
546                const batchPromises = batch.map(product => this.analyzeProduct(product, avgMetrics, true));
547                
548                const batchResults = await Promise.all(batchPromises);
549                
550                batchResults.forEach((analysis, idx) => {
551                    analyzedProducts.push({
552                        ...batch[idx],
553                        analysis
554                    });
555                });
556                
557                const progress = Math.min(i + batchSize, priorityProducts.length);
558                const totalProgress = normalProducts.length + progress;
559                const percentage = Math.round((totalProgress / products.length) * 100);
560                const remaining = Math.ceil(((priorityProducts.length - progress) / batchSize) * 2);
561                
562                if (onProgress) {
563                    onProgress(totalProgress, products.length, percentage, remaining);
564                }
565                
566                console.log(`✅ Проанализировано ${progress} из ${priorityProducts.length} приоритетных товаров`);
567            }
568            
569            if (onProgress) {
570                onProgress(products.length, products.length, 100, 0);
571            }
572
573            return analyzedProducts;
574        }
575
576        // Определяем, нужен ли детальный AI анализ
577        needsDetailedAnalysis(product, avgMetrics) {
578            const threshold = 5; // Порог отклонения 5%
579            
580            // Если есть значительное падение выручки
581            if (product.revenueChange !== null && product.revenueChange < avgMetrics.revenueChange - threshold) {
582                return true;
583            }
584            
585            // Если есть значительное падение заказов
586            if (product.ordersChange !== null && product.ordersChange < avgMetrics.ordersChange - threshold) {
587                return true;
588            }
589            
590            // Если высокий ДРР
591            if (product.drr !== null && product.drr > 20) {
592                return true;
593            }
594            
595            // Если низкие остатки
596            const daysOfStock = product.orders && product.stock ? Math.floor(product.stock / (product.orders / 7)) : null;
597            if (daysOfStock !== null && daysOfStock < 49) {
598                return true;
599            }
600            
601            // Если значительный рост (для масштабирования)
602            if (product.revenueChange !== null && product.revenueChange > avgMetrics.revenueChange + 15) {
603                return true;
604            }
605            
606            return false;
607        }
608
609        // Базовый анализ без AI (для товаров без проблем)
610        basicAnalysis(product, avgMetrics) {
611            const daysOfStock = product.orders && product.stock ? Math.floor(product.stock / (product.orders / 7)) : null;
612            const isLowStock = daysOfStock !== null && daysOfStock < 7;
613            const isHighDRR = product.drr !== null && product.drr > 20;
614            const isGrowth = this.detectGrowth(product, avgMetrics);
615            const isLowImpressions = product.impressionsChange !== null && product.impressionsChange <= -20;
616            const isLowCR = (product.conversionCardToCartChange !== null && product.conversionCardToCartChange <= -20) || 
617                           (product.conversionCatalogToCardChange !== null && product.conversionCatalogToCardChange <= -20);
618            const isLowProfit = product.profit !== null && product.revenue !== null && product.revenue > 0 && 
619                               (product.profit / product.revenue) < 0.25;
620            
621            // Генерируем рекомендации на основе проблем
622            const recommendations = [];
623            
624            if (isLowImpressions) {
625                // Проверяем ДРР - если снизился, рекомендуем увеличить бюджет
626                if (product.drrChange !== null && product.drrChange < 0) {
627                    recommendations.push('Показы упали. ДРР снизился - рекомендуем увеличить бюджет рекламы');
628                }
629                // Проверяем время доставки - если больше 3 дней, это снижает видимость
630                if (product.deliveryTime && parseInt(product.deliveryTime) > 3) {
631                    recommendations.push('Показы упали. Время доставки высокое - снижает видимость');
632                }
633                // Проверяем остатки - если маленькие, рекомендуем пополнить
634                if (isLowStock) {
635                    recommendations.push('Показы упали. Низкие остатки - рекомендуем пополнить склад');
636                }
637                // Если нет конкретных причин
638                if (recommendations.length === 0) {
639                    recommendations.push('Показы упали. Проверьте настройки рекламы и позиции товара');
640                }
641            }
642            
643            if (isLowCR) {
644                // Проверяем среднюю цену - если выросла, рекомендуем снизить
645                if (product.avgPriceChange !== null && product.avgPriceChange > 0) {
646                    recommendations.push('CR упал. Цена выросла - рекомендуем снизить цену для повышения конверсии');
647                }
648                // Проверяем время доставки
649                if (product.deliveryTime && parseInt(product.deliveryTime) > 3) {
650                    recommendations.push('CR упал. Время доставки высокое - снижает конверсию');
651                }
652                // Проверяем остатки
653                if (isLowStock) {
654                    recommendations.push('CR упал. Низкие остатки - снижает конверсию');
655                }
656                // Если нет конкретных причин
657                if (recommendations.length === 0) {
658                    recommendations.push('CR упал. Проверьте карточку товара, фото и описание');
659                }
660            }
661            
662            if (isLowProfit) {
663                recommendations.push('Низкая прибыль. Проверьте себестоимость, цену и рекламные расходы');
664            }
665            
666            if (isLowStock && !isLowImpressions && !isLowCR) {
667                recommendations.push('Низкие остатки - рекомендуем пополнить склад');
668            }
669            
670            if (isHighDRR && !isLowImpressions && !isLowCR) {
671                recommendations.push('Высокий ДРР - рекомендуем оптимизировать рекламные кампании');
672            }
673            
674            // Если нет проблем - выводим "Всё хорошо"
675            if (recommendations.length === 0) {
676                recommendations.push('Всё хорошо, рекомендаций нет');
677            }
678            
679            return {
680                priority: 'low',
681                problems: [],
682                recommendations,
683                daysOfStock,
684                isLowStock,
685                isHighDRR,
686                isGrowth,
687                isLowImpressions,
688                isLowCR,
689                isLowProfit
690            };
691        }
692
693        // Вычисление средних показателей
694        calculateAverageMetrics(products) {
695            const validProducts = products.filter(p => p.revenueChange !== null);
696            if (validProducts.length === 0) return { revenueChange: 0, ordersChange: 0, impressionsChange: 0 };
697            
698            const sum = validProducts.reduce((acc, p) => ({
699                revenueChange: acc.revenueChange + (p.revenueChange || 0),
700                ordersChange: acc.ordersChange + (p.ordersChange || 0),
701                impressionsChange: acc.impressionsChange + (p.impressionsChange || 0)
702            }), { revenueChange: 0, ordersChange: 0, impressionsChange: 0 });
703            
704            return {
705                revenueChange: sum.revenueChange / validProducts.length,
706                ordersChange: sum.ordersChange / validProducts.length,
707                impressionsChange: sum.impressionsChange / validProducts.length
708            };
709        }
710
711        async analyzeProduct(product, avgMetrics, useAI = true) {
712            try {
713                const daysOfStock = product.orders && product.stock ? Math.floor(product.stock / (product.orders / 7)) : null;
714                const isLowStock = daysOfStock !== null && daysOfStock < 49;
715                const isHighDRR = product.drr !== null && product.drr > 20;
716                const isGrowth = this.detectGrowth(product, avgMetrics);
717                const isLowCR = (product.conversionCardToCartChange !== null && product.conversionCardToCartChange <= -20) || 
718                               (product.conversionCatalogToCardChange !== null && product.conversionCatalogToCardChange <= -20);
719                const isLowProfit = product.profit !== null && product.revenue !== null && product.revenue > 0 && 
720                                   (product.profit / product.revenue) < 0.25;
721                
722                if (!useAI) {
723                    return this.basicAnalysis(product, avgMetrics);
724                }
725                
726                // Формируем промпт для AI
727                const prompt = `Проанализируй товар и определи проблемы:
728
729Товар: ${product.name}
730Показатели:
731- Выручка: ${product.revenue || 'н/д'} ₽ (${product.revenueChange || 0}%)
732- Заказы: ${product.orders || 'н/д'} (${product.ordersChange || 0}%)
733- Показы: ${product.impressions || 'н/д'} (${product.impressionsChange || 0}%)
734- Посещения карточки: ${product.cardVisits || 'н/д'} (${product.cardVisitsChange || 0}%)
735- CTR: ${product.conversionCatalogToCard || 'н/д'}%
736- CRL: ${product.conversionCardToCart || 'н/д'}%
737- CR: ${product.cr || 'н/д'}%
738- ДРР: ${product.drr || 'н/д'}%
739- Остаток: ${product.stock || 'н/д'} шт (хватит на ${daysOfStock || 'н/д'} дней)
740
741Определи приоритет (critical/high/medium/low), проблемы и рекомендации.`;
742
743                const response = await RM.aiCall(prompt, {
744                    type: 'json_schema',
745                    json_schema: {
746                        name: 'product_analysis',
747                        schema: {
748                            type: 'object',
749                            properties: {
750                                priority: {
751                                    type: 'string',
752                                    enum: ['critical', 'high', 'medium', 'low']
753                                },
754                                problems: {
755                                    type: 'array',
756                                    items: {
757                                        type: 'object',
758                                        properties: {
759                                            type: { type: 'string' },
760                                            description: { type: 'string' }
761                                        },
762                                        required: ['type', 'description']
763                                    }
764                                },
765                                recommendations: {
766                                    type: 'array',
767                                    items: { type: 'string' }
768                                }
769                            },
770                            required: ['priority', 'problems', 'recommendations']
771                        }
772                    }
773                });
774
775                return {
776                    ...response,
777                    daysOfStock,
778                    isLowStock,
779                    isHighDRR,
780                    isGrowth,
781                    isLowCR,
782                    isLowProfit
783                };
784            } catch (error) {
785                console.error('Ошибка AI анализа:', error);
786                return this.basicAnalysis(product, avgMetrics);
787            }
788        }
789
790        // Определение роста на основе средних показателей
791        detectGrowth(product, avgMetrics) {
792            const threshold = 15; // Порог отклонения от среднего в %
793            
794            // Если выручка растет значительно выше среднего
795            if (product.revenueChange !== null && 
796                product.revenueChange > avgMetrics.revenueChange + threshold) {
797                return true;
798            }
799            
800            // Если заказы растут значительно выше среднего
801            if (product.ordersChange !== null && 
802                product.ordersChange > avgMetrics.ordersChange + threshold) {
803                return true;
804            }
805            
806            return false;
807        }
808    }
809
810    // Класс для UI
811    class AnalyticsUI {
812        constructor() {
813            this.container = null;
814            this.filteredProducts = [];
815            this.allProducts = [];
816            this.currentFilter = 'all';
817        }
818
819        createUI() {
820            console.log('🎨 Создаем UI...');
821
822            // Создаем контейнер для нашего UI
823            this.container = document.createElement('div');
824            this.container.id = 'ozon-ai-analytics';
825            this.container.style.cssText = `
826                position: fixed;
827                top: 80px;
828                right: 20px;
829                width: 500px;
830                max-height: 85vh;
831                background: white;
832                border-radius: 12px;
833                box-shadow: 0 4px 20px rgba(0,0,0,0.15);
834                z-index: 10000;
835                overflow: hidden;
836                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
837            `;
838
839            // Заголовок
840            const header = document.createElement('div');
841            header.style.cssText = `
842                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
843                color: white;
844                padding: 18px 24px;
845                font-weight: 600;
846                font-size: 18px;
847                display: flex;
848                justify-content: space-between;
849                align-items: center;
850            `;
851            header.innerHTML = `
852                <span>🤖 AI Аналитик Продаж</span>
853                <button id="ozon-ai-close" style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; padding: 0; width: 28px; height: 28px;">×</button>
854            `;
855
856            // Кнопка запуска анализа
857            const startButton = document.createElement('button');
858            startButton.id = 'ozon-ai-start';
859            startButton.textContent = '🚀 Запустить анализ';
860            startButton.style.cssText = `
861                width: calc(100% - 40px);
862                margin: 20px;
863                padding: 16px;
864                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
865                color: white;
866                border: none;
867                border-radius: 8px;
868                font-size: 16px;
869                font-weight: 600;
870                cursor: pointer;
871                transition: transform 0.2s;
872            `;
873            startButton.onmouseover = () => startButton.style.transform = 'scale(1.02)';
874            startButton.onmouseout = () => startButton.style.transform = 'scale(1)';
875
876            // Контейнер для контента
877            const content = document.createElement('div');
878            content.id = 'ozon-ai-content';
879            content.style.cssText = `
880                padding: 20px;
881                max-height: calc(85vh - 140px);
882                overflow-y: auto;
883            `;
884
885            this.container.appendChild(header);
886            this.container.appendChild(startButton);
887            this.container.appendChild(content);
888
889            document.body.appendChild(this.container);
890
891            // События
892            document.getElementById('ozon-ai-close').addEventListener('click', () => {
893                this.container.style.display = 'none';
894            });
895
896            document.getElementById('ozon-ai-start').addEventListener('click', () => {
897                this.startAnalysis();
898            });
899
900            console.log('✅ UI создан');
901        }
902
903        async startAnalysis() {
904            const content = document.getElementById('ozon-ai-content');
905            const startButton = document.getElementById('ozon-ai-start');
906            
907            startButton.disabled = true;
908            startButton.textContent = '⏳ Загрузка товаров...';
909
910            try {
911                // Шаг 1: Загрузка всех товаров
912                const collector = new ProductDataCollector();
913                await collector.loadAllProducts();
914
915                startButton.textContent = '📊 Сбор данных...';
916                
917                // Шаг 2: Сбор данных
918                const products = collector.collectProductData();
919
920                if (products.length === 0) {
921                    content.innerHTML = '<p style="color: #e74c3c; padding: 20px; text-align: center; font-size: 14px;">❌ Не удалось найти товары. Убедитесь, что вы на странице аналитики.</p>';
922                    startButton.disabled = false;
923                    startButton.textContent = '🚀 Запустить анализ';
924                    return;
925                }
926
927                // Шаг 3: AI анализ с прогрессом
928                const analyzer = new AIAnalyzer();
929                
930                const onProgress = (current, total, percentage, remaining) => {
931                    const remainingText = remaining > 0 ? ` (~${remaining} сек)` : '';
932                    startButton.textContent = `🤖 AI анализ: ${current}/${total} (${percentage}%)${remainingText}`;
933                };
934                
935                const analyzedProducts = await analyzer.analyzeProducts(products, onProgress);
936
937                this.allProducts = analyzedProducts;
938                this.filteredProducts = analyzedProducts;
939
940                // Шаг 4: Отображение результатов
941                this.displayResults(analyzedProducts);
942
943                startButton.textContent = '✅ Анализ завершен';
944                startButton.disabled = false;
945
946            } catch (error) {
947                console.error('Ошибка анализа:', error);
948                content.innerHTML = `<p style="color: #e74c3c; padding: 20px; text-align: center; font-size: 14px;">❌ Ошибка: ${error.message}</p>`;
949                startButton.disabled = false;
950                startButton.textContent = '🚀 Запустить анализ';
951            }
952        }
953
954        displayResults(products) {
955            const content = document.getElementById('ozon-ai-content');
956            
957            // Фильтры
958            const filters = this.createFilters(products);
959            
960            // Список товаров
961            const productsList = this.createProductsList(products);
962
963            content.innerHTML = '';
964            content.appendChild(filters);
965            content.appendChild(productsList);
966        }
967
968        createFilters(products) {
969            const filtersContainer = document.createElement('div');
970            filtersContainer.style.cssText = `
971                margin-bottom: 20px;
972                display: flex;
973                flex-wrap: wrap;
974                gap: 8px;
975            `;
976
977            // Подсчет товаров по категориям
978            const critical = products.filter(p => p.analysis.priority === 'critical').length;
979            const high = products.filter(p => p.analysis.priority === 'high').length;
980            const lowStock = products.filter(p => p.analysis.isLowStock).length;
981            const highDRR = products.filter(p => p.analysis.isHighDRR).length;
982            const growth = products.filter(p => p.analysis.isGrowth).length;
983            const lowImpressions = products.filter(p => p.analysis.isLowImpressions).length;
984            const lowCR = products.filter(p => p.analysis.isLowCR).length;
985            const lowProfit = products.filter(p => p.analysis.isLowProfit).length;
986
987            const filterButtons = [
988                { id: 'all', label: `Все (${products.length})`, color: '#95a5a6' },
989                { id: 'critical', label: `🔴 Критичные (${critical})`, color: '#e74c3c' },
990                { id: 'high', label: `🟠 Высокий (${high})`, color: '#f39c12' },
991                { id: 'lowStock', label: `📦 Низкие остатки (${lowStock})`, color: '#e67e22' },
992                { id: 'highDRR', label: `💰 Высокий ДРР (${highDRR})`, color: '#c0392b' },
993                { id: 'lowImpressions', label: `📉 Упали показы (${lowImpressions})`, color: '#9b59b6' },
994                { id: 'lowCR', label: `📊 Упал CR (${lowCR})`, color: '#e91e63' },
995                { id: 'lowProfit', label: `💸 Низкая прибыль (${lowProfit})`, color: '#d32f2f' },
996                { id: 'growth', label: `📈 Рост (${growth})`, color: '#27ae60' }
997            ];
998
999            filterButtons.forEach(filter => {
1000                const btn = document.createElement('button');
1001                btn.textContent = filter.label;
1002                btn.style.cssText = `
1003                    padding: 8px 12px;
1004                    background: ${this.currentFilter === filter.id ? filter.color : '#ecf0f1'};
1005                    color: ${this.currentFilter === filter.id ? 'white' : '#2c3e50'};
1006                    border: none;
1007                    border-radius: 6px;
1008                    font-size: 13px;
1009                    font-weight: 500;
1010                    cursor: pointer;
1011                    transition: all 0.2s;
1012                `;
1013                
1014                btn.addEventListener('click', () => {
1015                    this.currentFilter = filter.id;
1016                    this.applyFilter(filter.id);
1017                });
1018
1019                filtersContainer.appendChild(btn);
1020            });
1021
1022            return filtersContainer;
1023        }
1024
1025        applyFilter(filterId) {
1026            let filtered = this.allProducts;
1027
1028            switch(filterId) {
1029            case 'critical':
1030                filtered = this.allProducts.filter(p => p.analysis.priority === 'critical');
1031                break;
1032            case 'high':
1033                filtered = this.allProducts.filter(p => p.analysis.priority === 'high');
1034                break;
1035            case 'lowStock':
1036                filtered = this.allProducts.filter(p => p.analysis.isLowStock);
1037                break;
1038            case 'highDRR':
1039                filtered = this.allProducts.filter(p => p.analysis.isHighDRR);
1040                break;
1041            case 'lowImpressions':
1042                filtered = this.allProducts.filter(p => p.analysis.isLowImpressions);
1043                break;
1044            case 'lowCR':
1045                filtered = this.allProducts.filter(p => p.analysis.isLowCR);
1046                break;
1047            case 'lowProfit':
1048                filtered = this.allProducts.filter(p => p.analysis.isLowProfit);
1049                break;
1050            case 'growth':
1051                filtered = this.allProducts.filter(p => p.analysis.isGrowth);
1052                break;
1053            }
1054
1055            this.filteredProducts = filtered;
1056            this.displayResults(filtered);
1057        }
1058
1059        createProductsList(products) {
1060            const list = document.createElement('div');
1061            list.style.cssText = `
1062                display: flex;
1063                flex-direction: column;
1064                gap: 12px;
1065            `;
1066
1067            products.forEach(product => {
1068                const card = this.createProductCard(product);
1069                list.appendChild(card);
1070            });
1071
1072            return list;
1073        }
1074
1075        formatMetric(value, change, isPercent = false) {
1076            // Округляем проценты до десятых
1077            let displayValue = value;
1078            if (isPercent && value !== null && value !== undefined) {
1079                displayValue = parseFloat(value.toFixed(1));
1080            }
1081            
1082            const valueStr = displayValue !== null && displayValue !== undefined ? 
1083                (isPercent ? `${displayValue}%` : displayValue.toLocaleString()) : '—';
1084            
1085            if (change === null || change === undefined) return valueStr;
1086            
1087            // Округляем изменение до десятых
1088            const roundedChange = parseFloat(change.toFixed(1));
1089            const changeStr = roundedChange > 0 ? `+${roundedChange}%` : `${roundedChange}%`;
1090            const color = roundedChange > 0 ? '#27ae60' : '#e74c3c';
1091            
1092            return `${valueStr} <span style="color: ${color}; font-size: 11px;">(${changeStr})</span>`;
1093        }
1094
1095        createProductCard(product) {
1096            const card = document.createElement('div');
1097            
1098            const priorityColors = {
1099                critical: '#e74c3c',
1100                high: '#f39c12',
1101                medium: '#3498db',
1102                low: '#95a5a6'
1103            };
1104
1105            const priorityLabels = {
1106                critical: '🔴 Критичный',
1107                high: '🟠 Высокий',
1108                medium: '🟡 Средний',
1109                low: '🟢 Низкий'
1110            };
1111
1112            // Определяем цвет прибыли
1113            const profitColor = product.profitPercent !== null && product.profitPercent >= 25 ? '#27ae60' : '#e74c3c';
1114
1115            card.style.cssText = `
1116                background: white;
1117                border: 2px solid ${priorityColors[product.analysis.priority]};
1118                border-radius: 8px;
1119                padding: 14px;
1120                cursor: pointer;
1121                transition: all 0.2s;
1122            `;
1123
1124            card.innerHTML = `
1125                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
1126                    <div style="flex: 1;">
1127                        <div style="font-weight: 600; font-size: 14px; color: #2c3e50; margin-bottom: 4px;">${product.name}</div>
1128                        <div style="font-size: 12px; color: #7f8c8d;">Арт. ${product.article}</div>
1129                    </div>
1130                    <div style="background: ${priorityColors[product.analysis.priority]}; color: white; padding: 5px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; white-space: nowrap;">
1131                        ${priorityLabels[product.analysis.priority]}
1132                    </div>
1133                </div>
1134
1135                ${product.analysis.problems.length > 0 ? `
1136                <div style="background: #fff3cd; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 500; color: #856404;">
1137                    ${product.analysis.problems.slice(0, 2).map(p => `
1138                        <div style="margin-bottom: 4px;">⚠️ ${p.description}</div>
1139                    `).join('')}
1140                </div>
1141                ` : ''}
1142
1143                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 12px;">
1144                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
1145                        <div><strong>Выручка:</strong> ${this.formatMetric(product.revenue, product.revenueChange)}</div>
1146                        <div><strong>Прибыль:</strong> <span style="color: ${profitColor}; font-weight: 600;">${product.profit !== null ? `${product.profit.toLocaleString()}` : '—'} ${product.profitPercent !== null ? `(${product.profitPercent}%)` : ''}</span></div>
1147                        <div><strong>Показы:</strong> ${this.formatMetric(product.impressions, product.impressionsChange)}</div>
1148                        <div><strong>Карточка:</strong> ${this.formatMetric(product.cardVisits, product.cardVisitsChange)}</div>
1149                        <div><strong>Заказы:</strong> ${this.formatMetric(product.orders, product.ordersChange)}</div>
1150                        <div><strong>Корзины:</strong> ${this.formatMetric(product.cartAdditions, product.cartAdditionsChange)}</div>
1151                        <div><strong>CTR:</strong> ${this.formatMetric(product.conversionCatalogToCard, product.conversionCatalogToCardChange, true)}</div>
1152                        <div><strong>CRL:</strong> ${this.formatMetric(product.conversionCardToCart, product.conversionCardToCartChange, true)}</div>
1153                        <div><strong>CR:</strong> ${this.formatMetric(product.cr, product.crChange, true)}</div>
1154                        <div><strong>ДРР:</strong> ${this.formatMetric(product.drr, product.drrChange, true)}</div>
1155                        <div><strong>Цена:</strong> ${this.formatMetric(product.avgPrice, product.avgPriceChange)}</div>
1156                        <div><strong>Доставка:</strong> ${product.deliveryTime || '—'}</div>
1157                        <div><strong>Остаток:</strong> ${product.stock || '—'} шт</div>
1158                        <div><strong>На дней:</strong> ${product.daysOfStock || '—'}</div>
1159                    </div>
1160                </div>
1161
1162                <div style="font-size: 11px; color: #7f8c8d; border-top: 1px solid #ecf0f1; padding-top: 8px;">
1163                    <strong>Рекомендации:</strong>
1164                    ${product.analysis.recommendations.slice(0, 1).map(r => `<div>• ${r}</div>`).join('')}
1165                </div>
1166            `;
1167
1168            card.addEventListener('click', () => {
1169                this.showProductDetails(product);
1170            });
1171
1172            return card;
1173        }
1174
1175        showProductDetails(product) {
1176            // Определяем цвет прибыли
1177            const profitColor = product.profitPercent !== null && product.profitPercent >= 25 ? '#27ae60' : '#e74c3c';
1178
1179            // Создаем модальное окно с детальной информацией
1180            const modal = document.createElement('div');
1181            modal.style.cssText = `
1182                position: fixed;
1183                top: 0;
1184                left: 0;
1185                right: 0;
1186                bottom: 0;
1187                background: rgba(0,0,0,0.7);
1188                z-index: 10001;
1189                display: flex;
1190                align-items: center;
1191                justify-content: center;
1192                padding: 20px;
1193            `;
1194
1195            const modalContent = document.createElement('div');
1196            modalContent.style.cssText = `
1197                background: white;
1198                border-radius: 12px;
1199                padding: 24px;
1200                max-width: 700px;
1201                max-height: 80vh;
1202                overflow-y: auto;
1203                width: 100%;
1204            `;
1205
1206            modalContent.innerHTML = `
1207                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
1208                    <h2 style="margin: 0; font-size: 18px; color: #2c3e50;">${product.name}</h2>
1209                    <button id="close-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #95a5a6;">×</button>
1210                </div>
1211
1212                <div style="background: #f8f9fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
1213                    <div style="font-size: 12px; color: #7f8c8d; margin-bottom: 12px;">Артикул: ${product.article}</div>
1214                    
1215                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; font-size: 13px;">
1216                        <div>
1217                            <div style="color: #7f8c8d; font-size: 11px;">Выручка</div>
1218                            <div style="font-weight: 600;">${this.formatMetric(product.revenue, product.revenueChange)}</div>
1219                        </div>
1220                        
1221                        <div>
1222                            <div style="color: #7f8c8d; font-size: 11px;">Прибыль</div>
1223                            <div style="font-weight: 600; color: ${profitColor};">${product.profit !== null ? `${product.profit.toLocaleString()}` : '—'}</div>
1224                        </div>
1225                        
1226                        <div>
1227                            <div style="color: #7f8c8d; font-size: 11px;">Прибыль %</div>
1228                            <div style="font-weight: 600; color: ${profitColor};">${product.profitPercent !== null ? `${product.profitPercent}%` : '—'}</div>
1229                        </div>
1230                        
1231                        <div>
1232                            <div style="color: #7f8c8d; font-size: 11px;">Заказы</div>
1233                            <div style="font-weight: 600;">${this.formatMetric(product.orders, product.ordersChange)}</div>
1234                        </div>
1235
1236                        <div>
1237                            <div style="color: #7f8c8d; font-size: 11px;">Показы всего</div>
1238                            <div style="font-weight: 600;">${this.formatMetric(product.impressions, product.impressionsChange)}</div>
1239                        </div>
1240
1241                        <div>
1242                            <div style="color: #7f8c8d; font-size: 11px;">Посещения карточки</div>
1243                            <div style="font-weight: 600;">${this.formatMetric(product.cardVisits, product.cardVisitsChange)}</div>
1244                        </div>
1245
1246                        <div>
1247                            <div style="color: #7f8c8d; font-size: 11px;">Добавления в корзину</div>
1248                            <div style="font-weight: 600;">${this.formatMetric(product.cartAdditions, product.cartAdditionsChange)}</div>
1249                        </div>
1250
1251                        <div>
1252                            <div style="color: #7f8c8d; font-size: 11px;">CTR (каталог→карточка)</div>
1253                            <div style="font-weight: 600;">${this.formatMetric(product.conversionCatalogToCard, product.conversionCatalogToCardChange, true)}</div>
1254                        </div>
1255
1256                        <div>
1257                            <div style="color: #7f8c8d; font-size: 11px;">CRL (карточка→корзина)</div>
1258                            <div style="font-weight: 600;">${this.formatMetric(product.conversionCardToCart, product.conversionCardToCartChange, true)}</div>
1259                        </div>
1260
1261                        <div>
1262                            <div style="color: #7f8c8d; font-size: 11px;">CR (заказы/карточка)</div>
1263                            <div style="font-weight: 600;">${this.formatMetric(product.cr, product.crChange, true)}</div>
1264                        </div>
1265
1266                        <div>
1267                            <div style="color: #7f8c8d; font-size: 11px;">ДРР</div>
1268                            <div style="font-weight: 600;">${this.formatMetric(product.drr, product.drrChange, true)}</div>
1269                        </div>
1270
1271                        <div>
1272                            <div style="color: #7f8c8d; font-size: 11px;">Время доставки</div>
1273                            <div style="font-weight: 600;">${product.deliveryTime || '—'}</div>
1274                        </div>
1275
1276                        <div>
1277                            <div style="color: #7f8c8d; font-size: 11px;">Остаток на конец периода</div>
1278                            <div style="font-weight: 600;">${product.stock || '—'} шт</div>
1279                        </div>
1280
1281                        <div>
1282                            <div style="color: #7f8c8d; font-size: 11px;">Хватит на дней</div>
1283                            <div style="font-weight: 600;">${product.daysOfStock || '—'}</div>
1284                        </div>
1285                    </div>
1286                </div>
1287
1288                <div style="margin-bottom: 16px;">
1289                    <h3 style="font-size: 14px; color: #2c3e50; margin-bottom: 8px;">🔍 Выявленные проблемы</h3>
1290                    ${product.analysis.problems.length > 0 ? product.analysis.problems.map(p => `
1291                        <div style="background: #fff3cd; padding: 10px; border-radius: 6px; margin-bottom: 8px; font-size: 12px;">
1292                            <strong>${p.type}:</strong> ${p.description}
1293                        </div>
1294                    `).join('') : '<div style="color: #27ae60; font-size: 12px;">✅ Проблем не выявлено</div>'}
1295                </div>
1296
1297                <div>
1298                    <h3 style="font-size: 14px; color: #2c3e50; margin-bottom: 8px;">💡 Рекомендации</h3>
1299                    ${product.analysis.recommendations.map(r => `
1300                        <div style="background: #d4edda; padding: 10px; border-radius: 6px; margin-bottom: 8px; font-size: 12px;">
1301                            ${r}
1302                        </div>
1303                    `).join('')}
1304                </div>
1305
1306                <button id="filter-by-article" style="
1307                    width: 100%;
1308                    margin-top: 16px;
1309                    padding: 12px;
1310                    background: #667eea;
1311                    color: white;
1312                    border: none;
1313                    border-radius: 8px;
1314                    font-size: 14px;
1315                    font-weight: 600;
1316                    cursor: pointer;
1317                ">
1318                    🔍 Показать только этот товар в таблице
1319                </button>
1320            `;
1321
1322            modal.appendChild(modalContent);
1323            document.body.appendChild(modal);
1324
1325            // Закрытие модального окна
1326            modal.addEventListener('click', (e) => {
1327                if (e.target === modal) {
1328                    modal.remove();
1329                }
1330            });
1331
1332            modalContent.querySelector('#close-modal').addEventListener('click', () => {
1333                modal.remove();
1334            });
1335
1336            // Фильтрация по артикулу
1337            modalContent.querySelector('#filter-by-article').addEventListener('click', () => {
1338                this.filterByArticle(product.article);
1339                modal.remove();
1340            });
1341        }
1342
1343        filterByArticle(article) {
1344            console.log(`🔍 Фильтруем по артикулу: ${article}`);
1345            
1346            // Находим поле фильтра по артикулу на странице
1347            const articleInput = document.querySelector('input[placeholder*="артикул"], input[name*="article"]');
1348            
1349            if (articleInput) {
1350                articleInput.value = article;
1351                articleInput.dispatchEvent(new Event('input', { bubbles: true }));
1352                articleInput.dispatchEvent(new Event('change', { bubbles: true }));
1353                
1354                // Ищем кнопку "Применить" - ищем все кнопки и проверяем текст
1355                const buttons = document.querySelectorAll('button[type="submit"]');
1356                let applyButton = null;
1357                for (const btn of buttons) {
1358                    if (btn.textContent.includes('Применить')) {
1359                        applyButton = btn;
1360                        break;
1361                    }
1362                }
1363                
1364                if (applyButton) {
1365                    setTimeout(() => applyButton.click(), 300);
1366                }
1367            } else {
1368                console.warn('Не найдено поле для ввода артикула');
1369            }
1370        }
1371    }
1372
1373    // Инициализация
1374    async function init() {
1375        console.log('🎯 Инициализация AI Аналитика Продаж...');
1376
1377        // Проверяем, что мы на странице аналитики
1378        if (!window.location.href.includes('seller.ozon.ru/app/analytics')) {
1379            console.log('⚠️ Не на странице аналитики, ожидаем...');
1380            return;
1381        }
1382
1383        // Ждем загрузки таблицы
1384        const waitForTable = setInterval(() => {
1385            const table = document.querySelector('table.ct590-a');
1386            if (table) {
1387                clearInterval(waitForTable);
1388                console.log('✅ Таблица найдена, создаем UI');
1389                
1390                const ui = new AnalyticsUI();
1391                ui.createUI();
1392            }
1393        }, 1000);
1394    }
1395
1396    // Запуск при загрузке страницы
1397    if (document.readyState === 'loading') {
1398        document.addEventListener('DOMContentLoaded', init);
1399    } else {
1400        init();
1401    }
1402
1403    // Отслеживание изменений URL (для SPA)
1404    let lastUrl = location.href;
1405    new MutationObserver(() => {
1406        const url = location.href;
1407        if (url !== lastUrl) {
1408            lastUrl = url;
1409            init();
1410        }
1411    }).observe(document, { subtree: true, childList: true });
1412
1413})();
Ozon AI Analyzer 2.0 | Robomonkey