Ozon AI Analyzer 5.0

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

Size

133.1 KB

Version

1.1.66

Created

Dec 10, 2025

Updated

about 1 month ago

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