Ozon AI Analyzer 5.0

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

Size

178.2 KB

Version

1.1.99

Created

Jan 4, 2026

Updated

17 days ago

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