Ozon AI Analyzer 5.0

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

Size

137.3 KB

Version

1.1.59

Created

Dec 8, 2025

Updated

about 1 month ago

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