Ozon AI Analyzer 5.0

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

Size

128.9 KB

Version

1.1.59

Created

Dec 8, 2025

Updated

4 days ago

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