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