Ozon AI Аналитик Продаж

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

Size

70.8 KB

Version

1.1.14

Created

Dec 5, 2025

Updated

7 days ago

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