Ozon AI Analyzer 2.0

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

Size

104.8 KB

Version

1.1.45

Created

Dec 6, 2025

Updated

7 days ago

1// ==UserScript==
2// @name		Ozon AI Analyzer 2.0
3// @description		Мощный AI-аналитик для выявления проблем с продажами, анализа показателей и рекомендаций по улучшению
4// @version		1.1.45
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
13    'use strict';
14
15
16    console.log('🚀 Ozon AI Аналитик Продаж запущен');
17
18
19    // Утилита для задержки
20
21    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
22
23
24    // Парсинг числовых значений
25
26    function parseNumber(str) {
27
28        if (!str || str === '—' || str === '') return null;
29
30        // Извлекаем первое число ДО знака процента
31
32        const match = str.match(/^([\d\s,.]+)/);
33
34        if (!match) return null;
35
36        // Убираем пробелы (разделители тысяч), потом парсим
37
38        const cleaned = match[1].replace(/\s/g, '').replace(',', '.');
39
40        const num = parseFloat(cleaned);
41
42        return isNaN(num) ? null : num;
43
44    }
45
46
47    // Парсинг процентов
48
49    function parsePercent(str) {
50
51        if (!str || str === '—' || str === '') return null;
52
53        const match = str.match(/([+-]?\d+(?:\.\d+)?)\s*%/);
54
55        return match ? parseFloat(match[1]) : null;
56
57    }
58
59
60    // Парсинг цены (убираем пробелы между цифрами)
61
62    function parsePrice(str) {
63
64        if (!str || str === '—' || str === '') return null;
65
66        // Извлекаем первое число ДО знака процента
67
68        const match = str.match(/^([\d\s,.]+)/);
69
70        if (!match) return null;
71
72        // Убираем пробелы, затем все кроме цифр и точек
73
74        const cleaned = match[1].replace(/\s/g, '').replace(',', '.');
75
76        const num = parseFloat(cleaned);
77
78        return isNaN(num) ? null : num;
79
80    }
81
82
83    // Таблица с данными для расчета прибыли
84
85    const PRODUCT_COST_DATA = {
86
87        '72252': { cost: 158.4, commission: 0.27, delivery: 90 },
88
89        '71613': { cost: 108, commission: 0.27, delivery: 90 },
90
91        '73716': { cost: 126, commission: 0.27, delivery: 90 },
92
93        '80036': { cost: 103.2, commission: 0.27, delivery: 90 },
94
95        '73365': { cost: 166.8, commission: 0.27, delivery: 90 },
96
97        '74881': { cost: 135.6, commission: 0.27, delivery: 90 },
98
99        '73266': { cost: 708, commission: 0.27, delivery: 90 },
100
101        '73655': { cost: 219.6, commission: 0.27, delivery: 90 },
102
103        '75222': { cost: 103.2, commission: 0.27, delivery: 90 },
104
105        '73358': { cost: 163.2, commission: 0.27, delivery: 90 },
106
107        '73723': { cost: 116.4, commission: 0.27, delivery: 90 },
108
109        '74119': { cost: 110.4, commission: 0.27, delivery: 90 },
110
111        '72573': { cost: 146.4, commission: 0.27, delivery: 90 },
112
113        '72221': { cost: 90, commission: 0.27, delivery: 90 },
114
115        '75345': { cost: 111.6, commission: 0.27, delivery: 90 },
116
117        '73334': { cost: 104.4, commission: 0.27, delivery: 90 },
118
119        '72184': { cost: 177.6, commission: 0.27, delivery: 90 },
120
121        '75291': { cost: 100.8, commission: 0.27, delivery: 90 },
122
123        '73617': { cost: 163.2, commission: 0.27, delivery: 90 },
124
125        '73976': { cost: 170.4, commission: 0.27, delivery: 90 },
126
127        '75710': { cost: 172.8, commission: 0.27, delivery: 90 },
128
129        '76113': { cost: 115.2, commission: 0.27, delivery: 90 },
130
131        '75499': { cost: 96, commission: 0.27, delivery: 90 },
132
133        '71569': { cost: 117.6, commission: 0.27, delivery: 90 },
134
135        '72276': { cost: 286.8, commission: 0.27, delivery: 90 },
136
137        '75338': { cost: 90, commission: 0.27, delivery: 90 },
138
139        '75536': { cost: 87.6, commission: 0.27, delivery: 90 },
140
141        '76014': { cost: 135.6, commission: 0.27, delivery: 90 },
142
143        '73730': { cost: 121.2, commission: 0.27, delivery: 90 },
144
145        '75628': { cost: 124.8, commission: 0.27, delivery: 90 },
146
147        '74249': { cost: 312, commission: 0.27, delivery: 90 },
148
149        '75468': { cost: 138, commission: 0.27, delivery: 90 },
150
151        '73495': { cost: 120, commission: 0.27, delivery: 90 },
152
153        '74393': { cost: 126, commission: 0.27, delivery: 90 },
154
155        '74188': { cost: 174, commission: 0.27, delivery: 90 },
156
157        '73907': { cost: 155.376, commission: 0.27, delivery: 90 },
158
159        '73396': { cost: 112.8, commission: 0.27, delivery: 90 },
160
161        '71668': { cost: 96, commission: 0.27, delivery: 90 },
162
163        '73235': { cost: 114, commission: 0.27, delivery: 90 },
164
165        '75093': { cost: 96, commission: 0.27, delivery: 90 },
166
167        '73891': { cost: 100.8, commission: 0.27, delivery: 90 },
168
169        '75505': { cost: 91.2, commission: 0.27, delivery: 90 },
170
171        '71590': { cost: 100.8, commission: 0.27, delivery: 90 },
172
173        '73488': { cost: 150, commission: 0.27, delivery: 90 },
174
175        '75413': { cost: 128.4, commission: 0.27, delivery: 90 },
176
177        '76403': { cost: 352.8, commission: 0.27, delivery: 90 },
178
179        '74799': { cost: 162, commission: 0.27, delivery: 90 },
180
181        '75406': { cost: 117.6, commission: 0.27, delivery: 90 },
182
183        '75154': { cost: 123.6, commission: 0.27, delivery: 90 },
184
185        '75383': { cost: 82.8, commission: 0.27, delivery: 90 },
186
187        '80029': { cost: 67.416, commission: 0.27, delivery: 90 },
188
189        '76120': { cost: 264, commission: 0.27, delivery: 90 },
190
191        '72306': { cost: 186, commission: 0.27, delivery: 90 },
192
193        '75246': { cost: 88.8, commission: 0.27, delivery: 90 },
194
195        '73228': { cost: 133.476, commission: 0.27, delivery: 90 },
196
197        '73419': { cost: 165.6, commission: 0.27, delivery: 90 },
198
199        '74379': { cost: 175.2, commission: 0.27, delivery: 90 },
200
201        '83356': { cost: 229.2, commission: 0.27, delivery: 90 },
202
203        '75444': { cost: 123.6, commission: 0.27, delivery: 90 },
204
205        '79992': { cost: 127.2, commission: 0.27, delivery: 90 },
206
207        '73709': { cost: 218.4, commission: 0.27, delivery: 90 },
208
209        '73778': { cost: 144, commission: 0.27, delivery: 90 },
210
211        '72269': { cost: 194.4, commission: 0.27, delivery: 90 },
212
213        '73440': { cost: 118.8, commission: 0.27, delivery: 90 },
214
215        '74669': { cost: 176.4, commission: 0.27, delivery: 90 },
216
217        '77660': { cost: 0, commission: 0.27, delivery: 90 },
218
219        '77578': { cost: 0, commission: 0.27, delivery: 90 },
220
221        '71545': { cost: 124.8, commission: 0.27, delivery: 90 },
222
223        '75673': { cost: 97.2, commission: 0.27, delivery: 90 },
224
225        '76168': { cost: 80.4, commission: 0.27, delivery: 90 },
226
227        '75277': { cost: 217.2, commission: 0.27, delivery: 90 },
228
229        '75390': { cost: 108, commission: 0.27, delivery: 90 },
230
231        '74263': { cost: 160.8, commission: 0.27, delivery: 90 },
232
233        '74676': { cost: 176.4, commission: 0.27, delivery: 90 },
234
235        '75727': { cost: 121.2, commission: 0.27, delivery: 90 },
236
237        '74126': { cost: 111.6, commission: 0.27, delivery: 90 },
238
239        '74294': { cost: 145.2, commission: 0.27, delivery: 90 },
240
241        '76069': { cost: 220.8, commission: 0.27, delivery: 90 },
242
243        '71361': { cost: 204, commission: 0.27, delivery: 90 },
244
245        '73501': { cost: 114, commission: 0.27, delivery: 90 },
246
247        '72238': { cost: 102, commission: 0.27, delivery: 90 },
248
249        '75482': { cost: 218.4, commission: 0.27, delivery: 90 },
250
251        '76489': { cost: 216, commission: 0.27, delivery: 90 },
252
253        '76076': { cost: 103.2, commission: 0.27, delivery: 90 },
254
255        '75437': { cost: 69.6, commission: 0.27, delivery: 90 },
256
257        '75352': { cost: 72, commission: 0.27, delivery: 90 },
258
259        '75550': { cost: 112.8, commission: 0.27, delivery: 90 },
260
261        '75529': { cost: 114, commission: 0.27, delivery: 90 },
262
263        '76021': { cost: 243.6, commission: 0.27, delivery: 90 },
264
265        '73969': { cost: 91.2, commission: 0.27, delivery: 90 },
266
267        '73242': { cost: 133.488, commission: 0.27, delivery: 90 },
268
269        '80111': { cost: 58.8, commission: 0.27, delivery: 90 },
270
271        '73693': { cost: 159.6, commission: 0.27, delivery: 90 },
272
273        '75703': { cost: 208.8, commission: 0.27, delivery: 90 },
274
275        '74980': { cost: 158.4, commission: 0.27, delivery: 90 },
276
277        '76380': { cost: 145.6, commission: 0.27, delivery: 90 },
278
279        '77677': { cost: 129.948, commission: 0.27, delivery: 90 },
280
281        '75369': { cost: 103.2, commission: 0.27, delivery: 90 },
282
283        '74713': { cost: 180, commission: 0.27, delivery: 90 },
284
285        '75024': { cost: 114, commission: 0.27, delivery: 90 },
286
287        '77615': { cost: 0, commission: 0.27, delivery: 90 },
288
289        '73389': { cost: 136.8, commission: 0.27, delivery: 90 },
290
291        '74850': { cost: 169.2, commission: 0.27, delivery: 90 },
292
293        '75192': { cost: 180, commission: 0.27, delivery: 90 },
294
295        '73310': { cost: 140.4, commission: 0.27, delivery: 90 },
296
297        '73280': { cost: 81.6, commission: 0.27, delivery: 90 },
298
299        '75048': { cost: 122.4, commission: 0.27, delivery: 90 },
300
301        '74874': { cost: 140.4, commission: 0.27, delivery: 90 },
302
303        '71675': { cost: 105.6, commission: 0.27, delivery: 90 },
304
305        '74225': { cost: 153.6, commission: 0.27, delivery: 90 },
306
307        '74768': { cost: 117.6, commission: 0.27, delivery: 90 },
308
309        '73136': { cost: 163.2, commission: 0.27, delivery: 90 },
310
311        '74300': { cost: 134.4, commission: 0.27, delivery: 90 },
312
313        '76410': { cost: 328.8, commission: 0.27, delivery: 90 },
314
315        '74898': { cost: 139.2, commission: 0.27, delivery: 90 },
316
317        '73129': { cost: 159.6, commission: 0.27, delivery: 90 },
318
319        '75253': { cost: 117.6, commission: 0.27, delivery: 90 },
320
321        '75642': { cost: 144.54, commission: 0.27, delivery: 90 },
322
323        '75512': { cost: 88.8, commission: 0.27, delivery: 90 },
324
325        '70999': { cost: 164.4, commission: 0.27, delivery: 90 },
326
327        '76137': { cost: 103.788, commission: 0.27, delivery: 90 },
328
329        '74072': { cost: 148.8, commission: 0.27, delivery: 90 },
330
331        '73297': { cost: 85.2, commission: 0.27, delivery: 90 },
332
333        '76465': { cost: 301.452, commission: 0.27, delivery: 90 },
334
335        '71835': { cost: 73.2, commission: 0.27, delivery: 90 },
336
337        '74324': { cost: 129.6, commission: 0.27, delivery: 90 },
338
339        '71644': { cost: 132, commission: 0.27, delivery: 90 },
340
341        '75420': { cost: 106.8, commission: 0.27, delivery: 90 },
342
343        '74355': { cost: 182.4, commission: 0.27, delivery: 90 },
344
345        '71651': { cost: 174, commission: 0.27, delivery: 90 },
346
347        '74973': { cost: 144, commission: 0.27, delivery: 90 },
348
349        '73341': { cost: 130.8, commission: 0.27, delivery: 90 },
350
351        '75185': { cost: 157.2, commission: 0.27, delivery: 90 },
352
353        '74348': { cost: 132, commission: 0.27, delivery: 90 },
354
355        '75376': { cost: 69.6, commission: 0.27, delivery: 90 },
356
357        '74942': { cost: 159.6, commission: 0.27, delivery: 90 },
358
359        '77592': { cost: 0, commission: 0.27, delivery: 90 },
360
361        '74737': { cost: 183.6, commission: 0.27, delivery: 90 },
362
363        '76045': { cost: 235.2, commission: 0.27, delivery: 90 },
364
365        '74256': { cost: 186, commission: 0.27, delivery: 90 },
366
367        '75208': { cost: 200.4, commission: 0.27, delivery: 90 },
368
369        '76601': { cost: 313.2, commission: 0.27, delivery: 90 },
370
371        '75116': { cost: 346.8, commission: 0.27, delivery: 90 },
372
373        '73464': { cost: 258, commission: 0.27, delivery: 90 },
374
375        '74577': { cost: 134.4, commission: 0.27, delivery: 90 },
376
377        '73792': { cost: 120, commission: 0.27, delivery: 90 },
378
379        '74997': { cost: 159.6, commission: 0.27, delivery: 90 },
380
381        '75611': { cost: 150, commission: 0.27, delivery: 90 },
382
383        '74782': { cost: 145.2, commission: 0.27, delivery: 90 },
384
385        '75031': { cost: 87.6, commission: 0.27, delivery: 90 },
386
387        '74195': { cost: 171.6, commission: 0.27, delivery: 90 },
388
389        '75161': { cost: 96, commission: 0.27, delivery: 90 },
390
391        '74591': { cost: 132, commission: 0.27, delivery: 90 },
392
393        '20107': { cost: 283.752, commission: 0.27, delivery: 90 },
394
395        '74935': { cost: 331.2, commission: 0.27, delivery: 90 },
396
397        '75062': { cost: 99.6, commission: 0.27, delivery: 90 },
398
399        '74706': { cost: 130.8, commission: 0.27, delivery: 90 },
400
401        '75147': { cost: 470.4, commission: 0.27, delivery: 90 },
402
403        '73181': { cost: 234, commission: 0.27, delivery: 90 },
404
405        '74812': { cost: 366, commission: 0.27, delivery: 90 },
406
407        '74805': { cost: 421.2, commission: 0.27, delivery: 90 },
408
409        '74010': { cost: 112.8, commission: 0.27, delivery: 90 },
410
411        '73167': { cost: 98.4, commission: 0.27, delivery: 90 },
412
413        '74416': { cost: 91.2, commission: 0.27, delivery: 90 },
414
415        '75574': { cost: 1517, commission: 0.27, delivery: 90 },
416
417        '74546': { cost: 240, commission: 0.27, delivery: 90 },
418
419        '76199': { cost: 271.2, commission: 0.27, delivery: 90 },
420
421        '74829': { cost: 372, commission: 0.27, delivery: 90 },
422
423        '80173': { cost: 768, commission: 0.27, delivery: 90 },
424
425        '75567': { cost: 1497, commission: 0.27, delivery: 90 },
426
427        '73754': { cost: 392.4, commission: 0.27, delivery: 90 },
428
429        '76298': { cost: 637.2, commission: 0.27, delivery: 90 },
430
431        '74454': { cost: 88.8, commission: 0.27, delivery: 90 },
432
433        '76205': { cost: 273.6, commission: 0.27, delivery: 90 },
434
435        '76274': { cost: 662.4, commission: 0.27, delivery: 90 },
436
437        '76441': { cost: 124.8, commission: 0.27, delivery: 90 },
438
439        '76434': { cost: 145.2, commission: 0.27, delivery: 90 },
440
441        '80227': { cost: 768, commission: 0.27, delivery: 90 },
442
443        '76175': { cost: 285.6, commission: 0.27, delivery: 90 },
444
445        '76304': { cost: 324, commission: 0.27, delivery: 90 },
446
447        '71682': { cost: 99.6, commission: 0.27, delivery: 90 },
448
449        '74959': { cost: 183.6, commission: 0.27, delivery: 90 }
450
451    };
452
453
454    // Функция расчета прибыли
455
456    function calculateProfit(article, revenue, orders, drr) {
457
458        const costData = PRODUCT_COST_DATA[article];
459
460        if (!costData || !revenue || !orders) return null;
461
462        
463
464        // Расходы на рекламу = выручка * (ДРР / 100)
465
466        const adCost = drr ? (revenue * (drr / 100)) : 0;
467
468        
469
470        // Прибыль = Выручка - (заказы * себестоимость) - (заказы * доставка) - (выручка * комиссия) - расходы на рекламу
471
472        const profit = revenue - (orders * costData.cost) - (orders * costData.delivery) - (revenue * costData.commission) - adCost;
473
474        return Math.round(profit); // Округляем до целых
475
476    }
477
478
479    // Класс для сбора данных о товарах
480
481    class ProductDataCollector {
482
483        constructor() {
484
485            this.products = [];
486
487            this.isCollecting = false;
488
489        }
490
491
492        // Автоматическая подгрузка всех товаров
493
494        async loadAllProducts() {
495
496            console.log('📦 Начинаем загрузку всех товаров...');
497
498            this.isCollecting = true;
499
500
501            let previousCount = 0;
502
503            let stableCount = 0; // Счетчик стабильных попыток
504
505            let attempts = 0;
506
507            const maxAttempts = 300; // Увеличили максимум попыток до 300
508
509            const maxStableAttempts = 5; // Увеличили до 5 стабильных попыток
510
511
512            while (attempts < maxAttempts) {
513
514                const loadMoreBtn = document.querySelector('button.styles_loadMoreButton_2RI3D');
515
516                
517
518                if (!loadMoreBtn) {
519
520                    console.log('✅ Кнопка "Показать ещё" не найдена - все товары загружены');
521
522                    break;
523
524                }
525
526
527                // Проверяем, не отключена ли кнопка
528
529                if (loadMoreBtn.disabled || loadMoreBtn.classList.contains('disabled')) {
530
531                    console.log('✅ Кнопка "Показать ещё" отключена - все товары загружены');
532
533                    break;
534
535                }
536
537
538                // Проверяем, есть ли товары с нулевой выручкой (значит дошли до конца)
539
540                const rows = document.querySelectorAll('tr.ct590-c0.ct590-b9');
541
542                let hasZeroRevenue = false;
543
544                
545
546                for (const row of rows) {
547
548                    const cells = row.querySelectorAll('td');
549
550                    if (cells.length >= 3) {
551
552                        const revenueText = cells[2].textContent.trim();
553
554                        // Проверяем, есть ли "0 ₽" или "0₽" в тексте выручки
555
556                        if (revenueText.match(/^0\s*₽/) || revenueText === '0') {
557
558                            hasZeroRevenue = true;
559
560                            console.log('✅ Найден товар с нулевой выручкой - останавливаем загрузку');
561
562                            break;
563
564                        }
565
566                    }
567
568                }
569
570                
571
572                if (hasZeroRevenue) {
573
574                    console.log('✅ Достигнут конец списка активных товаров');
575
576                    break;
577
578                }
579
580
581                // Прокручиваем к кнопке, чтобы она была видна
582
583                loadMoreBtn.scrollIntoView({ behavior: 'smooth', block: 'center' });
584
585                await delay(800);
586
587
588                console.log(`🔄 Клик по кнопке "Показать ещё" (попытка ${attempts + 1})`);
589
590                loadMoreBtn.click();
591
592                
593
594                // Увеличили задержку до 4 секунд для полной загрузки данных
595
596                await delay(4000);
597
598
599                const currentCount = document.querySelectorAll('tr.ct590-c0.ct590-b9').length;
600
601                console.log(`📊 Загружено товаров: ${currentCount} (было: ${previousCount})`);
602
603
604                if (currentCount === previousCount) {
605
606                    stableCount++;
607
608                    console.log(`⏸️ Количество не изменилось (${stableCount}/${maxStableAttempts})`);
609
610                    
611
612                    if (stableCount >= maxStableAttempts) {
613
614                        console.log('✅ Количество товаров стабильно - загрузка завершена');
615
616                        break;
617
618                    }
619
620                } else {
621
622                    stableCount = 0; // Сбрасываем счетчик, если количество изменилось
623
624                }
625
626
627                previousCount = currentCount;
628
629                attempts++;
630
631            }
632
633
634            const finalCount = document.querySelectorAll('tr.ct590-c0.ct590-b9').length;
635
636            console.log(`✅ Загрузка завершена. Всего товаров: ${finalCount}`);
637
638            this.isCollecting = false;
639
640        }
641
642
643        // Сбор данных из таблицы
644
645        collectProductData() {
646
647            console.log('📊 Собираем данные о товарах...');
648
649            this.products = [];
650
651
652            const rows = document.querySelectorAll('tr.ct590-c0.ct590-b9');
653
654            console.log(`Найдено строк: ${rows.length}`);
655
656
657            rows.forEach((row, index) => {
658
659                try {
660
661                    const cells = row.querySelectorAll('td');
662
663                    if (cells.length < 10) return;
664
665
666                    // Извлекаем данные из ячеек
667
668                    const productData = this.extractProductData(cells);
669
670                    if (productData) {
671
672                        this.products.push(productData);
673
674                    }
675
676                } catch (error) {
677
678                    console.error(`Ошибка при обработке строки ${index}:`, error);
679
680                }
681
682            });
683
684
685            console.log(`✅ Собрано товаров: ${this.products.length}`);
686
687            return this.products;
688
689        }
690
691
692        // Извлечение данных о товаре из ячеек
693
694        extractProductData(cells) {
695
696            try {
697
698                // Название и артикул (первая ячейка)
699
700                const nameCell = cells[0];
701
702                const nameLink = nameCell.querySelector('a.styles_productName_2qRJi');
703
704                const captionEl = nameCell.querySelector('.styles_productCaption_7MqtH');
705
706                
707
708                const name = nameLink ? nameLink.textContent.trim() : '';
709
710                const articleMatch = captionEl ? captionEl.textContent.match(/Арт\.\s*(\d+)/) : null;
711
712                const article = articleMatch ? articleMatch[1] : '';
713
714
715                if (!name || !article) return null;
716
717
718                // Получаем текстовое содержимое всех ячеек
719
720                const cellTexts = Array.from(cells).map(cell => cell.textContent.trim());
721
722
723                // Парсим основные показатели по правильным индексам
724
725                // Выручка - индекс 2
726
727                const revenue = parseNumber(cellTexts[2]);
728
729                const revenueChange = parsePercent(cellTexts[2]);
730
731                
732
733                // Заказано товаров - индекс 20
734
735                const orders = parseNumber(cellTexts[20]);
736
737                const ordersChange = parsePercent(cellTexts[20]);
738
739                
740
741                // Показы всего - индекс 5
742
743                const impressions = parseNumber(cellTexts[5]);
744
745                const impressionsChange = parsePercent(cellTexts[5]);
746
747                
748
749                // Посещения карточки товара - индекс 13
750
751                const cardVisits = parseNumber(cellTexts[13]);
752
753                const cardVisitsChange = parsePercent(cellTexts[13]);
754
755                
756
757                // Конверсия из поиска и каталога в карточку (CTR) - индекс 12
758
759                const conversionCatalogToCard = parseNumber(cellTexts[12]);
760
761                const conversionCatalogToCardChange = parsePercent(cellTexts[12]);
762
763                
764
765                // Конверсия из карточки в корзину (CRL) - индекс 15
766
767                const conversionCardToCart = parseNumber(cellTexts[15]);
768
769                const conversionCardToCartChange = parsePercent(cellTexts[15]);
770
771                
772
773                // Добавления в корзину всего - индекс 18
774
775                const cartAdditions = parseNumber(cellTexts[18]);
776
777                const cartAdditionsChange = parsePercent(cellTexts[18]);
778
779                
780
781                // CR - высчитываем: Заказано товаров / Посещения карточки товаров
782
783                const cr = (orders && cardVisits && cardVisits > 0) ? parseFloat(((orders / cardVisits) * 100).toFixed(1)) : null;
784
785                const crChange = null; // Изменение CR нужно высчитывать отдельно
786
787                
788
789                // Общая ДРР - индекс 32 (парсим как процент, убираем знак %)
790
791                const drrText = cellTexts[32] || '';
792
793                const drrMatch = drrText.match(/(\d+(?:\.\d+)?)\s*%/);
794
795                const drr = drrMatch ? parseFloat(drrMatch[1]) : null;
796
797                const drrChange = parsePercent(cellTexts[32]);
798
799                
800
801                // Остаток на конец периода - индекс 35
802
803                const stockText = cellTexts[35] || '';
804
805                const stockMatch = stockText.match(/(\d+)/);
806
807                const stock = stockMatch ? parseInt(stockMatch[1]) : null;
808
809                
810
811                // Средняя цена - индекс 28 (используем parsePrice для корректного парсинга)
812
813                const avgPrice = parsePrice(cellTexts[28]);
814
815                const avgPriceChange = parsePercent(cellTexts[28]);
816
817                
818
819                // Среднее время доставки - индекс 37
820
821                const deliveryTime = cellTexts[37] || null;
822
823                
824
825                // Рассчитываем прибыль
826
827                const profit = calculateProfit(article, revenue, orders, drr);
828
829                
830
831                // Рассчитываем прибыль в процентах от выручки
832                const profitPercent = (profit !== null && revenue && revenue > 0) ? 
833                    parseFloat(((profit / revenue) * 100).toFixed(1)) : null;
834                
835                // Рассчитываем "на дней" - остаток / заказы
836                const daysOfStock = (orders && stock && orders > 0) ? Math.floor(stock / orders) : null;
837
838                // Логируем расчет для отладки
839                console.log(`📊 Артикул ${article}: остаток=${stock}, заказы=${orders}, на дней=${daysOfStock}`);
840
841
842                const product = {
843
844                    name,
845
846                    article,
847
848                    revenue,
849
850                    revenueChange,
851
852                    orders,
853
854                    ordersChange,
855
856                    impressions,
857
858                    impressionsChange,
859
860                    cardVisits,
861
862                    cardVisitsChange,
863
864                    conversionCatalogToCard,
865
866                    conversionCatalogToCardChange,
867
868                    conversionCardToCart,
869
870                    conversionCardToCartChange,
871
872                    cartAdditions,
873
874                    cartAdditionsChange,
875
876                    cr,
877
878                    crChange,
879
880                    avgPrice,
881
882                    avgPriceChange,
883
884                    drr,
885
886                    drrChange,
887
888                    stock,
889
890                    deliveryTime,
891
892                    daysOfStock,
893
894                    profit,
895
896                    profitPercent,
897
898                    rawData: cellTexts
899
900                };
901
902
903                return product;
904
905            } catch (error) {
906
907                console.error('Ошибка извлечения данных товара:', error);
908
909                return null;
910
911            }
912
913        }
914
915    }
916
917
918    // Класс для AI анализа
919
920    class AIAnalyzer {
921
922        // Батч-анализ товаров с умной фильтрацией
923
924        async analyzeProducts(products, onProgress) {
925
926            console.log('🤖 Начинаем AI анализ товаров...');
927
928            
929
930            // Сначала вычисляем средние показатели
931
932            const avgMetrics = this.calculateAverageMetrics(products);
933
934            console.log('📊 Средние показатели:', avgMetrics);
935
936            
937
938            // Разделяем товары на приоритетные и обычные
939
940            const priorityProducts = [];
941
942            const normalProducts = [];
943
944            
945
946            products.forEach(product => {
947
948                const needsAIAnalysis = this.needsDetailedAnalysis(product, avgMetrics);
949
950                if (needsAIAnalysis) {
951
952                    priorityProducts.push(product);
953
954                } else {
955
956                    normalProducts.push(product);
957
958                }
959
960            });
961
962            
963
964            console.log(`📊 Приоритетных товаров для AI анализа: ${priorityProducts.length}`);
965
966            console.log(`📊 Обычных товаров (базовый анализ): ${normalProducts.length}`);
967
968            
969
970            const analyzedProducts = [];
971
972            const batchSize = 10; // Увеличили до 10 товаров одновременно
973
974            
975
976            // Сначала быстро обрабатываем обычные товары (без AI)
977
978            normalProducts.forEach(product => {
979
980                analyzedProducts.push({
981
982                    ...product,
983
984                    analysis: this.basicAnalysis(product, avgMetrics)
985
986                });
987
988            });
989
990            
991
992            // Обновляем прогресс после базового анализа
993
994            if (onProgress) {
995
996                const percentage = Math.round((normalProducts.length / products.length) * 100);
997
998                const remaining = Math.ceil((priorityProducts.length / batchSize) * 2);
999
1000                onProgress(normalProducts.length, products.length, percentage, remaining);
1001
1002            }
1003
1004            
1005
1006            // Анализируем приоритетные товары с AI
1007
1008            for (let i = 0; i < priorityProducts.length; i += batchSize) {
1009
1010                const batch = priorityProducts.slice(i, i + batchSize);
1011
1012                const batchPromises = batch.map(product => this.analyzeProduct(product, avgMetrics, true));
1013
1014                
1015
1016                const batchResults = await Promise.all(batchPromises);
1017
1018                
1019
1020                batchResults.forEach((analysis, idx) => {
1021
1022                    analyzedProducts.push({
1023
1024                        ...batch[idx],
1025
1026                        analysis
1027
1028                    });
1029
1030                });
1031
1032                
1033
1034                const progress = Math.min(i + batchSize, priorityProducts.length);
1035
1036                const totalProgress = normalProducts.length + progress;
1037
1038                const percentage = Math.round((totalProgress / products.length) * 100);
1039
1040                const remaining = Math.ceil(((priorityProducts.length - progress) / batchSize) * 2);
1041
1042                
1043
1044                if (onProgress) {
1045
1046                    onProgress(totalProgress, products.length, percentage, remaining);
1047
1048                }
1049
1050                
1051
1052                console.log(`✅ Проанализировано ${progress} из ${priorityProducts.length} приоритетных товаров`);
1053
1054            }
1055
1056            
1057
1058            if (onProgress) {
1059
1060                onProgress(products.length, products.length, 100, 0);
1061
1062            }
1063
1064
1065            return analyzedProducts;
1066
1067        }
1068
1069
1070        // Определяем, нужен ли детальный AI анализ
1071
1072        needsDetailedAnalysis(product, avgMetrics) {
1073
1074            const threshold = 5; // Порог отклонения 5%
1075
1076            
1077
1078            // Если есть значительное падение выручки
1079
1080            if (product.revenueChange !== null && product.revenueChange < avgMetrics.revenueChange - threshold) {
1081
1082                return true;
1083
1084            }
1085
1086            
1087
1088            // Если есть значительное падение заказов
1089
1090            if (product.ordersChange !== null && product.ordersChange < avgMetrics.ordersChange - threshold) {
1091
1092                return true;
1093
1094            }
1095
1096            
1097
1098            // Если высокий ДРР
1099
1100            if (product.drr !== null && product.drr > 20) {
1101
1102                return true;
1103
1104            }
1105
1106            
1107
1108            // Если низкие остатки
1109
1110            const daysOfStock = product.orders && product.stock ? Math.floor(product.stock / (product.orders / 7)) : null;
1111
1112            if (daysOfStock !== null && daysOfStock < 49) {
1113
1114                return true;
1115
1116            }
1117
1118            
1119
1120            // Если значительный рост (для масштабирования)
1121
1122            if (product.revenueChange !== null && product.revenueChange > avgMetrics.revenueChange + 15) {
1123
1124                return true;
1125
1126            }
1127
1128            
1129
1130            return false;
1131
1132        }
1133
1134
1135        // Базовый анализ без AI (для товаров без проблем)
1136
1137        basicAnalysis(product, avgMetrics) {
1138
1139            const daysOfStock = product.orders && product.stock ? Math.floor(product.stock / (product.orders / 7)) : null;
1140
1141            const isLowStock = daysOfStock !== null && daysOfStock <= 14;
1142
1143            const isHighDRR = product.drr !== null && product.drr > 20;
1144
1145            const isLowDRR = product.drr !== null && product.drr <= 17;
1146
1147            const isGrowth = this.detectGrowth(product, avgMetrics);
1148
1149            const isLowImpressions = product.impressionsChange !== null && product.impressionsChange <= -20;
1150
1151            const isLowCR = (product.conversionCardToCartChange !== null && product.conversionCardToCartChange <= -20) || 
1152
1153                           (product.conversionCatalogToCardChange !== null && product.conversionCatalogToCardChange <= -20);
1154
1155            const isLowProfit = product.profit !== null && product.revenue !== null && product.revenue > 0 && 
1156
1157                               (product.profit / product.revenue) < 0.25;
1158
1159            
1160
1161            // Проверяем время доставки (парсим число из строки типа "35 ч")
1162
1163            const deliveryHours = product.deliveryTime ? parseInt(product.deliveryTime) : null;
1164
1165            const isBadDeliveryTime = deliveryHours !== null && deliveryHours >= 35;
1166
1167
1168            // Генерируем рекомендации на основе проблем
1169
1170            const recommendations = [];
1171
1172            
1173
1174            if (isLowStock) {
1175
1176                recommendations.push('Низкие остатки - Поставить товар, повысить цену, снизить ДРР');
1177
1178            }
1179
1180
1181            if (isHighDRR) {
1182
1183                recommendations.push('Высокий ДРР - Понизить ДРР, Снизить цену');
1184
1185            }
1186
1187
1188            if (isLowDRR) {
1189
1190                recommendations.push('Повысить ДРР - Повысить ДРР, Повысить цену');
1191
1192            }
1193
1194
1195            if (isLowImpressions) {
1196
1197                recommendations.push('Упали Показы - Проверить остатки, Повысить ДРР, Понизить цену');
1198
1199            }
1200
1201            
1202
1203            if (isLowCR) {
1204
1205                recommendations.push('Упал CR - Проверить остатки, Понизить цену');
1206
1207            }
1208
1209            
1210
1211            if (isLowProfit) {
1212
1213                recommendations.push('Низкая прибыль - снизить ДРР, проверить цену');
1214
1215            }
1216
1217            
1218
1219            if (isBadDeliveryTime) {
1220
1221                recommendations.push('Плохое время - проверить остатки, сделать поставку');
1222
1223            }
1224
1225
1226            if (isGrowth) {
1227
1228                recommendations.push('Рост - поднять цену');
1229
1230            }
1231
1232            
1233
1234            // Если нет проблем - выводим "Всё хорошо"
1235
1236            if (recommendations.length === 0) {
1237
1238                recommendations.push('Всё хорошо, рекомендаций нет');
1239
1240            }
1241
1242            
1243
1244            return {
1245
1246                priority: 'low',
1247
1248                problems: [],
1249
1250                recommendations,
1251
1252                daysOfStock,
1253
1254                isLowStock,
1255
1256                isHighDRR,
1257
1258                isLowDRR,
1259
1260                isGrowth,
1261
1262                isLowImpressions,
1263
1264                isLowCR,
1265
1266                isLowProfit,
1267
1268                isBadDeliveryTime
1269
1270            };
1271
1272        }
1273
1274
1275        // Вычисление средних показателей
1276
1277        calculateAverageMetrics(products) {
1278
1279            const validProducts = products.filter(p => p.revenueChange !== null);
1280
1281            if (validProducts.length === 0) return { revenueChange: 0, ordersChange: 0, impressionsChange: 0 };
1282
1283            
1284
1285            const sum = validProducts.reduce((acc, p) => ({
1286
1287                revenueChange: acc.revenueChange + (p.revenueChange || 0),
1288
1289                ordersChange: acc.ordersChange + (p.ordersChange || 0),
1290
1291                impressionsChange: acc.impressionsChange + (p.impressionsChange || 0)
1292
1293            }), { revenueChange: 0, ordersChange: 0, impressionsChange: 0 });
1294
1295            
1296
1297            return {
1298
1299                revenueChange: sum.revenueChange / validProducts.length,
1300
1301                ordersChange: sum.ordersChange / validProducts.length,
1302
1303                impressionsChange: sum.impressionsChange / validProducts.length
1304
1305            };
1306
1307        }
1308
1309
1310        async analyzeProduct(product, avgMetrics, useAI = true) {
1311
1312            try {
1313
1314                // Используем уже рассчитанное значение daysOfStock из product
1315                const daysOfStock = product.daysOfStock;
1316
1317                const isLowStock = daysOfStock !== null && daysOfStock <= 14;
1318
1319                const isHighDRR = product.drr !== null && product.drr > 20;
1320
1321
1322                const isLowDRR = product.drr !== null && product.drr <= 17;
1323
1324                const isGrowth = this.detectGrowth(product, avgMetrics);
1325
1326                const isLowImpressions = product.impressionsChange !== null && product.impressionsChange <= -20;
1327
1328                const isLowCR = (product.conversionCardToCartChange !== null && product.conversionCardToCartChange <= -20) || 
1329
1330                               (product.conversionCatalogToCardChange !== null && product.conversionCatalogToCardChange <= -20);
1331
1332                const isLowProfit = product.profit !== null && product.revenue !== null && product.revenue > 0 && 
1333
1334                                   (product.profit / product.revenue) < 0.25;
1335
1336                
1337
1338                // Проверяем время доставки
1339
1340                const deliveryHours = product.deliveryTime ? parseInt(product.deliveryTime) : null;
1341
1342                const isBadDeliveryTime = deliveryHours !== null && deliveryHours >= 35;
1343
1344                
1345
1346                if (!useAI) {
1347
1348                    return this.basicAnalysis(product, avgMetrics);
1349
1350                }
1351
1352                
1353
1354                // Формируем промпт для AI
1355
1356                const prompt = `Ты — AI‑аналитик маркетплейса Ozon. На вход ты получаешь показатели одного товара за выбранный период и их динамику к прошлому периоду.
1357
1358Метрики, которые ты можешь видеть:
1359- выручка, прибыль, маржа в %;
1360- заказы, корзины;
1361- показы, клики/переходы в карточку, CTR, CR, CRL;
1362- рекламные расходы, ДРР;
1363- цена;
1364- остаток в штуках, показатель «хватит на дней»;
1365- время доставки (в часах);
1366- динамика каждого показателя к прошлому периоду в % (например, «-7%»).
1367
1368Других данных (рейтинг, отзывы, акции) у тебя может не быть — не опирайся на них, если они прямо не переданы.
1369
1370БИЗНЕС‑ЦЕЛЬ:
1371Максимизировать оборот (выручку и заказы) при соблюдении ограничений:
1372- целевая маржа — не ниже 25% (нормальный диапазон 25–30%; выше 30% — высокая маржа);
1373- целевой ДРР — около 20% (нормальный диапазон 17–25%).
1374- абсолютный минимум маржи, ниже которого НЕЛЬЗЯ предлагать снижать цену — 15%.
1375
1376Допустимы временные отклонения:
1377- при агрессивном разгоне нового товара можно повышать ДРР до ~35%, если прибыль остаётся положительной и маржа не падает ниже 30%;
1378- при огромных остатках (>60 дней запаса) или выводе товара допускается снижать маржу, но всё равно не ниже 15%.
1379
1380ДОПУСТИМЫЕ РЫЧАГИ УПРАВЛЕНИЯ:
1381Ты можешь рекомендовать действия только в четырёх областях:
13821. Цена.
13832. Реклама / ДРР (ставки, бюджеты).
13843. Остатки и логистика (объём и распределение остатков, поставки, влияние на срок доставки).
13854. Карточка товара (фото, заголовок, описание).
1386
1387Запрещено предлагать любые действия вне этих четырёх областей.
1388
1389ПОРОГИ И ОПРЕДЕЛЕНИЯ СОСТОЯНИЙ:
1390
1391Время доставки:
1392- Нормальное: ≤ 35 часов.
1393- Плохое: > 35 и ≤ 40 часов, особенно если выросло к прошлому периоду.
1394- Критически плохое: > 40 часов.
1395
1396Остатки / «хватит на дней»:
1397- Критически мало: ≤ 7 дней.
1398- Низкий запас: > 7 и ≤ 14 дней.
1399- Нормально: > 14 и ≤ 30 дней.
1400- Избыточный запас: > 30 дней.
1401- Огромные остатки: > 60 дней.
1402
1403Маржа (прибыль в %):
1404- Очень низкая: < 20%.
1405- Низкая: 20–25%.
1406- Нормальная: 25–30%.
1407- Высокая: > 30%.
1408- Абсолютный минимум, ниже которого нельзя снижать цену — 15%.
1409
1410ДРР:
1411- Целевой ДРР: ~20%.
1412- Слишком низкий ДРР: < 17% (есть запас для роста трафика).
1413- Нормальный ДРР: 17–25%.
1414- Слишком высокий ДРР: > 25% (режет прибыль; допустим только как временная мера при разгоне нового товара).
1415
1416ШАГИ ИЗМЕНЕНИЯ ЦЕНЫ:
1417Разовые рекомендации по изменению цены (в любую сторону) должны быть в диапазоне примерно 5–10%, не больше. Нельзя предлагать очень резких скачков цены за один шаг.
1418
1419ПРИОРИТЕТЫ АНАЛИЗА:
1420
14211. Остатки и время доставки — первый приоритет.
14222. Потом — выручка, заказы, прибыль, маржа и ДРР.
14233. Затем — показы, CTR, CR, CRL, конверсия и качество карточки.
1424
1425ПРАВИЛА АНАЛИЗА:
1426
14271. ОСТАТКИ И ВРЕМЯ ДОСТАВКИ — ГЛАВНЫЕ СТОП‑ФАКТОРЫ
1428
1429Если остатки критически низкие или низкие (≤14 дней) ИЛИ время доставки плохое/критическое (>35 часов):
1430
1431- Главная задача — не уйти в out of stock и восстановить нормальную доставку.
1432- Рекомендовать в первую очередь:
1433  • [Остатки] Поставку товара и/или перераспределение на ближайший склад, чтобы довести запас минимум до 14–30 дней и сократить срок доставки.
1434  • [Цена] При необходимости временно ПОВЫСИТЬ цену (на 5–10%), чтобы притормозить продажи, пока товар едет или пока не нормализуется доставка.
1435  • [Реклама] Временно СНИЗИТЬ ДРР (урезать ставки и бюджеты, отключить самые неэффективные кампании), чтобы не разгонять спрос.
1436
1437ВАЖНО: В этом состоянии СТРОГО ЗАПРЕЩЕНО:
1438- советовать снижение цены,
1439- советовать повышение ДРР,
1440даже если упали показы, клики или заказы.
1441
14422. НОРМАЛЬНЫЕ ОСТАТКИ И ДОСТАВКА
1443
1444Если остатки >14 дней и время доставки ≤35 часов (нормальные):
1445
1446- Можно фокусироваться на росте оборта и оптимизации прибыли в рамках целевых маржи и ДРР.
1447- При избыточных остатках:
1448  • 30–60 дней: можно аккуратно усиливать спрос (повышать ДРР, немного снижать цену, но по возможности держать маржу ≥25% и не опускаться ниже 20%).
1449  • >60 дней (огромные остатки): допусти более агрессивный разгон (снижение цены, повышение ДРР), но маржа всё равно не должна опускаться ниже 15%.
1450
14513. ВЫРУЧКА, ЗАКАЗЫ, ПРИБЫЛЬ, МАРЖА
1452
1453- Если выручка и заказы падают при нормальных остатках и доставке:
1454  • искать причины в показах, рекламе, цене, конверсии и качестве карточки;
1455  • смотреть динамику CTR/CR/CRL.
1456
1457- Если прибыль и маржа низкие (ниже 25%) или падают, особенно на фоне высокого ДРР (>25%):
1458  • основная цель — вернуть маржу и прибыль к целевым уровням;
1459  • рекомендовать снижать ДРР (резать ставки и бюджеты, отключать неэффективные кампании/ключи);
1460  • рассматривать повышение цены (в разумных пределах 5–10%), если спрос это позволяет;
1461  • не предлагать существенное снижение цены, если маржа уже низкая или близка к минимуму (особенно ниже 20%).
1462
14634. РЕКЛАМА И ДРР
1464
1465- Высокий ДРР (>25%):
1466  • реклама неэффективна или слишком агрессивна;
1467  • рекомендуемые действия:
1468    – [Реклама] Снизить ДРР: уменьшить ставки и бюджеты, отключить слабые кампании/запросы;
1469    – при нормальных остатках и низком CR дополнительно: немного снизить цену и/или улучшить карточку, чтобы поднять конверсию;
1470    – исключение — новый товар: временно можно терпеть ДРР до ~35%, но с контролем маржи (не ниже 15–20%).
1471
1472- Слишком низкий ДРР (<17%) при нормальной/высокой марже и достатенных остатках:
1473  • есть потенциал роста оборта;
1474  • рекомендовать:
1475    – [Реклама] Постепенно повышать ДРР: поднимать ставки, расширять кампании;
1476    – при необходимости можно немного поднять цену, чтобы сохранить маржу при росте трафика.
1477
1478- Если показы упали, а остатки и доставка в норме:
1479  • в приоритете рекламные действия:
1480    – [Реклама] Повысить ДРР для ускорения продаж (если ДРР низкий);
1481  • вторым шагом:
1482    – [Цена] Немного снизить цену (5–10%), если маржа выше целевой;
1483    – [Карточка] При необходимости доработать заголовок и ключевые слова для релевантности.
1484
14856. ПОЛОЖИТЕЛЬНАЯ ДИНАМИКА
1486
1487Если выручка, заказы и прибыль растут, маржа ≥25%, ДРР в норме (17–25%), остатки достаточные (>14 дней) и нет риска out of stock, срок доставки хороший (≤35 часов):
1488
1489- не предлагать резких изменений;
1490- основная рекомендация:
1491  • [Цена] Осторожно тестировать повышение цены на 5–10% с контролем CR и выручки;
1492  • [Реклама] При низком ДРР и хорошем запасе по прибыли — мягко расширять рекламу.
1493
14947. НОВЫЕ ТОВАРЫ И ВЫВОД ТОВАРА
1495
1496- Новый товар (распознавать по очень малому количеству заказов за период при нормальных/избыточных остатках):
1497  • допустимо:
1498    – [Реклама] Повышать ДРР до ~35% ради разгона, если есть запас по марже и бюджу;
1499    – [Цена] Сильных скидок не давать сразу, чтобы не убивать маржу; тестировать цену аккуратно.
1500
1501- Товар под вывод / очень большие остатки (запас >60 дней и слабые продажи):
1502  • КРИТИЧЕСКИ ВАЖНО: Перед любой рекомендацией по снижению цены ОБЯЗАТЕЛЬНО проверь текущую маржу:
1503    – Если маржа <20% (низкая или очень низкая): СТРОГО ЗАПРЕЩЕНО рекомендовать снижение цены в любом виде.
1504      * Вместо этого рекомендуй:
1505        [Реклама] Повысить ДРР для ускорения продаж (если ДРР низкий);
1506        [Карточка] Улучшить карточку товара для повышения конверсии;
1507        [Остатки] Рассмотреть перераспределение остатков или вывод товара из ассортимента.
1508    – Если маржа ≥20% и <25%: можно осторожно рекомендовать снижение цены на 3-5%, но с обязательным контролем, чтобы маржа не упала ниже 15%.
1509    – Если маржа ≥25%: можно рекомендовать снижение цены на 5-10%, но следить, чтобы маржа не упала ниже 20%.
1510  • ЗАПРЕЩЕНО: Рекомендовать снижение цены без явной проверки и упоминания текущей маржи в рекомендации.
1511  • допустимо:
1512    – [Реклама] Поддерживать или немного повышать ДРР, если это экономически оправдано;
1513    – цель — ускорить оборачиваемость без ухода в ноль или минус по прибыли.
1514
1515ФОРМАТ ОТВЕТА:
1516
15171. Блок «Диагноз» — 1–3 коротких предложения:
1518   - что происходит с товаром (рост/падение выручки, заказов, прибыли);
1519   - в чём основная причина (остатки, реклама, цена, карточка, доставка, конверсия).
1520
15212. Блок «Рекомендации» — маркированный список из 3–7 пунктов.
1522
1523Каждый пункт рекомендаций должен начинаться с области в квадратных скобках: [Цена] / [Реклама] / [Остатки] / [Карточка], а дальше — конкретное действие и короткое объяснение «зачем».
1524
1525Примеры формата:
1526- [Остатки] Сделать поставку на ближайший склад на 2–3 недели продаж, чтобы сократить срок доставки и не уйти в out of stock.
1527- [Реклама] Снизить ДРР с ~25% до ~18–20%, отключив неэффективные кампании — это повысит маржу и прибыль.
1528- [Цена] Тестово поднять цену на 5–10%, так как показатели растут и есть запас по конверсии.
1529- [Карточка] Переработать первое фото и заголовок, чтобы повысить CTR и вернуть посещаемость карточки.
1530
1531ОГРАНИЧЕНИЯ:
1532- Не перечисляй все возможные варианты действий — выбирай только те, которые реально вытекают из данных и не противоречат друг другу.
1533- Если информации не хватает для однозначного вывода — напиши, какие есть возможные причины и дай для каждой гипотезы отдельный набор действий.
1534- Пиши простым, понятным языком, без сложных терминов и «воды».
1535
1536--------------------
1537ДАННЫЕ ТОВАРА
1538--------------------
1539
1540Товар: ${product.name}
1541Артикул: ${product.article}
1542
1543Метрики:
1544- Выручка: ${product.revenue || 'н/д'} ₽ (изменение: ${product.revenueChange || 0}%)
1545- Прибыль: ${product.profit || 'н/д'} ₽ (${product.profitPercent || 'н/д'}% от выручки)
1546- Показы всего: ${product.impressions || 'н/д'} (изменение: ${product.impressionsChange || 0}%)
1547- Посещения карточки: ${product.cardVisits || 'н/д'} (изменение: ${product.cardVisitsChange || 0}%)
1548- CTR (каталог→карточка): ${product.conversionCatalogToCard || 'н/д'}% (изменение: ${product.conversionCatalogToCardChange || 0}%)
1549- Добавления в корзину: ${product.cartAdditions || 'н/д'} (изменение: ${product.cartAdditionsChange || 0}%)
1550- CRL (карточка→корзина): ${product.conversionCardToCart || 'н/д'}% (изменение: ${product.conversionCardToCartChange || 0}%)
1551- Заказы: ${product.orders || 'н/д'} шт (изменение: ${product.ordersChange || 0}%)
1552- CR (заказы/карточка): ${product.cr || 'н/д'}%
1553- ДРР: ${product.drr || 'н/д'}% (изменение: ${product.drrChange || 0}%)
1554- Средняя цена: ${product.avgPrice || 'н/д'} ₽ (изменение: ${product.avgPriceChange || 0}%)
1555- Остаток: ${product.stock || 'н/д'} шт
1556- На дней: ${daysOfStock || 'н/д'} дней
1557- Доставка: ${product.deliveryTime || 'н/д'}
1558
1559                `;
1560
1561                const response = await RM.aiCall(prompt, {
1562
1563                    type: 'json_schema',
1564
1565                    json_schema: {
1566
1567                        name: 'product_analysis',
1568
1569                        schema: {
1570
1571                            type: 'object',
1572
1573                            properties: {
1574
1575                                priority: {
1576
1577                                    type: 'string',
1578
1579                                    enum: ['critical', 'high', 'medium', 'low']
1580
1581                                },
1582
1583                                problems: {
1584
1585                                    type: 'array',
1586
1587                                    items: {
1588
1589                                        type: 'object',
1590
1591                                        properties: {
1592
1593                                            type: { type: 'string' },
1594
1595                                            description: { type: 'string' }
1596
1597                                        },
1598
1599                                        required: ['type', 'description']
1600
1601                                    }
1602
1603                                },
1604
1605                                recommendations: {
1606
1607                                    type: 'array',
1608
1609                                    items: { type: 'string' }
1610
1611                                }
1612
1613                            },
1614
1615                            required: ['priority', 'problems', 'recommendations']
1616
1617                        }
1618
1619                    }
1620
1621                });
1622
1623
1624                return {
1625
1626                    ...response,
1627
1628                    daysOfStock,
1629
1630                    isLowStock,
1631
1632                    isHighDRR,
1633
1634                    isLowDRR,
1635
1636                    isGrowth,
1637
1638                    isLowImpressions,
1639
1640                    isLowCR,
1641
1642                    isLowProfit,
1643
1644                    isBadDeliveryTime
1645
1646                };
1647
1648            } catch (error) {
1649
1650                console.error('Ошибка AI анализа:', error);
1651
1652                return this.basicAnalysis(product, avgMetrics);
1653
1654            }
1655
1656        }
1657
1658
1659        // Определение роста на основе средних показателей
1660
1661        detectGrowth(product, avgMetrics) {
1662
1663            const threshold = 15; // Порог отклонения от среднего в %
1664
1665            
1666
1667            // Если выручка растет значительно выше среднего
1668
1669            if (product.revenueChange !== null && 
1670
1671                product.revenueChange > avgMetrics.revenueChange + threshold) {
1672
1673                return true;
1674
1675            }
1676
1677            
1678
1679            // Если заказы растут значительно выше среднего
1680
1681            if (product.ordersChange !== null && 
1682
1683                product.ordersChange > avgMetrics.ordersChange + threshold) {
1684
1685                return true;
1686
1687            }
1688
1689            
1690
1691            return false;
1692
1693        }
1694
1695    }
1696
1697
1698    // Класс для UI
1699
1700    class AnalyticsUI {
1701
1702        constructor() {
1703
1704            this.container = null;
1705
1706            this.filteredProducts = [];
1707
1708            this.allProducts = [];
1709
1710            this.currentFilter = 'all';
1711
1712            this.isCollapsed = false;
1713
1714            this.isDragging = false;
1715
1716            this.isResizing = false;
1717
1718            this.dragStartX = 0;
1719
1720            this.dragStartY = 0;
1721
1722            this.containerStartX = 0;
1723
1724            this.containerStartY = 0;
1725
1726            this.resizeStartWidth = 0;
1727
1728            this.resizeStartHeight = 0;
1729
1730        }
1731
1732
1733        createUI() {
1734
1735            console.log('🎨 Создаем UI...');
1736
1737
1738            // Создаем контейнер для нашего UI
1739
1740            this.container = document.createElement('div');
1741
1742            this.container.id = 'ozon-ai-analytics';
1743
1744            this.container.style.cssText = `
1745
1746                position: fixed;
1747
1748                top: 80px;
1749
1750                right: 20px;
1751
1752                width: 500px;
1753
1754                max-height: 85vh;
1755
1756                background: white;
1757
1758                border-radius: 12px;
1759
1760                box-shadow: 0 4px 20px rgba(0,0,0,0.15);
1761
1762                z-index: 10000;
1763
1764                overflow: hidden;
1765
1766                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1767
1768                resize: both;
1769
1770                min-width: 400px;
1771
1772                min-height: 200px;
1773
1774            `;
1775
1776
1777            // Заголовок (с возможностью перетаскивания)
1778
1779            const header = document.createElement('div');
1780
1781            header.id = 'ozon-ai-header';
1782
1783            header.style.cssText = `
1784
1785                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1786
1787                color: white;
1788
1789                padding: 18px 24px;
1790
1791                font-weight: 600;
1792
1793                font-size: 18px;
1794
1795                display: flex;
1796
1797                justify-content: space-between;
1798
1799                align-items: center;
1800
1801                cursor: move;
1802
1803                user-select: none;
1804
1805            `;
1806
1807            header.innerHTML = `
1808
1809                <span>🤖 AI Аналитик Продаж</span>
1810
1811                <div style="display: flex; gap: 8px; align-items: center;">
1812
1813                    <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>
1814
1815                    <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>
1816
1817                </div>
1818
1819            `;
1820
1821
1822            // Кнопка запуска анализа
1823
1824            const startButton = document.createElement('button');
1825
1826            startButton.id = 'ozon-ai-start';
1827
1828            startButton.textContent = '🚀 Запустить анализ';
1829
1830            startButton.style.cssText = `
1831
1832                width: calc(100% - 40px);
1833
1834                margin: 20px;
1835
1836                padding: 16px;
1837
1838                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1839
1840                color: white;
1841
1842                border: none;
1843
1844                border-radius: 8px;
1845
1846                font-size: 16px;
1847
1848                font-weight: 600;
1849
1850                cursor: pointer;
1851
1852                transition: transform 0.2s;
1853
1854            `;
1855
1856            startButton.onmouseover = () => startButton.style.transform = 'scale(1.02)';
1857
1858            startButton.onmouseout = () => startButton.style.transform = 'scale(1)';
1859
1860
1861            // Контейнер для контента
1862
1863            const content = document.createElement('div');
1864
1865            content.id = 'ozon-ai-content';
1866
1867            content.style.cssText = `
1868
1869                padding: 20px;
1870
1871                max-height: calc(85vh - 140px);
1872
1873                overflow-y: auto;
1874
1875            `;
1876
1877
1878            // Индикатор изменения размера
1879
1880            const resizeHandle = document.createElement('div');
1881
1882            resizeHandle.id = 'ozon-ai-resize';
1883
1884            resizeHandle.style.cssText = `
1885
1886                position: absolute;
1887
1888                bottom: 0;
1889
1890                right: 0;
1891
1892                width: 20px;
1893
1894                height: 20px;
1895
1896                cursor: nwse-resize;
1897
1898                background: linear-gradient(135deg, transparent 0%, transparent 50%, #667eea 50%, #667eea 100%);
1899
1900                border-bottom-right-radius: 12px;
1901
1902            `;
1903
1904
1905            this.container.appendChild(header);
1906
1907            this.container.appendChild(startButton);
1908
1909            this.container.appendChild(content);
1910
1911            this.container.appendChild(resizeHandle);
1912
1913
1914            document.body.appendChild(this.container);
1915
1916
1917            // События для перетаскивания
1918
1919            header.addEventListener('mousedown', (e) => this.startDragging(e));
1920
1921            document.addEventListener('mousemove', (e) => this.drag(e));
1922
1923            document.addEventListener('mouseup', () => this.stopDragging());
1924
1925
1926            // События для изменения размера
1927
1928            resizeHandle.addEventListener('mousedown', (e) => this.startResizing(e));
1929
1930
1931            // События кнопок
1932
1933            document.getElementById('ozon-ai-close').addEventListener('click', () => {
1934
1935                this.container.style.display = 'none';
1936
1937            });
1938
1939
1940            document.getElementById('ozon-ai-collapse').addEventListener('click', () => {
1941
1942                this.toggleCollapse();
1943
1944            });
1945
1946
1947            document.getElementById('ozon-ai-start').addEventListener('click', () => {
1948
1949                this.startAnalysis();
1950
1951            });
1952
1953
1954            console.log('✅ UI создан');
1955
1956        }
1957
1958
1959        startDragging(e) {
1960
1961            if (e.target.closest('button')) return; // Не перетаскиваем при клике на кнопки
1962
1963            this.isDragging = true;
1964
1965            this.dragStartX = e.clientX;
1966
1967            this.dragStartY = e.clientY;
1968
1969            const rect = this.container.getBoundingClientRect();
1970
1971            this.containerStartX = rect.left;
1972
1973            this.containerStartY = rect.top;
1974
1975            this.container.style.transition = 'none';
1976
1977        }
1978
1979
1980        drag(e) {
1981
1982            if (this.isDragging) {
1983
1984                const deltaX = e.clientX - this.dragStartX;
1985
1986                const deltaY = e.clientY - this.dragStartY;
1987
1988                this.container.style.left = `${this.containerStartX + deltaX}px`;
1989
1990                this.container.style.top = `${this.containerStartY + deltaY}px`;
1991
1992                this.container.style.right = 'auto';
1993
1994            } else if (this.isResizing) {
1995
1996                const deltaX = e.clientX - this.dragStartX;
1997
1998                const deltaY = e.clientY - this.dragStartY;
1999
2000                const newWidth = Math.max(400, this.resizeStartWidth + deltaX);
2001
2002                const newHeight = Math.max(200, this.resizeStartHeight + deltaY);
2003
2004                this.container.style.width = `${newWidth}px`;
2005
2006                this.container.style.maxHeight = `${newHeight}px`;
2007
2008            }
2009
2010        }
2011
2012
2013        stopDragging() {
2014
2015            this.isDragging = false;
2016
2017            this.isResizing = false;
2018
2019            this.container.style.transition = '';
2020
2021        }
2022
2023
2024        startResizing(e) {
2025
2026            e.stopPropagation();
2027
2028            this.isResizing = true;
2029
2030            this.dragStartX = e.clientX;
2031
2032            this.dragStartY = e.clientY;
2033
2034            this.resizeStartWidth = this.container.offsetWidth;
2035
2036            this.resizeStartHeight = this.container.offsetHeight;
2037
2038        }
2039
2040
2041        toggleCollapse() {
2042
2043            this.isCollapsed = !this.isCollapsed;
2044
2045            const content = document.getElementById('ozon-ai-content');
2046
2047            const startButton = document.getElementById('ozon-ai-start');
2048
2049            const resizeHandle = document.getElementById('ozon-ai-resize');
2050
2051            const collapseButton = document.getElementById('ozon-ai-collapse');
2052
2053            
2054
2055            if (this.isCollapsed) {
2056
2057                content.style.display = 'none';
2058
2059                startButton.style.display = 'none';
2060
2061                resizeHandle.style.display = 'none';
2062
2063                collapseButton.textContent = '+';
2064
2065                this.container.style.maxHeight = 'auto';
2066
2067            } else {
2068
2069                content.style.display = 'block';
2070
2071                startButton.style.display = 'block';
2072
2073                resizeHandle.style.display = 'block';
2074
2075                collapseButton.textContent = '−';
2076
2077                this.container.style.maxHeight = '85vh';
2078
2079            }
2080
2081        }
2082
2083
2084        async startAnalysis() {
2085
2086            const content = document.getElementById('ozon-ai-content');
2087
2088            const startButton = document.getElementById('ozon-ai-start');
2089
2090            
2091
2092            startButton.disabled = true;
2093
2094            startButton.textContent = '⏳ Загрузка товаров...';
2095
2096
2097            try {
2098
2099                // Шаг 1: Загрузка всех товаров
2100
2101                const collector = new ProductDataCollector();
2102
2103                await collector.loadAllProducts();
2104
2105
2106                startButton.textContent = '📊 Сбор данных...';
2107
2108                
2109
2110                // Шаг 2: Сбор данных
2111
2112                const products = collector.collectProductData();
2113
2114
2115                if (products.length === 0) {
2116
2117                    content.innerHTML = '<p style="color: #e74c3c; padding: 20px; text-align: center; font-size: 14px;">❌ Не удалось найти товары. Убедитесь, что вы на странице аналитики.</p>';
2118
2119                    startButton.disabled = false;
2120
2121                    startButton.textContent = '🚀 Запустить анализ';
2122
2123                    return;
2124
2125                }
2126
2127
2128                // Шаг 3: AI анализ с прогрессом
2129
2130                const analyzer = new AIAnalyzer();
2131
2132                
2133
2134                const onProgress = (current, total, percentage, remaining) => {
2135
2136                    const remainingText = remaining > 0 ? ` (~${remaining} сек)` : '';
2137
2138                    startButton.textContent = `🤖 AI анализ: ${current}/${total} (${percentage}%)${remainingText}`;
2139
2140                };
2141
2142                
2143
2144                const analyzedProducts = await analyzer.analyzeProducts(products, onProgress);
2145
2146
2147                this.allProducts = analyzedProducts;
2148
2149                this.filteredProducts = analyzedProducts;
2150
2151
2152                // Вычисляем общую выручку и прибыль
2153
2154                const totalRevenue = analyzedProducts.reduce((sum, p) => sum + (p.revenue || 0), 0);
2155
2156                const totalProfit = analyzedProducts.reduce((sum, p) => sum + (p.profit || 0), 0);
2157
2158                const totalOrders = analyzedProducts.reduce((sum, p) => sum + (p.orders || 0), 0);
2159
2160                
2161
2162                // Вычисляем средний ДРР (взвешенный по выручке)
2163
2164                let totalDrrWeighted = 0;
2165
2166                let totalRevenueForDrr = 0;
2167
2168                analyzedProducts.forEach(p => {
2169
2170                    if (p.drr !== null && p.revenue) {
2171
2172                        totalDrrWeighted += p.drr * p.revenue;
2173
2174                        totalRevenueForDrr += p.revenue;
2175
2176                    }
2177
2178                });
2179
2180                const avgDrr = totalRevenueForDrr > 0 ? totalDrrWeighted / totalRevenueForDrr : 0;
2181
2182
2183                // Шаг 4: Отображение результатов
2184
2185                this.displayResults(analyzedProducts, {
2186
2187                    totalRevenue,
2188
2189                    totalProfit,
2190
2191                    totalOrders,
2192
2193                    avgDrr
2194
2195                });
2196
2197
2198                startButton.textContent = '✅ Анализ завершен';
2199
2200                startButton.disabled = false;
2201
2202
2203            } catch (error) {
2204
2205                console.error('Ошибка анализа:', error);
2206
2207                content.innerHTML = `<p style="color: #e74c3c; padding: 20px; text-align: center; font-size: 14px;">❌ Ошибка: ${error.message}</p>`;
2208
2209                startButton.disabled = false;
2210
2211                startButton.textContent = '🚀 Запустить анализ';
2212
2213            }
2214
2215        }
2216
2217
2218        displayResults(products, totals) {
2219
2220            const content = document.getElementById('ozon-ai-content');
2221
2222            
2223
2224            // Блок с общими показателями
2225
2226            const totalSalesBlock = this.createTotalSalesBlock(totals);
2227
2228            
2229
2230            // Фильтры
2231
2232            const filters = this.createFilters(products);
2233
2234            
2235
2236            // Список товаров
2237
2238            const productsList = this.createProductsList(products);
2239
2240            content.innerHTML = '';
2241
2242            content.appendChild(totalSalesBlock);
2243
2244            content.appendChild(filters);
2245
2246            content.appendChild(productsList);
2247
2248        }
2249
2250
2251        createTotalSalesBlock(totals) {
2252
2253            const block = document.createElement('div');
2254
2255            block.id = 'ozon-ai-total-sales';
2256
2257            block.style.cssText = `
2258
2259                margin-bottom: 20px;
2260
2261                padding: 16px;
2262
2263                background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
2264
2265                border-radius: 8px;
2266
2267            `;
2268
2269            
2270
2271            const profitColor = totals.totalProfit >= 0 ? '#27ae60' : '#e74c3c';
2272
2273            const profitPercent = totals.totalRevenue > 0 ? ((totals.totalProfit / totals.totalRevenue) * 100).toFixed(1) : 0;
2274
2275            const profitPercentColor = profitPercent >= 25 ? '#27ae60' : '#e74c3c';
2276
2277            
2278
2279            block.innerHTML = `
2280
2281                <div style="font-size: 14px; font-weight: 600; color: #2c3e50; margin-bottom: 12px;">📊 Общие показатели</div>
2282
2283                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px;">
2284
2285                    <div style="background: white; padding: 10px; border-radius: 6px;">
2286
2287                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Общая выручка</div>
2288
2289                        <div style="font-weight: 600; color: #2c3e50;">${totals.totalRevenue.toLocaleString()}</div>
2290
2291                    </div>
2292
2293                    <div style="background: white; padding: 10px; border-radius: 6px;">
2294
2295                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Общая прибыль</div>
2296
2297                        <div style="font-weight: 600; color: ${profitColor};">${totals.totalProfit.toLocaleString()}<span style="color: ${profitPercentColor};">(${profitPercent}%)</span></div>
2298
2299                    </div>
2300
2301                    <div style="background: white; padding: 10px; border-radius: 6px;">
2302
2303                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Всего заказов</div>
2304
2305                        <div style="font-weight: 600; color: #2c3e50;">${totals.totalOrders.toLocaleString()}</div>
2306
2307                    </div>
2308
2309                    <div style="background: white; padding: 10px; border-radius: 6px;">
2310
2311                        <div style="color: #7f8c8d; font-size: 11px; margin-bottom: 4px;">Средний ДРР</div>
2312
2313                        <div style="font-weight: 600; color: #2c3e50;">${totals.avgDrr.toFixed(1)}%</div>
2314
2315                    </div>
2316
2317                </div>
2318
2319            `;
2320
2321            
2322
2323            return block;
2324
2325        }
2326
2327
2328        // Фильтры
2329
2330        createFilters(products) {
2331
2332            const filtersContainer = document.createElement('div');
2333
2334            filtersContainer.style.cssText = `
2335
2336                margin-bottom: 20px;
2337
2338            `;
2339
2340
2341            // Поле поиска
2342
2343            const searchContainer = document.createElement('div');
2344
2345            searchContainer.style.cssText = `
2346
2347                margin-bottom: 12px;
2348
2349            `;
2350
2351            
2352
2353            const searchInput = document.createElement('input');
2354
2355            searchInput.type = 'text';
2356
2357            searchInput.placeholder = '🔍 Поиск по названию или артикулу...';
2358
2359            searchInput.id = 'ozon-ai-search';
2360
2361            searchInput.style.cssText = `
2362
2363                width: 100%;
2364
2365                padding: 10px 12px;
2366
2367                border: 2px solid #ecf0f1;
2368
2369                border-radius: 6px;
2370
2371                font-size: 14px;
2372
2373                font-family: inherit;
2374
2375                outline: none;
2376
2377                transition: border-color 0.2s;
2378
2379            `;
2380
2381            
2382
2383            searchInput.addEventListener('focus', () => {
2384
2385                searchInput.style.borderColor = '#667eea';
2386
2387            });
2388
2389            
2390
2391            searchInput.addEventListener('blur', () => {
2392
2393                searchInput.style.borderColor = '#ecf0f1';
2394
2395            });
2396
2397            
2398
2399            searchInput.addEventListener('input', (e) => {
2400
2401                this.applySearch(e.target.value);
2402
2403            });
2404
2405            
2406
2407            searchContainer.appendChild(searchInput);
2408
2409            filtersContainer.appendChild(searchContainer);
2410
2411
2412            // Кнопки фильтров
2413
2414            const buttonsContainer = document.createElement('div');
2415
2416            buttonsContainer.style.cssText = `
2417
2418                display: flex;
2419
2420                flex-wrap: wrap;
2421
2422                gap: 8px;
2423
2424            `;
2425
2426
2427            // Подсчет товаров по категориям
2428
2429            const critical = products.filter(p => p.analysis.priority === 'critical').length;
2430
2431            const high = products.filter(p => p.analysis.priority === 'high').length;
2432
2433            const lowStock = products.filter(p => p.analysis.isLowStock).length;
2434
2435            const highDRR = products.filter(p => p.analysis.isHighDRR).length;
2436
2437            const lowDRR = products.filter(p => p.analysis.isLowDRR).length;
2438
2439            const growth = products.filter(p => p.analysis.isGrowth).length;
2440
2441            const lowImpressions = products.filter(p => p.analysis.isLowImpressions).length;
2442
2443            const lowCR = products.filter(p => p.analysis.isLowCR).length;
2444
2445            const lowProfit = products.filter(p => p.analysis.isLowProfit).length;
2446
2447            const badDeliveryTime = products.filter(p => p.analysis.isBadDeliveryTime).length;
2448
2449
2450            const filterButtons = [
2451
2452                { id: 'all', label: `Все (${products.length})`, color: '#95a5a6' },
2453
2454                { id: 'critical', label: `🔴 Критичные (${critical})`, color: '#e74c3c' },
2455
2456                { id: 'high', label: `🟠 Высокий (${high})`, color: '#f39c12' },
2457
2458                { id: 'lowStock', label: `📦 Низкие остатки (${lowStock})`, color: '#e67e22' },
2459
2460                { id: 'highDRR', label: `💰 Высокий ДРР (${highDRR})`, color: '#c0392b' },
2461
2462                { id: 'lowDRR', label: `📊 Повысить ДРР (${lowDRR})`, color: '#16a085' },
2463
2464                { id: 'lowImpressions', label: `📉 Упали показы (${lowImpressions})`, color: '#9b59b6' },
2465
2466                { id: 'lowCR', label: `📊 Упал CR (${lowCR})`, color: '#e91e63' },
2467
2468                { id: 'lowProfit', label: `💸 Низкая прибыль (${lowProfit})`, color: '#d32f2f' },
2469
2470                { id: 'badDeliveryTime', label: `⏱️ Плохое время (${badDeliveryTime})`, color: '#8e44ad' },
2471
2472                { id: 'growth', label: `📈 Рост (${growth})`, color: '#27ae60' }
2473
2474            ];
2475
2476
2477            filterButtons.forEach(filter => {
2478
2479                const btn = document.createElement('button');
2480
2481                btn.textContent = filter.label;
2482
2483                btn.style.cssText = `
2484
2485                    padding: 8px 12px;
2486
2487                    background: ${this.currentFilter === filter.id ? filter.color : '#ecf0f1'};
2488
2489                    color: ${this.currentFilter === filter.id ? 'white' : '#2c3e50'};
2490
2491                    border: none;
2492
2493                    border-radius: 6px;
2494
2495                    font-size: 13px;
2496
2497                    font-weight: 500;
2498
2499                    cursor: pointer;
2500
2501                    transition: all 0.2s;
2502
2503                `;
2504
2505                
2506
2507                btn.addEventListener('click', () => {
2508
2509                    this.currentFilter = filter.id;
2510
2511                    this.applyFilter(filter.id);
2512
2513                });
2514
2515
2516                buttonsContainer.appendChild(btn);
2517
2518            });
2519
2520
2521            filtersContainer.appendChild(buttonsContainer);
2522
2523            return filtersContainer;
2524
2525        }
2526
2527
2528        applySearch(searchTerm) {
2529
2530            const term = searchTerm.toLowerCase().trim();
2531
2532            
2533
2534            console.log(`🔍 Поиск по запросу: "${term}"`);
2535
2536            
2537
2538            if (!term) {
2539
2540                // Если поиск пустой, применяем текущий фильтр
2541
2542                this.applyFilter(this.currentFilter);
2543
2544                return;
2545
2546            }
2547
2548            
2549
2550            // Фильтруем по поисковому запросу
2551
2552            const filtered = this.allProducts.filter(p => {
2553
2554                const nameMatch = p.name.toLowerCase().includes(term);
2555
2556                const articleMatch = p.article.includes(term);
2557
2558                return nameMatch || articleMatch;
2559
2560            });
2561
2562            
2563
2564            console.log(`✅ Найдено товаров: ${filtered.length}`);
2565
2566            
2567
2568            // Сортируем по выручке
2569
2570            filtered.sort((a, b) => {
2571
2572                const revenueA = a.revenue || 0;
2573
2574                const revenueB = b.revenue || 0;
2575
2576                return revenueB - revenueA;
2577
2578            });
2579
2580
2581            this.filteredProducts = filtered;
2582
2583            
2584
2585            // Обновляем только список товаров, не трогая фильтры
2586
2587            const content = document.getElementById('ozon-ai-content');
2588
2589            const productsList = this.createProductsList(filtered);
2590
2591            
2592
2593            // Находим и удаляем только список товаров (второй div в content)
2594
2595            const children = content.children;
2596
2597            if (children.length > 2) {
2598
2599                children[2].remove();
2600
2601            }
2602
2603            
2604
2605            content.appendChild(productsList);
2606
2607        }
2608
2609
2610        applyFilter(filterId) {
2611
2612            let filtered = this.allProducts;
2613
2614            
2615
2616            console.log(`🔍 Применяем фильтр: ${filterId}`);
2617
2618            
2619
2620            // Очищаем поле поиска при смене фильтра
2621
2622            const searchInput = document.getElementById('ozon-ai-search');
2623
2624            if (searchInput) {
2625
2626                searchInput.value = '';
2627
2628            }
2629
2630
2631            switch(filterId) {
2632
2633            case 'critical':
2634
2635                filtered = this.allProducts.filter(p => p.analysis.priority === 'critical');
2636
2637                break;
2638
2639            case 'high':
2640
2641                filtered = this.allProducts.filter(p => p.analysis.priority === 'high');
2642
2643                break;
2644
2645            case 'lowStock':
2646
2647                filtered = this.allProducts.filter(p => p.analysis.isLowStock);
2648
2649                break;
2650
2651            case 'highDRR':
2652
2653                filtered = this.allProducts.filter(p => p.analysis.isHighDRR);
2654
2655                break;
2656
2657            case 'lowDRR':
2658
2659                filtered = this.allProducts.filter(p => p.analysis.isLowDRR);
2660
2661                break;
2662
2663            case 'lowImpressions':
2664
2665                filtered = this.allProducts.filter(p => p.analysis.isLowImpressions);
2666
2667                console.log('📊 Товары с упавшими показами:', filtered.map(p => `${p.article} (${p.impressionsChange}%)`));
2668
2669                break;
2670
2671            case 'lowCR':
2672
2673                filtered = this.allProducts.filter(p => p.analysis.isLowCR);
2674
2675                break;
2676
2677            case 'lowProfit':
2678
2679                filtered = this.allProducts.filter(p => p.analysis.isLowProfit);
2680
2681                break;
2682
2683            case 'badDeliveryTime':
2684
2685                filtered = this.allProducts.filter(p => p.analysis.isBadDeliveryTime);
2686
2687                break;
2688
2689            case 'growth':
2690
2691                filtered = this.allProducts.filter(p => p.analysis.isGrowth);
2692
2693                break;
2694
2695            }
2696
2697
2698            console.log(`✅ Найдено товаров: ${filtered.length}`);
2699
2700
2701            // Сортируем по выручке (от большей к меньшей)
2702
2703            filtered.sort((a, b) => {
2704
2705                const revenueA = a.revenue || 0;
2706
2707                const revenueB = b.revenue || 0;
2708
2709                return revenueB - revenueA;
2710
2711            });
2712
2713
2714            this.filteredProducts = filtered;
2715
2716            
2717
2718            // Обновляем только список товаров и кнопки фильтров
2719
2720            const content = document.getElementById('ozon-ai-content');
2721
2722            const productsList = this.createProductsList(filtered);
2723
2724            
2725
2726            // Находим и удаляем только список товаров (третий элемент в content)
2727
2728            const children = content.children;
2729
2730            if (children.length > 2) {
2731
2732                children[2].remove();
2733
2734            }
2735
2736            
2737
2738            content.appendChild(productsList);
2739
2740            
2741
2742            // Обновляем стили кнопок фильтров
2743
2744            this.updateFilterButtons(filterId);
2745
2746        }
2747
2748
2749        updateFilterButtons(activeFilterId) {
2750
2751            const content = document.getElementById('ozon-ai-content');
2752
2753            const filtersContainer = content.children[1]; // Второй элемент - контейнер с фильтрами
2754
2755            const buttonsContainer = filtersContainer.children[1]; // Исправлено: второй элемент (после searchContainer) - контейнер с кнопками
2756
2757            
2758
2759            const buttons = buttonsContainer.querySelectorAll('button');
2760
2761            
2762
2763            const filterButtons = [
2764
2765                { id: 'all', color: '#95a5a6' },
2766
2767                { id: 'critical', color: '#e74c3c' },
2768
2769                { id: 'high', color: '#f39c12' },
2770
2771                { id: 'lowStock', color: '#e67e22' },
2772
2773                { id: 'highDRR', color: '#c0392b' },
2774
2775                { id: 'lowDRR', color: '#16a085' },
2776
2777                { id: 'lowImpressions', color: '#9b59b6' },
2778
2779                { id: 'lowCR', color: '#e91e63' },
2780
2781                { id: 'lowProfit', color: '#d32f2f' },
2782
2783                { id: 'badDeliveryTime', color: '#8e44ad' },
2784
2785                { id: 'growth', color: '#27ae60' }
2786
2787            ];
2788
2789            
2790
2791            buttons.forEach((btn, index) => {
2792
2793                const filter = filterButtons[index];
2794
2795                if (filter && filter.id === activeFilterId) {
2796
2797                    btn.style.background = filter.color;
2798
2799                    btn.style.color = 'white';
2800
2801                } else {
2802
2803                    btn.style.background = '#ecf0f1';
2804
2805                    btn.style.color = '#2c3e50';
2806
2807                }
2808
2809            });
2810
2811        }
2812
2813
2814        createProductsList(products) {
2815
2816            const list = document.createElement('div');
2817
2818            list.style.cssText = `
2819
2820                display: flex;
2821
2822                flex-direction: column;
2823
2824                gap: 12px;
2825
2826            `;
2827
2828
2829            products.forEach(product => {
2830
2831                const card = this.createProductCard(product);
2832
2833                list.appendChild(card);
2834
2835            });
2836
2837
2838            return list;
2839
2840        }
2841
2842
2843        formatMetric(value, change, isPercent = false) {
2844
2845            // Округляем проценты до десятых
2846
2847            let displayValue = value;
2848
2849            if (isPercent && value !== null && value !== undefined) {
2850
2851                displayValue = parseFloat(value.toFixed(1));
2852
2853            }
2854
2855            
2856
2857            const valueStr = displayValue !== null && displayValue !== undefined ? 
2858
2859                (isPercent ? `${displayValue}%` : displayValue.toLocaleString()) : '—';
2860
2861            
2862
2863            if (change === null || change === undefined) return valueStr;
2864
2865            
2866
2867            // Округляем изменение до десятых
2868
2869            const roundedChange = parseFloat(change.toFixed(1));
2870
2871            const changeStr = roundedChange > 0 ? `+${roundedChange}%` : `${roundedChange}%`;
2872
2873            const color = roundedChange > 0 ? '#27ae60' : '#e74c3c';
2874
2875            
2876
2877            return `${valueStr} <span style="color: ${color}; font-size: 11px;">(${changeStr})</span>`;
2878
2879        }
2880
2881
2882        createProductCard(product) {
2883
2884            const card = document.createElement('div');
2885
2886            
2887
2888            const priorityColors = {
2889
2890                critical: '#e74c3c',
2891
2892                high: '#f39c12',
2893
2894                medium: '#3498db',
2895
2896                low: '#95a5a6'
2897
2898            };
2899
2900
2901            const priorityLabels = {
2902
2903                critical: '🔴 Критичный',
2904
2905                high: '🟠 Высокий',
2906
2907                medium: '🟡 Средний',
2908
2909                low: '🟢 Низкий'
2910
2911            };
2912
2913
2914            // Определяем цвет прибыли
2915
2916            const profitColor = product.profitPercent !== null && product.profitPercent >= 25 ? '#27ae60' : '#e74c3c';
2917
2918
2919            card.style.cssText = `
2920
2921                background: white;
2922
2923                border: 2px solid ${priorityColors[product.analysis.priority]};
2924
2925                border-radius: 8px;
2926
2927                padding: 14px;
2928
2929                cursor: pointer;
2930
2931                transition: all 0.2s;
2932
2933            `;
2934
2935
2936            card.innerHTML = `
2937
2938                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
2939
2940                    <div style="flex: 1;">
2941
2942                        <div style="font-weight: 600; font-size: 14px; color: #2c3e50; margin-bottom: 4px;">${product.name}</div>
2943
2944                        <div class="article-copy" style="font-size: 12px; color: #7f8c8d; cursor: pointer; user-select: none;" title="Нажмите, чтобы скопировать артикул">Арт. ${product.article}</div>
2945
2946                    </div>
2947
2948                    <div style="background: ${priorityColors[product.analysis.priority]}; color: white; padding: 5px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; white-space: nowrap;">
2949
2950                        ${priorityLabels[product.analysis.priority]}
2951
2952                    </div>
2953
2954                </div>
2955
2956
2957                ${product.analysis.problems.length > 0 ? `
2958
2959                <div style="background: #fff3cd; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 500; color: #856404;">
2960
2961                    ${product.analysis.problems.slice(0, 2).map(p => `
2962
2963                        <div style="margin-bottom: 4px;">⚠️ ${p.description}</div>
2964
2965                    `).join('')}
2966
2967                </div>
2968
2969                ` : ''}
2970
2971                
2972                <!-- БЛОК 1: Выручка / ДРР / Прибыль / Прибыль % -->
2973                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 8px;">
2974
2975                    <div style="font-size: 11px; font-weight: 600; color: #2c3e50; margin-bottom: 6px;">💰 Финансы</div>
2976
2977                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 11px;">
2978
2979                        <div><strong>Выручка:</strong> ${this.formatMetric(product.revenue, product.revenueChange)}</div>
2980
2981                        <div><strong>ДРР:</strong> ${this.formatMetric(product.drr, product.drrChange, true)}</div>
2982
2983                        <div><strong>Прибыль:</strong> <span style="color: ${profitColor}; font-weight: 600;">${product.profit !== null ? `${product.profit.toLocaleString()}` : '—'}</span></div>
2984
2985                        <div><strong>Прибыль %:</strong> <span style="color: ${profitColor}; font-weight: 600;">${product.profitPercent !== null ? `${product.profitPercent}%` : '—'}</span></div>
2986
2987                    </div>
2988
2989                </div>
2990
2991                
2992                <!-- БЛОК 2: Показы / Посещения карточки / CTR -->
2993                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 8px;">
2994
2995                    <div style="font-size: 11px; font-weight: 600; color: #2c3e50; margin-bottom: 6px;">👁️ Трафик</div>
2996
2997                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; font-size: 11px;">
2998
2999                        <div><strong>Показы:</strong> ${this.formatMetric(product.impressions, product.impressionsChange)}</div>
3000
3001                        <div><strong>Карточка:</strong> ${this.formatMetric(product.cardVisits, product.cardVisitsChange)}</div>
3002
3003                        <div><strong>CTR:</strong> ${this.formatMetric(product.conversionCatalogToCard, product.conversionCatalogToCardChange, true)}</div>
3004
3005                    </div>
3006
3007                </div>
3008
3009                
3010                <!-- БЛОК 3: Добавления в корзину / CRL / Заказы / CR -->
3011                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 8px;">
3012
3013                    <div style="font-size: 11px; font-weight: 600; color: #2c3e50; margin-bottom: 6px;">🛒 Конверсия</div>
3014
3015                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 11px;">
3016
3017                        <div><strong>Корзины:</strong> ${this.formatMetric(product.cartAdditions, product.cartAdditionsChange)}</div>
3018
3019                        <div><strong>CRL:</strong> ${this.formatMetric(product.conversionCardToCart, product.conversionCardToCartChange, true)}</div>
3020
3021                        <div><strong>Заказы:</strong> ${this.formatMetric(product.orders, product.ordersChange)}</div>
3022
3023                        <div><strong>CR:</strong> ${this.formatMetric(product.cr, product.crChange, true)}</div>
3024
3025                    </div>
3026
3027                </div>
3028
3029                
3030                <!-- БЛОК 4: Остаток / На дней / Время доставки / Цена -->
3031                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px;">
3032
3033                    <div style="font-size: 11px; font-weight: 600; color: #2c3e50; margin-bottom: 6px;">📦 Логистика и цена</div>
3034
3035                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 11px;">
3036
3037                        <div><strong>Остаток:</strong> ${product.stock || '—'} шт</div>
3038
3039                        <div><strong>На дней:</strong> ${product.daysOfStock || '—'}</div>
3040
3041                        <div><strong>Доставка:</strong> ${product.deliveryTime || '—'}</div>
3042
3043                        <div><strong>Цена:</strong> ${this.formatMetric(product.avgPrice, product.avgPriceChange)}</div>
3044
3045                    </div>
3046
3047                </div>
3048
3049
3050                <div style="font-size: 11px; color: #7f8c8d; border-top: 1px solid #ecf0f1; padding-top: 8px;">
3051
3052                    <strong>Рекомендации:</strong>
3053
3054                    ${product.analysis.recommendations.slice(0, 1).map(r => `<div>• ${r}</div>`).join('')}
3055
3056                </div>
3057
3058            `;
3059
3060
3061            // Обработчик копирования артикула
3062
3063            const articleElement = card.querySelector('.article-copy');
3064
3065            articleElement.addEventListener('click', async (e) => {
3066
3067                e.stopPropagation();
3068
3069                try {
3070
3071                    await GM.setClipboard(product.article);
3072
3073                    const originalText = articleElement.textContent;
3074
3075                    articleElement.textContent = '✓ Скопировано!';
3076
3077                    articleElement.style.color = '#27ae60';
3078
3079                    setTimeout(() => {
3080
3081                        articleElement.textContent = originalText;
3082
3083                        articleElement.style.color = '#7f8c8d';
3084
3085                    }, 1500);
3086
3087                } catch (error) {
3088
3089                    console.error('Ошибка копирования:', error);
3090
3091                }
3092
3093            });
3094
3095
3096            card.addEventListener('click', () => {
3097
3098                this.showProductDetails(product);
3099
3100            });
3101
3102
3103            return card;
3104
3105        }
3106
3107
3108        showProductDetails(product) {
3109
3110            // Определяем цвет прибыли
3111
3112            const profitColor = product.profitPercent !== null && product.profitPercent >= 25 ? '#27ae60' : '#e74c3c';
3113
3114
3115            // Создаем модальное окно с детальной информацией
3116
3117            const modal = document.createElement('div');
3118
3119            modal.style.cssText = `
3120
3121                position: fixed;
3122
3123                top: 0;
3124
3125                left: 0;
3126
3127                right: 0;
3128
3129                bottom: 0;
3130
3131                background: rgba(0,0,0,0.7);
3132
3133                z-index: 10001;
3134
3135                display: flex;
3136
3137                align-items: center;
3138
3139                justify-content: center;
3140
3141                padding: 20px;
3142
3143            `;
3144
3145
3146            const modalContent = document.createElement('div');
3147
3148            modalContent.style.cssText = `
3149
3150                background: white;
3151
3152                border-radius: 12px;
3153
3154                padding: 24px;
3155
3156                max-width: 700px;
3157
3158                max-height: 80vh;
3159
3160                overflow-y: auto;
3161
3162                width: 100%;
3163
3164            `;
3165
3166
3167            modalContent.innerHTML = `
3168
3169                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
3170
3171                    <h2 style="margin: 0; font-size: 18px; color: #2c3e50;">${product.name}</h2>
3172
3173                    <button id="close-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #95a5a6;">×</button>
3174
3175                </div>
3176
3177
3178                <div style="font-size: 12px; color: #7f8c8d; margin-bottom: 16px;">Артикул: ${product.article}</div>
3179
3180                
3181                <!-- БЛОК 1: Выручка / ДРР / Прибыль / Прибыль % -->
3182                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 600; color: #2c3e50;">
3183
3184                    💰 Финансы
3185
3186                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px;">
3187
3188                        <div><strong>Выручка:</strong> ${this.formatMetric(product.revenue, product.revenueChange)}</div>
3189
3190                        <div><strong>ДРР:</strong> ${this.formatMetric(product.drr, product.drrChange, true)}</div>
3191
3192                        <div><strong>Прибыль:</strong> <span style="color: ${profitColor}; font-weight: 600;">${product.profit !== null ? `${product.profit.toLocaleString()}` : '—'}</span></div>
3193
3194                        <div><strong>Прибыль %:</strong> <span style="color: ${profitColor}; font-weight: 600;">${product.profitPercent !== null ? `${product.profitPercent}%` : '—'}</span></div>
3195
3196                    </div>
3197
3198                </div>
3199
3200                
3201                <!-- БЛОК 2: Показы / Посещения карточки / CTR -->
3202                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 600; color: #2c3e50;">
3203
3204                    👁️ Трафик
3205
3206                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; font-size: 13px;">
3207
3208                        <div><strong>Показы:</strong> ${this.formatMetric(product.impressions, product.impressionsChange)}</div>
3209
3210                        <div><strong>Карточка:</strong> ${this.formatMetric(product.cardVisits, product.cardVisitsChange)}</div>
3211
3212                        <div><strong>CTR:</strong> ${this.formatMetric(product.conversionCatalogToCard, product.conversionCatalogToCardChange, true)}</div>
3213
3214                    </div>
3215
3216                </div>
3217
3218                
3219                <!-- БЛОК 3: Добавления в корзину / CRL / Заказы / CR -->
3220                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 600; color: #2c3e50;">
3221
3222                    🛒 Конверсия
3223
3224                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px;">
3225
3226                        <div><strong>Корзины:</strong> ${this.formatMetric(product.cartAdditions, product.cartAdditionsChange)}</div>
3227
3228                        <div><strong>CRL:</strong> ${this.formatMetric(product.conversionCardToCart, product.conversionCardToCartChange, true)}</div>
3229
3230                        <div><strong>Заказы:</strong> ${this.formatMetric(product.orders, product.ordersChange)}</div>
3231
3232                        <div><strong>CR:</strong> ${this.formatMetric(product.cr, product.crChange, true)}</div>
3233
3234                    </div>
3235
3236                </div>
3237
3238                
3239                <!-- БЛОК 4: Остаток / На дней / Время доставки / Цена -->
3240                <div style="background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 10px; font-size: 13px; font-weight: 600; color: #2c3e50;">
3241
3242                    📦 Логистика и цена
3243
3244                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px;">
3245
3246                        <div><strong>Остаток:</strong> ${product.stock || '—'} шт</div>
3247
3248                        <div><strong>На дней:</strong> ${product.daysOfStock || '—'}</div>
3249
3250                        <div><strong>Доставка:</strong> ${product.deliveryTime || '—'}</div>
3251
3252                        <div><strong>Цена:</strong> ${this.formatMetric(product.avgPrice, product.avgPriceChange)}</div>
3253
3254                    </div>
3255
3256                </div>
3257
3258
3259                <div style="margin-bottom: 16px;">
3260
3261                    <h3 style="font-size: 14px; color: #2c3e50; margin-bottom: 8px;">🔍 Выявленные проблемы</h3>
3262
3263                    ${product.analysis.problems.length > 0 ? product.analysis.problems.map(p => `
3264
3265                        <div style="background: #fff3cd; padding: 10px; border-radius: 6px; margin-bottom: 8px; font-size: 12px;">
3266
3267                            <strong>${p.type}:</strong> ${p.description}
3268
3269                        </div>
3270
3271                    `).join('') : '<div style="color: #27ae60; font-size: 12px;">✅ Проблем не выявлено</div>'}
3272
3273                </div>
3274
3275
3276                <div>
3277
3278                    <h3 style="font-size: 14px; color: #2c3e50; margin-bottom: 8px;">💡 Рекомендации</h3>
3279
3280                    ${product.analysis.recommendations.map(r => `
3281
3282                        <div style="background: #d4edda; padding: 10px; border-radius: 6px; margin-bottom: 8px; font-size: 12px;">
3283
3284                            ${r}
3285
3286                        </div>
3287
3288                    `).join('')}
3289
3290                </div>
3291
3292
3293                <button id="filter-by-article" style="
3294
3295                    width: 100%;
3296
3297                    margin-top: 16px;
3298
3299                    padding: 12px;
3300
3301                    background: #667eea;
3302
3303                    color: white;
3304
3305                    border: none;
3306
3307                    border-radius: 8px;
3308
3309                    font-size: 14px;
3310
3311                    font-weight: 600;
3312
3313                    cursor: pointer;
3314
3315                ">
3316
3317                    🔍 Показать только этот товар в таблице
3318
3319                </button>
3320
3321            `;
3322
3323
3324            modal.appendChild(modalContent);
3325
3326            document.body.appendChild(modal);
3327
3328
3329            // Закрытие модального окна
3330
3331            modal.addEventListener('click', (e) => {
3332
3333                if (e.target === modal) {
3334
3335                    modal.remove();
3336
3337                }
3338
3339            });
3340
3341
3342            modalContent.querySelector('#close-modal').addEventListener('click', () => {
3343
3344                modal.remove();
3345
3346            });
3347
3348
3349            // Фильтрация по артикулу
3350
3351            modalContent.querySelector('#filter-by-article').addEventListener('click', () => {
3352
3353                this.filterByArticle(product.article);
3354
3355                modal.remove();
3356
3357            });
3358
3359        }
3360
3361
3362        filterByArticle(article) {
3363
3364            console.log(`🔍 Фильтруем по артикулу: ${article}`);
3365
3366            
3367
3368            // Находим поле фильтра по артикулу на странице
3369
3370            const articleInput = document.querySelector('input[placeholder*="артикул"], input[name*="article"]');
3371
3372            
3373
3374            if (articleInput) {
3375
3376                articleInput.value = article;
3377
3378                articleInput.dispatchEvent(new Event('input', { bubbles: true }));
3379
3380                articleInput.dispatchEvent(new Event('change', { bubbles: true }));
3381
3382                
3383
3384                // Ищем кнопку "Применить" - ищем все кнопки и проверяем текст
3385
3386                const buttons = document.querySelectorAll('button[type="submit"]');
3387
3388                let applyButton = null;
3389
3390                for (const btn of buttons) {
3391
3392                    if (btn.textContent.includes('Применить')) {
3393
3394                        applyButton = btn;
3395
3396                        break;
3397
3398                    }
3399
3400                }
3401
3402                
3403
3404                if (applyButton) {
3405
3406                    setTimeout(() => applyButton.click(), 300);
3407
3408                }
3409
3410            } else {
3411
3412                console.warn('Не найдено поле для ввода артикула');
3413
3414            }
3415
3416        }
3417
3418    }
3419
3420
3421    // Инициализация
3422
3423    async function init() {
3424
3425        console.log('🎯 Инициализация AI Аналитика Продаж...');
3426
3427
3428        // Проверяем, что мы на странице аналитики
3429
3430        if (!window.location.href.includes('seller.ozon.ru/app/analytics')) {
3431
3432            console.log('⚠️ Не на странице аналитики, ожидаем...');
3433
3434            return;
3435
3436        }
3437
3438
3439        // Ждем загрузки таблицы
3440
3441        const waitForTable = setInterval(() => {
3442
3443            const table = document.querySelector('table.ct590-a');
3444
3445            if (table) {
3446
3447                clearInterval(waitForTable);
3448
3449                console.log('✅ Таблица найдена, создаем UI');
3450                
3451                const ui = new AnalyticsUI();
3452
3453                ui.createUI();
3454
3455            }
3456
3457        }, 1000);
3458
3459    }
3460
3461
3462    // Запуск при загрузке страницы
3463
3464    if (document.readyState === 'loading') {
3465
3466        document.addEventListener('DOMContentLoaded', init);
3467
3468    } else {
3469
3470        init();
3471
3472    }
3473
3474
3475    // Отслеживание изменений URL (для SPA)
3476
3477    let lastUrl = location.href;
3478
3479    new MutationObserver(() => {
3480
3481        const url = location.href;
3482
3483        if (url !== lastUrl) {
3484
3485            lastUrl = url;
3486
3487            init();
3488
3489        }
3490
3491    }).observe(document, { subtree: true, childList: true });
3492
3493
3494})();
Ozon AI Analyzer 2.0 | Robomonkey