Ozon AI Analyzer 2.0

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

Size

89.3 KB

Version

1.1.29

Created

Dec 5, 2025

Updated

7 days ago

1// ==UserScript==
2// @name		Ozon AI Analyzer 2.0
3// @description		Мощный AI-аналитик для выявления проблем с продажами, анализа показателей и рекомендаций по улучшению
4// @version		1.1.29
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: 176.4, 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: 183.6, 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); // Округляем до целых
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 <= 14;
613            const isHighDRR = product.drr !== null && product.drr > 20;
614            const isLowDRR = product.drr !== null && product.drr <= 17;
615            const isGrowth = this.detectGrowth(product, avgMetrics);
616            const isLowImpressions = product.impressionsChange !== null && product.impressionsChange <= -20;
617            const isLowCR = (product.conversionCardToCartChange !== null && product.conversionCardToCartChange <= -20) || 
618                           (product.conversionCatalogToCardChange !== null && product.conversionCatalogToCardChange <= -20);
619            const isLowProfit = product.profit !== null && product.revenue !== null && product.revenue > 0 && 
620                               (product.profit / product.revenue) < 0.25;
621            
622            // Проверяем время доставки (парсим число из строки типа "35 ч")
623            const deliveryHours = product.deliveryTime ? parseInt(product.deliveryTime) : null;
624            const isBadDeliveryTime = deliveryHours !== null && deliveryHours >= 35;
625
626            // Генерируем рекомендации на основе проблем
627            const recommendations = [];
628            
629            if (isLowImpressions) {
630                // Проверяем ДРР - если снизился, рекомендуем увеличить бюджет
631                if (product.drrChange !== null && product.drrChange < 0) {
632                    recommendations.push('Показы упали. ДРР снизился - рекомендуем увеличить бюджет рекламы');
633                }
634                // Проверяем время доставки - если больше 3 дней, это снижает видимость
635                if (product.deliveryTime && parseInt(product.deliveryTime) > 3) {
636                    recommendations.push('Показы упали. Время доставки высокое - снижает видимость');
637                }
638                // Проверяем остатки - если маленькие, рекомендуем пополнить
639                if (isLowStock) {
640                    recommendations.push('Показы упали. Низкие остатки - рекомендуем пополнить склад');
641                }
642                // Если нет конкретных причин
643                if (recommendations.length === 0) {
644                    recommendations.push('Показы упали. Проверьте настройки рекламы и позиции товара');
645                }
646            }
647            
648            if (isLowCR) {
649                // Проверяем среднюю цену - если выросла, рекомендуем снизить
650                if (product.avgPriceChange !== null && product.avgPriceChange > 0) {
651                    recommendations.push('CR упал. Цена выросла - рекомендуем снизить цену для повышения конверсии');
652                }
653                // Проверяем время доставки
654                if (product.deliveryTime && parseInt(product.deliveryTime) > 3) {
655                    recommendations.push('CR упал. Время доставки высокое - снижает конверсию');
656                }
657                // Проверяем остатки
658                if (isLowStock) {
659                    recommendations.push('CR упал. Низкие остатки - снижает конверсию');
660                }
661                // Если нет конкретных причин
662                if (recommendations.length === 0) {
663                    recommendations.push('CR упал. Проверьте карточку товара, фото и описание');
664                }
665            }
666            
667            if (isLowProfit) {
668                recommendations.push('Низкая прибыль. Проверьте себестоимость, цену и рекламные расходы');
669            }
670            
671            if (isLowStock && !isLowImpressions && !isLowCR) {
672                recommendations.push('Низкие остатки - рекомендуем пополнить склад');
673            }
674            
675            if (isHighDRR && !isLowImpressions && !isLowCR) {
676                recommendations.push('Высокий ДРР - рекомендуем оптимизировать рекламные кампании');
677            }
678            
679            // Если нет проблем - выводим "Всё хорошо"
680            if (recommendations.length === 0) {
681                recommendations.push('Всё хорошо, рекомендаций нет');
682            }
683            
684            return {
685                priority: 'low',
686                problems: [],
687                recommendations,
688                daysOfStock,
689                isLowStock,
690                isHighDRR,
691                isLowDRR,
692                isGrowth,
693                isLowImpressions,
694                isLowCR,
695                isLowProfit,
696                isBadDeliveryTime
697            };
698        }
699
700        // Вычисление средних показателей
701        calculateAverageMetrics(products) {
702            const validProducts = products.filter(p => p.revenueChange !== null);
703            if (validProducts.length === 0) return { revenueChange: 0, ordersChange: 0, impressionsChange: 0 };
704            
705            const sum = validProducts.reduce((acc, p) => ({
706                revenueChange: acc.revenueChange + (p.revenueChange || 0),
707                ordersChange: acc.ordersChange + (p.ordersChange || 0),
708                impressionsChange: acc.impressionsChange + (p.impressionsChange || 0)
709            }), { revenueChange: 0, ordersChange: 0, impressionsChange: 0 });
710            
711            return {
712                revenueChange: sum.revenueChange / validProducts.length,
713                ordersChange: sum.ordersChange / validProducts.length,
714                impressionsChange: sum.impressionsChange / validProducts.length
715            };
716        }
717
718        async analyzeProduct(product, avgMetrics, useAI = true) {
719            try {
720                const daysOfStock = product.orders && product.stock ? Math.floor(product.stock / (product.orders / 7)) : null;
721                const isLowStock = daysOfStock !== null && daysOfStock < 49;
722                const isHighDRR = product.drr !== null && product.drr > 20;
723                const isLowDRR = product.drr !== null && product.drr <= 17;
724                const isGrowth = this.detectGrowth(product, avgMetrics);
725                const isLowImpressions = product.impressionsChange !== null && product.impressionsChange <= -20;
726                const isLowCR = (product.conversionCardToCartChange !== null && product.conversionCardToCartChange <= -20) || 
727                               (product.conversionCatalogToCardChange !== null && product.conversionCatalogToCardChange <= -20);
728                const isLowProfit = product.profit !== null && product.revenue !== null && product.revenue > 0 && 
729                                   (product.profit / product.revenue) < 0.25;
730                
731                // Проверяем время доставки
732                const deliveryHours = product.deliveryTime ? parseInt(product.deliveryTime) : null;
733                const isBadDeliveryTime = deliveryHours !== null && deliveryHours >= 35;
734                
735                if (!useAI) {
736                    return this.basicAnalysis(product, avgMetrics);
737                }
738                
739                // Формируем промпт для AI
740                const prompt = `Проанализируй товар и определи проблемы:
741
742Товар: ${product.name}
743Показатели:
744- Выручка: ${product.revenue || 'н/д'} ₽ (${product.revenueChange || 0}%)
745- Заказы: ${product.orders || 'н/д'} (${product.ordersChange || 0}%)
746- Показы: ${product.impressions || 'н/д'} (${product.impressionsChange || 0}%)
747- Посещения карточки: ${product.cardVisits || 'н/д'} (${product.cardVisitsChange || 0}%)
748- CTR: ${product.conversionCatalogToCard || 'н/д'}%
749- CRL: ${product.conversionCardToCart || 'н/д'}%
750- CR: ${product.cr || 'н/д'}%
751- ДРР: ${product.drr || 'н/д'}%
752- Остаток: ${product.stock || 'н/д'} шт (хватит на ${daysOfStock || 'н/д'} дней)
753
754Определи приоритет (critical/high/medium/low), проблемы и рекомендации.`;
755
756                const response = await RM.aiCall(prompt, {
757                    type: 'json_schema',
758                    json_schema: {
759                        name: 'product_analysis',
760                        schema: {
761                            type: 'object',
762                            properties: {
763                                priority: {
764                                    type: 'string',
765                                    enum: ['critical', 'high', 'medium', 'low']
766                                },
767                                problems: {
768                                    type: 'array',
769                                    items: {
770                                        type: 'object',
771                                        properties: {
772                                            type: { type: 'string' },
773                                            description: { type: 'string' }
774                                        },
775                                        required: ['type', 'description']
776                                    }
777                                },
778                                recommendations: {
779                                    type: 'array',
780                                    items: { type: 'string' }
781                                }
782                            },
783                            required: ['priority', 'problems', 'recommendations']
784                        }
785                    }
786                });
787
788                return {
789                    ...response,
790                    daysOfStock,
791                    isLowStock,
792                    isHighDRR,
793                    isLowDRR,
794                    isGrowth,
795                    isLowImpressions,
796                    isLowCR,
797                    isLowProfit,
798                    isBadDeliveryTime
799                };
800            } catch (error) {
801                console.error('Ошибка AI анализа:', error);
802                return this.basicAnalysis(product, avgMetrics);
803            }
804        }
805
806        // Определение роста на основе средних показателей
807        detectGrowth(product, avgMetrics) {
808            const threshold = 15; // Порог отклонения от среднего в %
809            
810            // Если выручка растет значительно выше среднего
811            if (product.revenueChange !== null && 
812                product.revenueChange > avgMetrics.revenueChange + threshold) {
813                return true;
814            }
815            
816            // Если заказы растут значительно выше среднего
817            if (product.ordersChange !== null && 
818                product.ordersChange > avgMetrics.ordersChange + threshold) {
819                return true;
820            }
821            
822            return false;
823        }
824    }
825
826    // Класс для UI
827    class AnalyticsUI {
828        constructor() {
829            this.container = null;
830            this.filteredProducts = [];
831            this.allProducts = [];
832            this.currentFilter = 'all';
833            this.isCollapsed = false;
834            this.isDragging = false;
835            this.isResizing = false;
836            this.dragStartX = 0;
837            this.dragStartY = 0;
838            this.containerStartX = 0;
839            this.containerStartY = 0;
840            this.resizeStartWidth = 0;
841            this.resizeStartHeight = 0;
842        }
843
844        createUI() {
845            console.log('🎨 Создаем UI...');
846
847            // Создаем контейнер для нашего UI
848            this.container = document.createElement('div');
849            this.container.id = 'ozon-ai-analytics';
850            this.container.style.cssText = `
851                position: fixed;
852                top: 80px;
853                right: 20px;
854                width: 500px;
855                max-height: 85vh;
856                background: white;
857                border-radius: 12px;
858                box-shadow: 0 4px 20px rgba(0,0,0,0.15);
859                z-index: 10000;
860                overflow: hidden;
861                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
862                resize: both;
863                min-width: 400px;
864                min-height: 200px;
865            `;
866
867            // Заголовок (с возможностью перетаскивания)
868            const header = document.createElement('div');
869            header.id = 'ozon-ai-header';
870            header.style.cssText = `
871                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
872                color: white;
873                padding: 18px 24px;
874                font-weight: 600;
875                font-size: 18px;
876                display: flex;
877                justify-content: space-between;
878                align-items: center;
879                cursor: move;
880                user-select: none;
881            `;
882            header.innerHTML = `
883                <span>🤖 AI Аналитик Продаж</span>
884                <div style="display: flex; gap: 8px; align-items: center;">
885                    <button id="ozon-ai-collapse" style="background: none; border: none; color: white; font-size: 20px; cursor: pointer; padding: 0; width: 28px; height: 28px;" title="Свернуть/Развернуть"></button>
886                    <button id="ozon-ai-close" style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; padding: 0; width: 28px; height: 28px;" title="Закрыть">×</button>
887                </div>
888            `;
889
890            // Кнопка запуска анализа
891            const startButton = document.createElement('button');
892            startButton.id = 'ozon-ai-start';
893            startButton.textContent = '🚀 Запустить анализ';
894            startButton.style.cssText = `
895                width: calc(100% - 40px);
896                margin: 20px;
897                padding: 16px;
898                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
899                color: white;
900                border: none;
901                border-radius: 8px;
902                font-size: 16px;
903                font-weight: 600;
904                cursor: pointer;
905                transition: transform 0.2s;
906            `;
907            startButton.onmouseover = () => startButton.style.transform = 'scale(1.02)';
908            startButton.onmouseout = () => startButton.style.transform = 'scale(1)';
909
910            // Контейнер для контента
911            const content = document.createElement('div');
912            content.id = 'ozon-ai-content';
913            content.style.cssText = `
914                padding: 20px;
915                max-height: calc(85vh - 140px);
916                overflow-y: auto;
917            `;
918
919            // Индикатор изменения размера
920            const resizeHandle = document.createElement('div');
921            resizeHandle.id = 'ozon-ai-resize';
922            resizeHandle.style.cssText = `
923                position: absolute;
924                bottom: 0;
925                right: 0;
926                width: 20px;
927                height: 20px;
928                cursor: nwse-resize;
929                background: linear-gradient(135deg, transparent 0%, transparent 50%, #667eea 50%, #667eea 100%);
930                border-bottom-right-radius: 12px;
931            `;
932
933            this.container.appendChild(header);
934            this.container.appendChild(startButton);
935            this.container.appendChild(content);
936            this.container.appendChild(resizeHandle);
937
938            document.body.appendChild(this.container);
939
940            // События для перетаскивания
941            header.addEventListener('mousedown', (e) => this.startDragging(e));
942            document.addEventListener('mousemove', (e) => this.drag(e));
943            document.addEventListener('mouseup', () => this.stopDragging());
944
945            // События для изменения размера
946            resizeHandle.addEventListener('mousedown', (e) => this.startResizing(e));
947
948            // События кнопок
949            document.getElementById('ozon-ai-close').addEventListener('click', () => {
950                this.container.style.display = 'none';
951            });
952
953            document.getElementById('ozon-ai-collapse').addEventListener('click', () => {
954                this.toggleCollapse();
955            });
956
957            document.getElementById('ozon-ai-start').addEventListener('click', () => {
958                this.startAnalysis();
959            });
960
961            console.log('✅ UI создан');
962        }
963
964        startDragging(e) {
965            if (e.target.closest('button')) return; // Не перетаскиваем при клике на кнопки
966            this.isDragging = true;
967            this.dragStartX = e.clientX;
968            this.dragStartY = e.clientY;
969            const rect = this.container.getBoundingClientRect();
970            this.containerStartX = rect.left;
971            this.containerStartY = rect.top;
972            this.container.style.transition = 'none';
973        }
974
975        drag(e) {
976            if (this.isDragging) {
977                const deltaX = e.clientX - this.dragStartX;
978                const deltaY = e.clientY - this.dragStartY;
979                this.container.style.left = `${this.containerStartX + deltaX}px`;
980                this.container.style.top = `${this.containerStartY + deltaY}px`;
981                this.container.style.right = 'auto';
982            } else if (this.isResizing) {
983                const deltaX = e.clientX - this.dragStartX;
984                const deltaY = e.clientY - this.dragStartY;
985                const newWidth = Math.max(400, this.resizeStartWidth + deltaX);
986                const newHeight = Math.max(200, this.resizeStartHeight + deltaY);
987                this.container.style.width = `${newWidth}px`;
988                this.container.style.maxHeight = `${newHeight}px`;
989            }
990        }
991
992        stopDragging() {
993            this.isDragging = false;
994            this.isResizing = false;
995            this.container.style.transition = '';
996        }
997
998        startResizing(e) {
999            e.stopPropagation();
1000            this.isResizing = true;
1001            this.dragStartX = e.clientX;
1002            this.dragStartY = e.clientY;
1003            this.resizeStartWidth = this.container.offsetWidth;
1004            this.resizeStartHeight = this.container.offsetHeight;
1005        }
1006
1007        toggleCollapse() {
1008            this.isCollapsed = !this.isCollapsed;
1009            const content = document.getElementById('ozon-ai-content');
1010            const startButton = document.getElementById('ozon-ai-start');
1011            const resizeHandle = document.getElementById('ozon-ai-resize');
1012            const collapseButton = document.getElementById('ozon-ai-collapse');
1013            
1014            if (this.isCollapsed) {
1015                content.style.display = 'none';
1016                startButton.style.display = 'none';
1017                resizeHandle.style.display = 'none';
1018                collapseButton.textContent = '+';
1019                this.container.style.maxHeight = 'auto';
1020            } else {
1021                content.style.display = 'block';
1022                startButton.style.display = 'block';
1023                resizeHandle.style.display = 'block';
1024                collapseButton.textContent = '−';
1025                this.container.style.maxHeight = '85vh';
1026            }
1027        }
1028
1029        async startAnalysis() {
1030            const content = document.getElementById('ozon-ai-content');
1031            const startButton = document.getElementById('ozon-ai-start');
1032            
1033            startButton.disabled = true;
1034            startButton.textContent = '⏳ Загрузка товаров...';
1035
1036            try {
1037                // Шаг 1: Загрузка всех товаров
1038                const collector = new ProductDataCollector();
1039                await collector.loadAllProducts();
1040
1041                startButton.textContent = '📊 Сбор данных...';
1042                
1043                // Шаг 2: Сбор данных
1044                const products = collector.collectProductData();
1045
1046                if (products.length === 0) {
1047                    content.innerHTML = '<p style="color: #e74c3c; padding: 20px; text-align: center; font-size: 14px;">❌ Не удалось найти товары. Убедитесь, что вы на странице аналитики.</p>';
1048                    startButton.disabled = false;
1049                    startButton.textContent = '🚀 Запустить анализ';
1050                    return;
1051                }
1052
1053                // Шаг 3: AI анализ с прогрессом
1054                const analyzer = new AIAnalyzer();
1055                
1056                const onProgress = (current, total, percentage, remaining) => {
1057                    const remainingText = remaining > 0 ? ` (~${remaining} сек)` : '';
1058                    startButton.textContent = `🤖 AI анализ: ${current}/${total} (${percentage}%)${remainingText}`;
1059                };
1060                
1061                const analyzedProducts = await analyzer.analyzeProducts(products, onProgress);
1062
1063                this.allProducts = analyzedProducts;
1064                this.filteredProducts = analyzedProducts;
1065
1066                // Вычисляем общую выручку и прибыль
1067                const totalRevenue = analyzedProducts.reduce((sum, p) => sum + (p.revenue || 0), 0);
1068                const totalProfit = analyzedProducts.reduce((sum, p) => sum + (p.profit || 0), 0);
1069                const totalOrders = analyzedProducts.reduce((sum, p) => sum + (p.orders || 0), 0);
1070                
1071                // Вычисляем средний ДРР (взвешенный по выручке)
1072                let totalDrrWeighted = 0;
1073                let totalRevenueForDrr = 0;
1074                analyzedProducts.forEach(p => {
1075                    if (p.drr !== null && p.revenue) {
1076                        totalDrrWeighted += p.drr * p.revenue;
1077                        totalRevenueForDrr += p.revenue;
1078                    }
1079                });
1080                const avgDrr = totalRevenueForDrr > 0 ? totalDrrWeighted / totalRevenueForDrr : 0;
1081
1082                // Шаг 4: Отображение результатов
1083                this.displayResults(analyzedProducts, {
1084                    totalRevenue,
1085                    totalProfit,
1086                    totalOrders,
1087                    avgDrr
1088                });
1089
1090                startButton.textContent = '✅ Анализ завершен';
1091                startButton.disabled = false;
1092
1093            } catch (error) {
1094                console.error('Ошибка анализа:', error);
1095                content.innerHTML = `<p style="color: #e74c3c; padding: 20px; text-align: center; font-size: 14px;">❌ Ошибка: ${error.message}</p>`;
1096                startButton.disabled = false;
1097                startButton.textContent = '🚀 Запустить анализ';
1098            }
1099        }
1100
1101        displayResults(products, totals) {
1102            const content = document.getElementById('ozon-ai-content');
1103            
1104            // Блок с общими показателями
1105            const totalSalesBlock = this.createTotalSalesBlock(totals);
1106            
1107            // Фильтры
1108            const filters = this.createFilters(products);
1109            
1110            // Список товаров
1111            const productsList = this.createProductsList(products);
1112            content.innerHTML = '';
1113            content.appendChild(totalSalesBlock);
1114            content.appendChild(filters);
1115            content.appendChild(productsList);
1116        }
1117
1118        createTotalSalesBlock(totals) {
1119            const block = document.createElement('div');
1120            block.id = 'ozon-ai-total-sales';
1121            block.style.cssText = `
1122                margin-bottom: 20px;
1123                padding: 16px;
1124                background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
1125                border-radius: 8px;
1126            `;
1127            
1128            const profitColor = totals.totalProfit >= 0 ? '#27ae60' : '#e74c3c';
1129            const profitPercent = totals.totalRevenue > 0 ? ((totals.totalProfit / totals.totalRevenue) * 100).toFixed(1) : 0;
1130            const profitPercentColor = profitPercent >= 25 ? '#27ae60' : '#e74c3c';
1131            
1132            block.innerHTML = `
1133                <div style="font-size: 14px; font-weight: 600; color: #2c3e50; margin-bottom: 12px;">📊 Общие показатели</div>
1134                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 13px;">
1135                    <div style="background: white; padding: 10px; border-radius: 6px;">
1136                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Общая выручка</div>
1137                        <div style="font-weight: 600; color: #2c3e50;">${totals.totalRevenue.toLocaleString()}</div>
1138                    </div>
1139                    <div style="background: white; padding: 10px; border-radius: 6px;">
1140                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Общая прибыль</div>
1141                        <div style="font-weight: 600; color: ${profitColor};">${totals.totalProfit.toLocaleString()}</div>
1142                    </div>
1143                    <div style="background: white; padding: 10px; border-radius: 6px;">
1144                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Прибыль %</div>
1145                        <div style="font-weight: 600; color: ${profitPercentColor};">${profitPercent}%</div>
1146                    </div>
1147                    <div style="background: white; padding: 10px; border-radius: 6px;">
1148                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Всего заказов</div>
1149                        <div style="font-weight: 600; color: #2c3e50;">${totals.totalOrders.toLocaleString()}</div>
1150                    </div>
1151                    <div style="background: white; padding: 10px; border-radius: 6px;">
1152                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Средний ДРР</div>
1153                        <div style="font-weight: 600; color: #2c3e50;">${totals.avgDrr.toFixed(1)}%</div>
1154                    </div>
1155                </div>
1156            `;
1157            
1158            return block;
1159        }
1160
1161        // Фильтры
1162        createFilters(products) {
1163            const filtersContainer = document.createElement('div');
1164            filtersContainer.style.cssText = `
1165                margin-bottom: 20px;
1166            `;
1167
1168            // Поле поиска
1169            const searchContainer = document.createElement('div');
1170            searchContainer.style.cssText = `
1171                margin-bottom: 12px;
1172            `;
1173            
1174            const searchInput = document.createElement('input');
1175            searchInput.type = 'text';
1176            searchInput.placeholder = '🔍 Поиск по названию или артикулу...';
1177            searchInput.id = 'ozon-ai-search';
1178            searchInput.style.cssText = `
1179                width: 100%;
1180                padding: 10px 12px;
1181                border: 2px solid #ecf0f1;
1182                border-radius: 6px;
1183                font-size: 14px;
1184                font-family: inherit;
1185                outline: none;
1186                transition: border-color 0.2s;
1187            `;
1188            
1189            searchInput.addEventListener('focus', () => {
1190                searchInput.style.borderColor = '#667eea';
1191            });
1192            
1193            searchInput.addEventListener('blur', () => {
1194                searchInput.style.borderColor = '#ecf0f1';
1195            });
1196            
1197            searchInput.addEventListener('input', (e) => {
1198                this.applySearch(e.target.value);
1199            });
1200            
1201            searchContainer.appendChild(searchInput);
1202            filtersContainer.appendChild(searchContainer);
1203
1204            // Кнопки фильтров
1205            const buttonsContainer = document.createElement('div');
1206            buttonsContainer.style.cssText = `
1207                display: flex;
1208                flex-wrap: wrap;
1209                gap: 8px;
1210            `;
1211
1212            // Подсчет товаров по категориям
1213            const critical = products.filter(p => p.analysis.priority === 'critical').length;
1214            const high = products.filter(p => p.analysis.priority === 'high').length;
1215            const lowStock = products.filter(p => p.analysis.isLowStock).length;
1216            const highDRR = products.filter(p => p.analysis.isHighDRR).length;
1217            const lowDRR = products.filter(p => p.analysis.isLowDRR).length;
1218            const growth = products.filter(p => p.analysis.isGrowth).length;
1219            const lowImpressions = products.filter(p => p.analysis.isLowImpressions).length;
1220            const lowCR = products.filter(p => p.analysis.isLowCR).length;
1221            const lowProfit = products.filter(p => p.analysis.isLowProfit).length;
1222            const badDeliveryTime = products.filter(p => p.analysis.isBadDeliveryTime).length;
1223
1224            const filterButtons = [
1225                { id: 'all', label: `Все (${products.length})`, color: '#95a5a6' },
1226                { id: 'critical', label: `🔴 Критичные (${critical})`, color: '#e74c3c' },
1227                { id: 'high', label: `🟠 Высокий (${high})`, color: '#f39c12' },
1228                { id: 'lowStock', label: `📦 Низкие остатки (${lowStock})`, color: '#e67e22' },
1229                { id: 'highDRR', label: `💰 Высокий ДРР (${highDRR})`, color: '#c0392b' },
1230                { id: 'lowDRR', label: `📊 Повысить ДРР (${lowDRR})`, color: '#16a085' },
1231                { id: 'lowImpressions', label: `📉 Упали показы (${lowImpressions})`, color: '#9b59b6' },
1232                { id: 'lowCR', label: `📊 Упал CR (${lowCR})`, color: '#e91e63' },
1233                { id: 'lowProfit', label: `💸 Низкая прибыль (${lowProfit})`, color: '#d32f2f' },
1234                { id: 'badDeliveryTime', label: `⏱️ Плохое время (${badDeliveryTime})`, color: '#8e44ad' },
1235                { id: 'growth', label: `📈 Рост (${growth})`, color: '#27ae60' }
1236            ];
1237
1238            filterButtons.forEach(filter => {
1239                const btn = document.createElement('button');
1240                btn.textContent = filter.label;
1241                btn.style.cssText = `
1242                    padding: 8px 12px;
1243                    background: ${this.currentFilter === filter.id ? filter.color : '#ecf0f1'};
1244                    color: ${this.currentFilter === filter.id ? 'white' : '#2c3e50'};
1245                    border: none;
1246                    border-radius: 6px;
1247                    font-size: 13px;
1248                    font-weight: 500;
1249                    cursor: pointer;
1250                    transition: all 0.2s;
1251                `;
1252                
1253                btn.addEventListener('click', () => {
1254                    this.currentFilter = filter.id;
1255                    this.applyFilter(filter.id);
1256                });
1257
1258                buttonsContainer.appendChild(btn);
1259            });
1260
1261            filtersContainer.appendChild(buttonsContainer);
1262            return filtersContainer;
1263        }
1264
1265        applySearch(searchTerm) {
1266            const term = searchTerm.toLowerCase().trim();
1267            
1268            console.log(`🔍 Поиск по запросу: "${term}"`);
1269            
1270            if (!term) {
1271                // Если поиск пустой, применяем текущий фильтр
1272                this.applyFilter(this.currentFilter);
1273                return;
1274            }
1275            
1276            // Фильтруем по поисковому запросу
1277            const filtered = this.allProducts.filter(p => {
1278                const nameMatch = p.name.toLowerCase().includes(term);
1279                const articleMatch = p.article.includes(term);
1280                return nameMatch || articleMatch;
1281            });
1282            
1283            console.log(`✅ Найдено товаров: ${filtered.length}`);
1284            
1285            // Сортируем по выручке
1286            filtered.sort((a, b) => {
1287                const revenueA = a.revenue || 0;
1288                const revenueB = b.revenue || 0;
1289                return revenueB - revenueA;
1290            });
1291            
1292            this.filteredProducts = filtered;
1293            
1294            // Обновляем только список товаров, не трогая фильтры
1295            const content = document.getElementById('ozon-ai-content');
1296            const productsList = this.createProductsList(filtered);
1297            
1298            // Находим и удаляем только список товаров (второй div в content)
1299            const children = content.children;
1300            if (children.length > 1) {
1301                children[1].remove();
1302            }
1303            
1304            content.appendChild(productsList);
1305        }
1306
1307        applyFilter(filterId) {
1308            let filtered = this.allProducts;
1309            
1310            console.log(`🔍 Применяем фильтр: ${filterId}`);
1311            
1312            // Очищаем поле поиска при смене фильтра
1313            const searchInput = document.getElementById('ozon-ai-search');
1314            if (searchInput) {
1315                searchInput.value = '';
1316            }
1317
1318            switch(filterId) {
1319            case 'critical':
1320                filtered = this.allProducts.filter(p => p.analysis.priority === 'critical');
1321                break;
1322            case 'high':
1323                filtered = this.allProducts.filter(p => p.analysis.priority === 'high');
1324                break;
1325            case 'lowStock':
1326                filtered = this.allProducts.filter(p => p.analysis.isLowStock);
1327                break;
1328            case 'highDRR':
1329                filtered = this.allProducts.filter(p => p.analysis.isHighDRR);
1330                break;
1331            case 'lowDRR':
1332                filtered = this.allProducts.filter(p => p.analysis.isLowDRR);
1333                break;
1334            case 'lowImpressions':
1335                filtered = this.allProducts.filter(p => p.analysis.isLowImpressions);
1336                console.log('📊 Товары с упавшими показами:', filtered.map(p => `${p.article} (${p.impressionsChange}%)`));
1337                break;
1338            case 'lowCR':
1339                filtered = this.allProducts.filter(p => p.analysis.isLowCR);
1340                break;
1341            case 'lowProfit':
1342                filtered = this.allProducts.filter(p => p.analysis.isLowProfit);
1343                break;
1344            case 'badDeliveryTime':
1345                filtered = this.allProducts.filter(p => p.analysis.isBadDeliveryTime);
1346                break;
1347            case 'growth':
1348                filtered = this.allProducts.filter(p => p.analysis.isGrowth);
1349                break;
1350            }
1351
1352            console.log(`✅ Найдено товаров: ${filtered.length}`);
1353
1354            // Сортируем по выручке (от большей к меньшей)
1355            filtered.sort((a, b) => {
1356                const revenueA = a.revenue || 0;
1357                const revenueB = b.revenue || 0;
1358                return revenueB - revenueA;
1359            });
1360
1361            this.filteredProducts = filtered;
1362            
1363            // Обновляем только список товаров и кнопки фильтров
1364            const content = document.getElementById('ozon-ai-content');
1365            const productsList = this.createProductsList(filtered);
1366            
1367            // Находим и удаляем только список товаров (третий элемент в content)
1368            const children = content.children;
1369            if (children.length > 2) {
1370                children[2].remove();
1371            }
1372            
1373            content.appendChild(productsList);
1374            
1375            // Обновляем стили кнопок фильтров
1376            this.updateFilterButtons(filterId);
1377        }
1378
1379        updateFilterButtons(activeFilterId) {
1380            const content = document.getElementById('ozon-ai-content');
1381            const filtersContainer = content.children[1]; // Второй элемент - контейнер с фильтрами
1382            const buttonsContainer = filtersContainer.children[1]; // Второй элемент в фильтрах - контейнер с кнопками
1383            
1384            const buttons = buttonsContainer.querySelectorAll('button');
1385            
1386            const filterButtons = [
1387                { id: 'all', color: '#95a5a6' },
1388                { id: 'critical', color: '#e74c3c' },
1389                { id: 'high', color: '#f39c12' },
1390                { id: 'lowStock', color: '#e67e22' },
1391                { id: 'highDRR', color: '#c0392b' },
1392                { id: 'lowDRR', color: '#16a085' },
1393                { id: 'lowImpressions', color: '#9b59b6' },
1394                { id: 'lowCR', color: '#e91e63' },
1395                { id: 'lowProfit', color: '#d32f2f' },
1396                { id: 'badDeliveryTime', color: '#8e44ad' },
1397                { id: 'growth', color: '#27ae60' }
1398            ];
1399            
1400            buttons.forEach((btn, index) => {
1401                const filter = filterButtons[index];
1402                if (filter && filter.id === activeFilterId) {
1403                    btn.style.background = filter.color;
1404                    btn.style.color = 'white';
1405                } else {
1406                    btn.style.background = '#ecf0f1';
1407                    btn.style.color = '#2c3e50';
1408                }
1409            });
1410        }
1411
1412        createProductsList(products) {
1413            const list = document.createElement('div');
1414            list.style.cssText = `
1415                display: flex;
1416                flex-direction: column;
1417                gap: 12px;
1418            `;
1419
1420            products.forEach(product => {
1421                const card = this.createProductCard(product);
1422                list.appendChild(card);
1423            });
1424
1425            return list;
1426        }
1427
1428        formatMetric(value, change, isPercent = false) {
1429            // Округляем проценты до десятых
1430            let displayValue = value;
1431            if (isPercent && value !== null && value !== undefined) {
1432                displayValue = parseFloat(value.toFixed(1));
1433            }
1434            
1435            const valueStr = displayValue !== null && displayValue !== undefined ? 
1436                (isPercent ? `${displayValue}%` : displayValue.toLocaleString()) : '—';
1437            
1438            if (change === null || change === undefined) return valueStr;
1439            
1440            // Округляем изменение до десятых
1441            const roundedChange = parseFloat(change.toFixed(1));
1442            const changeStr = roundedChange > 0 ? `+${roundedChange}%` : `${roundedChange}%`;
1443            const color = roundedChange > 0 ? '#27ae60' : '#e74c3c';
1444            
1445            return `${valueStr} <span style="color: ${color}; font-size: 11px;">(${changeStr})</span>`;
1446        }
1447
1448        createProductCard(product) {
1449            const card = document.createElement('div');
1450            
1451            const priorityColors = {
1452                critical: '#e74c3c',
1453                high: '#f39c12',
1454                medium: '#3498db',
1455                low: '#95a5a6'
1456            };
1457
1458            const priorityLabels = {
1459                critical: '🔴 Критичный',
1460                high: '🟠 Высокий',
1461                medium: '🟡 Средний',
1462                low: '🟢 Низкий'
1463            };
1464
1465            // Определяем цвет прибыли
1466            const profitColor = product.profitPercent !== null && product.profitPercent >= 25 ? '#27ae60' : '#e74c3c';
1467
1468            card.style.cssText = `
1469                background: white;
1470                border: 2px solid ${priorityColors[product.analysis.priority]};
1471                border-radius: 8px;
1472                padding: 14px;
1473                cursor: pointer;
1474                transition: all 0.2s;
1475            `;
1476
1477            card.innerHTML = `
1478                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
1479                    <div style="flex: 1;">
1480                        <div style="font-weight: 600; font-size: 14px; color: #2c3e50; margin-bottom: 4px;">${product.name}</div>
1481                        <div class="article-copy" style="font-size: 12px; color: #7f8c8d; cursor: pointer; user-select: none;" title="Нажмите, чтобы скопировать артикул">Арт. ${product.article}</div>
1482                    </div>
1483                    <div style="background: ${priorityColors[product.analysis.priority]}; color: white; padding: 5px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; white-space: nowrap;">
1484                        ${priorityLabels[product.analysis.priority]}
1485                    </div>
1486                </div>
1487
1488                ${product.analysis.problems.length > 0 ? `
1489                <div style="background: #fff3cd; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 500; color: #856404;">
1490                    ${product.analysis.problems.slice(0, 2).map(p => `
1491                        <div style="margin-bottom: 4px;">⚠️ ${p.description}</div>
1492                    `).join('')}
1493                </div>
1494                ` : ''}
1495
1496                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 12px;">
1497                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
1498                        <div><strong>Выручка:</strong> ${this.formatMetric(product.revenue, product.revenueChange)}</div>
1499                        <div><strong>Прибыль:</strong> <span style="color: ${profitColor}; font-weight: 600;">${product.profit !== null ? `${product.profit.toLocaleString()}` : '—'} ${product.profitPercent !== null ? `(${product.profitPercent}%)` : ''}</span></div>
1500                        <div><strong>Показы:</strong> ${this.formatMetric(product.impressions, product.impressionsChange)}</div>
1501                        <div><strong>Карточка:</strong> ${this.formatMetric(product.cardVisits, product.cardVisitsChange)}</div>
1502                        <div><strong>Заказы:</strong> ${this.formatMetric(product.orders, product.ordersChange)}</div>
1503                        <div><strong>Корзины:</strong> ${this.formatMetric(product.cartAdditions, product.cartAdditionsChange)}</div>
1504                        <div><strong>CTR:</strong> ${this.formatMetric(product.conversionCatalogToCard, product.conversionCatalogToCardChange, true)}</div>
1505                        <div><strong>CRL:</strong> ${this.formatMetric(product.conversionCardToCart, product.conversionCardToCartChange, true)}</div>
1506                        <div><strong>CR:</strong> ${this.formatMetric(product.cr, product.crChange, true)}</div>
1507                        <div><strong>ДРР:</strong> ${this.formatMetric(product.drr, product.drrChange, true)}</div>
1508                        <div><strong>Цена:</strong> ${this.formatMetric(product.avgPrice, product.avgPriceChange)}</div>
1509                        <div><strong>Доставка:</strong> ${product.deliveryTime || '—'}</div>
1510                        <div><strong>Остаток:</strong> ${product.stock || '—'} шт</div>
1511                        <div><strong>На дней:</strong> ${product.daysOfStock || '—'}</div>
1512                    </div>
1513                </div>
1514
1515                <div style="font-size: 11px; color: #7f8c8d; border-top: 1px solid #ecf0f1; padding-top: 8px;">
1516                    <strong>Рекомендации:</strong>
1517                    ${product.analysis.recommendations.slice(0, 1).map(r => `<div>• ${r}</div>`).join('')}
1518                </div>
1519            `;
1520
1521            // Обработчик копирования артикула
1522            const articleElement = card.querySelector('.article-copy');
1523            articleElement.addEventListener('click', async (e) => {
1524                e.stopPropagation();
1525                try {
1526                    await GM.setClipboard(product.article);
1527                    const originalText = articleElement.textContent;
1528                    articleElement.textContent = '✓ Скопировано!';
1529                    articleElement.style.color = '#27ae60';
1530                    setTimeout(() => {
1531                        articleElement.textContent = originalText;
1532                        articleElement.style.color = '#7f8c8d';
1533                    }, 1500);
1534                } catch (error) {
1535                    console.error('Ошибка копирования:', error);
1536                }
1537            });
1538
1539            card.addEventListener('click', () => {
1540                this.showProductDetails(product);
1541            });
1542
1543            return card;
1544        }
1545
1546        showProductDetails(product) {
1547            // Определяем цвет прибыли
1548            const profitColor = product.profitPercent !== null && product.profitPercent >= 25 ? '#27ae60' : '#e74c3c';
1549
1550            // Создаем модальное окно с детальной информацией
1551            const modal = document.createElement('div');
1552            modal.style.cssText = `
1553                position: fixed;
1554                top: 0;
1555                left: 0;
1556                right: 0;
1557                bottom: 0;
1558                background: rgba(0,0,0,0.7);
1559                z-index: 10001;
1560                display: flex;
1561                align-items: center;
1562                justify-content: center;
1563                padding: 20px;
1564            `;
1565
1566            const modalContent = document.createElement('div');
1567            modalContent.style.cssText = `
1568                background: white;
1569                border-radius: 12px;
1570                padding: 24px;
1571                max-width: 700px;
1572                max-height: 80vh;
1573                overflow-y: auto;
1574                width: 100%;
1575            `;
1576
1577            modalContent.innerHTML = `
1578                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
1579                    <h2 style="margin: 0; font-size: 18px; color: #2c3e50;">${product.name}</h2>
1580                    <button id="close-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #95a5a6;">×</button>
1581                </div>
1582
1583                <div style="background: #f8f9fa; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
1584                    <div style="font-size: 12px; color: #7f8c8d; margin-bottom: 12px;">Артикул: ${product.article}</div>
1585                    
1586                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; font-size: 13px;">
1587                        <div>
1588                            <div style="color: #7f8c8d; font-size: 11px;">Выручка</div>
1589                            <div style="font-weight: 600;">${this.formatMetric(product.revenue, product.revenueChange)}</div>
1590                        </div>
1591                        
1592                        <div>
1593                            <div style="color: #7f8c8d; font-size: 11px;">Прибыль</div>
1594                            <div style="font-weight: 600; color: ${profitColor};">${product.profit !== null ? `${product.profit.toLocaleString()}` : '—'}</div>
1595                        </div>
1596                        
1597                        <div>
1598                            <div style="color: #7f8c8d; font-size: 11px;">Прибыль %</div>
1599                            <div style="font-weight: 600; color: ${profitColor};">${product.profitPercent !== null ? `${product.profitPercent}%` : '—'}</div>
1600                        </div>
1601                        
1602                        <div>
1603                            <div style="color: #7f8c8d; font-size: 11px;">Заказы</div>
1604                            <div style="font-weight: 600;">${this.formatMetric(product.orders, product.ordersChange)}</div>
1605                        </div>
1606
1607                        <div>
1608                            <div style="color: #7f8c8d; font-size: 11px;">Показы всего</div>
1609                            <div style="font-weight: 600;">${this.formatMetric(product.impressions, product.impressionsChange)}</div>
1610                        </div>
1611
1612                        <div>
1613                            <div style="color: #7f8c8d; font-size: 11px;">Посещения карточки</div>
1614                            <div style="font-weight: 600;">${this.formatMetric(product.cardVisits, product.cardVisitsChange)}</div>
1615                        </div>
1616
1617                        <div>
1618                            <div style="color: #7f8c8d; font-size: 11px;">Добавления в корзину</div>
1619                            <div style="font-weight: 600;">${this.formatMetric(product.cartAdditions, product.cartAdditionsChange)}</div>
1620                        </div>
1621
1622                        <div>
1623                            <div style="color: #7f8c8d; font-size: 11px;">CTR (каталог→карточка)</div>
1624                            <div style="font-weight: 600;">${this.formatMetric(product.conversionCatalogToCard, product.conversionCatalogToCardChange, true)}</div>
1625                        </div>
1626
1627                        <div>
1628                            <div style="color: #7f8c8d; font-size: 11px;">CRL (карточка→корзина)</div>
1629                            <div style="font-weight: 600;">${this.formatMetric(product.conversionCardToCart, product.conversionCardToCartChange, true)}</div>
1630                        </div>
1631
1632                        <div>
1633                            <div style="color: #7f8c8d; font-size: 11px;">CR (заказы/карточка)</div>
1634                            <div style="font-weight: 600;">${this.formatMetric(product.cr, product.crChange, true)}</div>
1635                        </div>
1636
1637                        <div>
1638                            <div style="color: #7f8c8d; font-size: 11px;">ДРР</div>
1639                            <div style="font-weight: 600;">${this.formatMetric(product.drr, product.drrChange, true)}</div>
1640                        </div>
1641
1642                        <div>
1643                            <div style="color: #7f8c8d; font-size: 11px;">Время доставки</div>
1644                            <div style="font-weight: 600;">${product.deliveryTime || '—'}</div>
1645                        </div>
1646
1647                        <div>
1648                            <div style="color: #7f8c8d; font-size: 11px;">Остаток на конец периода</div>
1649                            <div style="font-weight: 600;">${product.stock || '—'} шт</div>
1650                        </div>
1651
1652                        <div>
1653                            <div style="color: #7f8c8d; font-size: 11px;">Хватит на дней</div>
1654                            <div style="font-weight: 600;">${product.daysOfStock || '—'}</div>
1655                        </div>
1656                    </div>
1657                </div>
1658
1659                <div style="margin-bottom: 16px;">
1660                    <h3 style="font-size: 14px; color: #2c3e50; margin-bottom: 8px;">🔍 Выявленные проблемы</h3>
1661                    ${product.analysis.problems.length > 0 ? product.analysis.problems.map(p => `
1662                        <div style="background: #fff3cd; padding: 10px; border-radius: 6px; margin-bottom: 8px; font-size: 12px;">
1663                            <strong>${p.type}:</strong> ${p.description}
1664                        </div>
1665                    `).join('') : '<div style="color: #27ae60; font-size: 12px;">✅ Проблем не выявлено</div>'}
1666                </div>
1667
1668                <div>
1669                    <h3 style="font-size: 14px; color: #2c3e50; margin-bottom: 8px;">💡 Рекомендации</h3>
1670                    ${product.analysis.recommendations.map(r => `
1671                        <div style="background: #d4edda; padding: 10px; border-radius: 6px; margin-bottom: 8px; font-size: 12px;">
1672                            ${r}
1673                        </div>
1674                    `).join('')}
1675                </div>
1676
1677                <button id="filter-by-article" style="
1678                    width: 100%;
1679                    margin-top: 16px;
1680                    padding: 12px;
1681                    background: #667eea;
1682                    color: white;
1683                    border: none;
1684                    border-radius: 8px;
1685                    font-size: 14px;
1686                    font-weight: 600;
1687                    cursor: pointer;
1688                ">
1689                    🔍 Показать только этот товар в таблице
1690                </button>
1691            `;
1692
1693            modal.appendChild(modalContent);
1694            document.body.appendChild(modal);
1695
1696            // Закрытие модального окна
1697            modal.addEventListener('click', (e) => {
1698                if (e.target === modal) {
1699                    modal.remove();
1700                }
1701            });
1702
1703            modalContent.querySelector('#close-modal').addEventListener('click', () => {
1704                modal.remove();
1705            });
1706
1707            // Фильтрация по артикулу
1708            modalContent.querySelector('#filter-by-article').addEventListener('click', () => {
1709                this.filterByArticle(product.article);
1710                modal.remove();
1711            });
1712        }
1713
1714        filterByArticle(article) {
1715            console.log(`🔍 Фильтруем по артикулу: ${article}`);
1716            
1717            // Находим поле фильтра по артикулу на странице
1718            const articleInput = document.querySelector('input[placeholder*="артикул"], input[name*="article"]');
1719            
1720            if (articleInput) {
1721                articleInput.value = article;
1722                articleInput.dispatchEvent(new Event('input', { bubbles: true }));
1723                articleInput.dispatchEvent(new Event('change', { bubbles: true }));
1724                
1725                // Ищем кнопку "Применить" - ищем все кнопки и проверяем текст
1726                const buttons = document.querySelectorAll('button[type="submit"]');
1727                let applyButton = null;
1728                for (const btn of buttons) {
1729                    if (btn.textContent.includes('Применить')) {
1730                        applyButton = btn;
1731                        break;
1732                    }
1733                }
1734                
1735                if (applyButton) {
1736                    setTimeout(() => applyButton.click(), 300);
1737                }
1738            } else {
1739                console.warn('Не найдено поле для ввода артикула');
1740            }
1741        }
1742    }
1743
1744    // Инициализация
1745    async function init() {
1746        console.log('🎯 Инициализация AI Аналитика Продаж...');
1747
1748        // Проверяем, что мы на странице аналитики
1749        if (!window.location.href.includes('seller.ozon.ru/app/analytics')) {
1750            console.log('⚠️ Не на странице аналитики, ожидаем...');
1751            return;
1752        }
1753
1754        // Ждем загрузки таблицы
1755        const waitForTable = setInterval(() => {
1756            const table = document.querySelector('table.ct590-a');
1757            if (table) {
1758                clearInterval(waitForTable);
1759                console.log('✅ Таблица найдена, создаем UI');
1760                
1761                const ui = new AnalyticsUI();
1762                ui.createUI();
1763            }
1764        }, 1000);
1765    }
1766
1767    // Запуск при загрузке страницы
1768    if (document.readyState === 'loading') {
1769        document.addEventListener('DOMContentLoaded', init);
1770    } else {
1771        init();
1772    }
1773
1774    // Отслеживание изменений URL (для SPA)
1775    let lastUrl = location.href;
1776    new MutationObserver(() => {
1777        const url = location.href;
1778        if (url !== lastUrl) {
1779            lastUrl = url;
1780            init();
1781        }
1782    }).observe(document, { subtree: true, childList: true });
1783
1784})();
Ozon AI Analyzer 2.0 | Robomonkey