Analyzes competitor prices from search queries and suggests optimal pricing using Bayesian methods and AI to maximize profit
Size
137.1 KB
Version
2.1.46
Created
Dec 17, 2025
Updated
about 2 months ago
1// ==UserScript==
2// @name OZON Price Optimizer with Competitor Analysis
3// @description Analyzes competitor prices from search queries and suggests optimal pricing using Bayesian methods and AI to maximize profit
4// @version 2.1.46
5// @match https://*.ozon.ru/*
6// @icon https://st.ozone.ru/assets/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// @require https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js
11// ==/UserScript==
12(function() {
13 'use strict';
14
15 console.log('🚀🚀🚀 OZON PRICE OPTIMIZER VERSION 2.0.1 LOADED 🚀🚀🚀');
16
17 // Product data mapping (артикул -> себестоимость, комиссия, доставка)
18 const PRODUCT_DATA = {
19'1740824669': { cost: 146.4, commission: 0.39, delivery: 105 },
20 '1157786755': { cost: 159.6, commission: 0.39, delivery: 105 },
21 '1126896590': { cost: 111.6, commission: 0.39, delivery: 105 },
22 '1643994739': { cost: 242.4, commission: 0.39, delivery: 105 },
23 '1672597444': { cost: 130.8, commission: 0.39, delivery: 105 },
24 '1220422423': { cost: 115.2, commission: 0.39, delivery: 105 },
25 '1791969680': { cost: 258, commission: 0.39, delivery: 105 },
26 '1134301258': { cost: 117.6, commission: 0.39, delivery: 105 },
27 '881812095': { cost: 219.6, commission: 0.39, delivery: 105 },
28 '476573272': { cost: 198, commission: 0.39, delivery: 105 },
29 '1091687376': { cost: 133.2, commission: 0.39, delivery: 105 },
30 '1865971423': { cost: 213.6, commission: 0.39, delivery: 105 },
31 '1091884893': { cost: 115.2, commission: 0.39, delivery: 105 },
32 '1509173668': { cost: 146.4, commission: 0.39, delivery: 105 },
33 '1643957952': { cost: 154.8, commission: 0.39, delivery: 105 },
34 '1615018099': { cost: 216, commission: 0.39, delivery: 105 },
35 '1149665681': { cost: 92.4, commission: 0.39, delivery: 105 },
36 '1042512806': { cost: 118.8, commission: 0.39, delivery: 105 },
37 '1586302587': { cost: 200.4, commission: 0.39, delivery: 105 },
38 '1915136286': { cost: 199.2, commission: 0.39, delivery: 105 },
39 '1061692407': { cost: 188.4, commission: 0.39, delivery: 105 },
40 '907545404': { cost: 199.2, commission: 0.39, delivery: 105 },
41 '701748607': { cost: 124.8, commission: 0.39, delivery: 105 },
42 '1464498458': { cost: 147.6, commission: 0.39, delivery: 105 },
43 '1423324644': { cost: 117.6, commission: 0.39, delivery: 105 },
44 '847234813': { cost: 146.4, commission: 0.39, delivery: 105 },
45 '1644305207': { cost: 236.4, commission: 0.39, delivery: 105 },
46 '1220412279': { cost: 110.4, commission: 0.39, delivery: 105 },
47 '1440280204': { cost: 172.8, commission: 0.39, delivery: 105 },
48 '881841274': { cost: 182.4, commission: 0.39, delivery: 105 },
49 '881911476': { cost: 162, commission: 0.39, delivery: 105 },
50 '1558183265': { cost: 151.2, commission: 0.39, delivery: 105 },
51 '442408015': { cost: 213.6, commission: 0.39, delivery: 105 },
52 '1323322968': { cost: 175.2, commission: 0.39, delivery: 105 },
53 '524540150': { cost: 138, commission: 0.39, delivery: 105 },
54 '1648863445': { cost: 172.8, commission: 0.39, delivery: 105 },
55 '1646939718': { cost: 157.2, commission: 0.39, delivery: 105 },
56 '1205194839': { cost: 153.6, commission: 0.39, delivery: 105 },
57 '645226241': { cost: 158.4, commission: 0.39, delivery: 105 },
58 '2007243137': { cost: 291.6, commission: 0.39, delivery: 105 },
59 '1421135622': { cost: 91.2, commission: 0.39, delivery: 105 },
60 '701884874': { cost: 116.4, commission: 0.39, delivery: 105 },
61 '1421119246': { cost: 158.4, commission: 0.39, delivery: 105 },
62 '2007259608': { cost: 307.2, commission: 0.39, delivery: 105 },
63 '359062751': { cost: 122.4, commission: 0.39, delivery: 105 },
64 '1005718040': { cost: 158.4, commission: 0.39, delivery: 105 },
65 '906225589': { cost: 96, commission: 0.39, delivery: 105 },
66 '1556242664': { cost: 680.4, commission: 0.39, delivery: 105 },
67 '1064594212': { cost: 148.8, commission: 0.39, delivery: 105 },
68 '799768912': { cost: 122.4, commission: 0.39, delivery: 105 },
69 '1739604132': { cost: 217.2, commission: 0.39, delivery: 105 },
70 '1062956956': { cost: 136.8, commission: 0.39, delivery: 105 },
71 '645221613': { cost: 144, commission: 0.39, delivery: 105 },
72 '259307672': { cost: 154.8, commission: 0.39, delivery: 105 },
73 '1739610762': { cost: 214.8, commission: 0.39, delivery: 105 },
74 '261137335': { cost: 130.8, commission: 0.39, delivery: 105 },
75 '1134258903': { cost: 120, commission: 0.39, delivery: 105 },
76 '1354364340': { cost: 140.4, commission: 0.39, delivery: 105 },
77 '583903282': { cost: 105.6, commission: 0.39, delivery: 105 },
78 '1210396815': { cost: 152.4, commission: 0.39, delivery: 105 },
79 '840692395': { cost: 140.4, commission: 0.39, delivery: 105 },
80 '1428754652': { cost: 117.6, commission: 0.39, delivery: 105 },
81 '830793833': { cost: 141.6, commission: 0.39, delivery: 105 },
82 '645231878': { cost: 122.4, commission: 0.39, delivery: 105 },
83 '1638790412': { cost: 147.6, commission: 0.39, delivery: 105 },
84 '1594351493': { cost: 148.8, commission: 0.39, delivery: 105 },
85 '1556059491': { cost: 472.8, commission: 0.39, delivery: 105 },
86 '1652932872': { cost: 166.8, commission: 0.39, delivery: 105 },
87 '1220450238': { cost: 141.6, commission: 0.39, delivery: 105 },
88 '601543246': { cost: 133.2, commission: 0.39, delivery: 105 },
89 '907613435': { cost: 156, commission: 0.39, delivery: 105 },
90 '528451380': { cost: 109.2, commission: 0.39, delivery: 105 },
91 '645231366': { cost: 111.6, commission: 0.39, delivery: 105 },
92 '1583090470': { cost: 211.2, commission: 0.39, delivery: 105 },
93 '645344262': { cost: 217.2, commission: 0.39, delivery: 105 },
94 '1427430872': { cost: 135.6, commission: 0.39, delivery: 105 },
95 '308474502': { cost: 135.6, commission: 0.39, delivery: 105 },
96 '1034686951': { cost: 110.4, commission: 0.39, delivery: 105 },
97 '1635265452': { cost: 164.4, commission: 0.39, delivery: 105 },
98 '1181294718': { cost: 136.8, commission: 0.39, delivery: 105 },
99 '907477992': { cost: 111.6, commission: 0.39, delivery: 105 },
100 '275785773': { cost: 105.6, commission: 0.39, delivery: 105 },
101 '415116965': { cost: 129.6, commission: 0.39, delivery: 105 },
102 '885661840': { cost: 132, commission: 0.39, delivery: 105 },
103 '1138405158': { cost: 123.6, commission: 0.39, delivery: 105 },
104 '1474598641': { cost: 153.6, commission: 0.39, delivery: 105 },
105 '1749441319': { cost: 156, commission: 0.39, delivery: 105 },
106 '983496770': { cost: 180, commission: 0.39, delivery: 105 },
107 '259307514': { cost: 121.2, commission: 0.39, delivery: 105 },
108 '1412471842': { cost: 147.6, commission: 0.39, delivery: 105 },
109 '2007285388': { cost: 260.4, commission: 0.39, delivery: 105 },
110 '753545450': { cost: 105.6, commission: 0.39, delivery: 105 },
111 '414662752': { cost: 145.2, commission: 0.39, delivery: 105 },
112 '906537851': { cost: 120, commission: 0.39, delivery: 105 },
113 '745134928': { cost: 120, commission: 0.39, delivery: 105 },
114 '1450813313': { cost: 171.6, commission: 0.39, delivery: 105 },
115 '815579726': { cost: 108, commission: 0.39, delivery: 105 },
116 '1554889144': { cost: 172.8, commission: 0.39, delivery: 105 },
117 '1317529509': { cost: 108, commission: 0.39, delivery: 105 },
118 '1412482622': { cost: 201.6, commission: 0.39, delivery: 105 },
119 '711457941': { cost: 116.4, commission: 0.39, delivery: 105 },
120 '746323388': { cost: 166.8, commission: 0.39, delivery: 105 },
121 '1678615312': { cost: 144, commission: 0.39, delivery: 105 },
122 '1261461822': { cost: 93.6, commission: 0.39, delivery: 105 },
123 '601551066': { cost: 144, commission: 0.39, delivery: 105 },
124 '1440304060': { cost: 218.4, commission: 0.39, delivery: 105 },
125 '1363004944': { cost: 132, commission: 0.39, delivery: 105 },
126 '583586841': { cost: 162, commission: 0.39, delivery: 105 },
127 '701743635': { cost: 112.8, commission: 0.39, delivery: 105 },
128 '483752519': { cost: 114, commission: 0.39, delivery: 105 },
129 '254240996': { cost: 110.4, commission: 0.39, delivery: 105 },
130 '711403910': { cost: 105.6, commission: 0.39, delivery: 105 },
131 '701745855': { cost: 111.6, commission: 0.39, delivery: 105 },
132 '483722993': { cost: 123.6, commission: 0.39, delivery: 105 },
133 '265381213': { cost: 109.2, commission: 0.39, delivery: 105 },
134 '1865968664': { cost: 189.6, commission: 0.39, delivery: 105 },
135 '1361974814': { cost: 127.2, commission: 0.39, delivery: 105 },
136 '1711210766': { cost: 154.8, commission: 0.39, delivery: 105 },
137 '1430070632': { cost: 117.6, commission: 0.39, delivery: 105 },
138 '645347197': { cost: 111.6, commission: 0.39, delivery: 105 },
139 '601547006': { cost: 111.6, commission: 0.39, delivery: 105 },
140 '1554898780': { cost: 159.6, commission: 0.39, delivery: 105 },
141 '576759419': { cost: 146.4, commission: 0.39, delivery: 105 },
142 '361963040': { cost: 145.2, commission: 0.39, delivery: 105 },
143 '1588873517': { cost: 144, commission: 0.39, delivery: 105 },
144 '701743610': { cost: 94.8, commission: 0.39, delivery: 105 },
145 '362561005': { cost: 126, commission: 0.39, delivery: 105 },
146 '1638838932': { cost: 142.8, commission: 0.39, delivery: 105 },
147 '1586310017': { cost: 175.2, commission: 0.39, delivery: 105 },
148 '265381147': { cost: 112.8, commission: 0.39, delivery: 105 },
149 '799722971': { cost: 138, commission: 0.39, delivery: 105 },
150 '701746001': { cost: 133.2, commission: 0.39, delivery: 105 },
151 '885718701': { cost: 129.6, commission: 0.39, delivery: 105 },
152 '627707226': { cost: 135.6, commission: 0.39, delivery: 105 },
153 '907359259': { cost: 157.2, commission: 0.39, delivery: 105 },
154 '1422546406': { cost: 127.2, commission: 0.39, delivery: 105 },
155 '711392959': { cost: 123.6, commission: 0.39, delivery: 105 },
156 '701739222': { cost: 109.2, commission: 0.39, delivery: 105 },
157 '799782895': { cost: 120, commission: 0.39, delivery: 105 },
158 '905741712': { cost: 145.2, commission: 0.39, delivery: 105 },
159 '627696549': { cost: 153.6, commission: 0.39, delivery: 105 },
160 '627691916': { cost: 126, commission: 0.39, delivery: 105 },
161 '1525587675': { cost: 94.8, commission: 0.39, delivery: 105 },
162 '518696433': { cost: 118.8, commission: 0.39, delivery: 105 },
163 '1915145626': { cost: 94.8, commission: 0.39, delivery: 105 },
164 '1915134983': { cost: 92.4, commission: 0.39, delivery: 105 },
165 '1617625622': { cost: 669.6, commission: 0.39, delivery: 105 },
166 '2151674296': { cost: 294, commission: 0.39, delivery: 105 },
167 '1688371971': { cost: 213.6, commission: 0.39, delivery: 105 },
168 '2007269923': { cost: 348, commission: 0.39, delivery: 105 },
169 '1619711279': { cost: 535.2, commission: 0.39, delivery: 105 },
170 '1556227793': { cost: 598.8, commission: 0.39, delivery: 105 },
171 '1556213113': { cost: 624, commission: 0.39, delivery: 105 },
172 '1556033933': { cost: 805.2, commission: 0.39, delivery: 105 },
173 '1614280315': { cost: 711.6, commission: 0.39, delivery: 105 },
174 '1623554699': { cost: 613.2, commission: 0.39, delivery: 105 },
175 '1614312529': { cost: 598.8, commission: 0.39, delivery: 105 },
176 '1556157230': { cost: 568.8, commission: 0.39, delivery: 105 },
177 '1621230498': { cost: 567.6, commission: 0.39, delivery: 105 },
178 '1556203577': { cost: 487.2, commission: 0.39, delivery: 105 },
179 '1619457861': { cost: 476.4, commission: 0.39, delivery: 105 },
180 '1691210828': { cost: 118.8, commission: 0.39, delivery: 105 },
181 '1643979499': { cost: 309.6, commission: 0.39, delivery: 105 },
182 '1467776379': { cost: 175.2, commission: 0.39, delivery: 105 },
183 '881795381': { cost: 204, commission: 0.39, delivery: 105 },
184 '815257353': { cost: 126, commission: 0.39, delivery: 105 },
185 '2196230178': { cost: 96.168, commission: 0.39, delivery: 105 },
186 '2196231035': { cost: 97.332, commission: 0.39, delivery: 105 },
187 '2524038158': { cost: 97.392, commission: 0.39, delivery: 105 },
188 '2196231426': { cost: 97.596, commission: 0.39, delivery: 105 },
189 '2196232023': { cost: 119.916, commission: 0.39, delivery: 105 },
190 '2196243941': { cost: 129.12, commission: 0.39, delivery: 105 },
191 '2196234143': { cost: 134.58, commission: 0.39, delivery: 105 },
192 '320244429': { cost: 158.4, commission: 0.3, delivery: 90 },
193 '240637697': { cost: 108, commission: 0.3, delivery: 90 },
194 '608450235': { cost: 126, commission: 0.3, delivery: 90 },
195 '946421600': { cost: 103.2, commission: 0.3, delivery: 90 },
196 '496076761': { cost: 166.8, commission: 0.3, delivery: 90 },
197 '1413311039': { cost: 135.6, commission: 0.3, delivery: 90 },
198 '440778548': { cost: 708, commission: 0.3, delivery: 90 },
199 '523217298': { cost: 219.6, commission: 0.3, delivery: 90 },
200 '384090220': { cost: 103.2, commission: 0.3, delivery: 90 },
201 '496052718': { cost: 163.2, commission: 0.3, delivery: 90 },
202 '608424787': { cost: 116.4, commission: 0.3, delivery: 90 },
203 '710687654': { cost: 110.4, commission: 0.3, delivery: 90 },
204 '302313862': { cost: 146.4, commission: 0.3, delivery: 90 },
205 '275431854': { cost: 90, commission: 0.3, delivery: 90 },
206 '629089591': { cost: 111.6, commission: 0.3, delivery: 90 },
207 '445990016': { cost: 104.4, commission: 0.3, delivery: 90 },
208 '269055046': { cost: 177.6, commission: 0.3, delivery: 90 },
209 '1006091512': { cost: 100.8, commission: 0.3, delivery: 90 },
210 '541317459': { cost: 163.2, commission: 0.3, delivery: 90 },
211 '643867409': { cost: 170.4, commission: 0.3, delivery: 90 },
212 '1742879159': { cost: 172.8, commission: 0.3, delivery: 90 },
213 '723677583': { cost: 115.2, commission: 0.3, delivery: 90 },
214 '1162597065': { cost: 96, commission: 0.3, delivery: 90 },
215 '240618618': { cost: 117.6, commission: 0.3, delivery: 90 },
216 '308612909': { cost: 286.8, commission: 0.3, delivery: 90 },
217 '641019479': { cost: 90, commission: 0.3, delivery: 90 },
218 '415367670': { cost: 87.6, commission: 0.3, delivery: 90 },
219 '1755740945': { cost: 135.6, commission: 0.3, delivery: 90 },
220 '605720925': { cost: 121.2, commission: 0.3, delivery: 90 },
221 '1787685452': { cost: 124.8, commission: 0.3, delivery: 90 },
222 '930467608': { cost: 312, commission: 0.3, delivery: 90 },
223 '1675833489': { cost: 138, commission: 0.3, delivery: 90 },
224 '523205834': { cost: 120, commission: 0.3, delivery: 90 },
225 '955365874': { cost: 126, commission: 0.3, delivery: 90 },
226 '765946072': { cost: 174, commission: 0.3, delivery: 90 },
227 '302313862': { cost: 146.4, commission: 0.3, delivery: 90 },
228 '275431854': { cost: 90, commission: 0.3, delivery: 90 },
229 '629089591': { cost: 111.6, commission: 0.3, delivery: 90 },
230 '445990016': { cost: 104.4, commission: 0.3, delivery: 90 },
231 '269055046': { cost: 177.6, commission: 0.3, delivery: 90 },
232 '1006091512': { cost: 100.8, commission: 0.3, delivery: 90 },
233 '541317459': { cost: 163.2, commission: 0.3, delivery: 90 },
234 '643867409': { cost: 170.4, commission: 0.3, delivery: 90 },
235 '1742879159': { cost: 172.8, commission: 0.3, delivery: 90 },
236 '723677583': { cost: 115.2, commission: 0.3, delivery: 90 },
237 '1162597065': { cost: 96, commission: 0.3, delivery: 90 },
238 '240618618': { cost: 117.6, commission: 0.3, delivery: 90 },
239 '308612909': { cost: 286.8, commission: 0.3, delivery: 90 },
240 '641019479': { cost: 90, commission: 0.3, delivery: 90 },
241 '415367670': { cost: 87.6, commission: 0.3, delivery: 90 },
242 '1755740945': { cost: 135.6, commission: 0.3, delivery: 90 },
243 '605720925': { cost: 121.2, commission: 0.3, delivery: 90 },
244 '1787685452': { cost: 124.8, commission: 0.3, delivery: 90 },
245 '930467608': { cost: 312, commission: 0.3, delivery: 90 },
246 '1675833489': { cost: 138, commission: 0.3, delivery: 90 },
247 '523205834': { cost: 120, commission: 0.3, delivery: 90 },
248 '955365874': { cost: 126, commission: 0.3, delivery: 90 },
249 '765946072': { cost: 174, commission: 0.3, delivery: 90 },
250 '302474910': { cost: 155.376, commission: 0.3, delivery: 90 },
251 '496313974': { cost: 112.8, commission: 0.3, delivery: 90 },
252 '235921201': { cost: 96, commission: 0.3, delivery: 90 },
253 '257759548': { cost: 129.6, commission: 0.3, delivery: 90 },
254 '1423368794': { cost: 96, commission: 0.3, delivery: 90 },
255 '496162051': { cost: 100.8, commission: 0.3, delivery: 90 },
256 '256464706': { cost: 91.2, commission: 0.3, delivery: 90 },
257 '256403990': { cost: 100.8, commission: 0.3, delivery: 90 },
258 '496078352': { cost: 150, commission: 0.3, delivery: 90 },
259 '439787475': { cost: 128.4, commission: 0.3, delivery: 90 },
260 '2246237790': { cost: 352.8, commission: 0.3, delivery: 90 },
261 '1162432442': { cost: 162, commission: 0.3, delivery: 90 },
262 '496385269': { cost: 117.6, commission: 0.3, delivery: 90 },
263 '235747007': { cost: 123.6, commission: 0.3, delivery: 90 },
264 '240596679': { cost: 82.8, commission: 0.3, delivery: 90 },
265 '946386913': { cost: 67.416, commission: 0.3, delivery: 90 },
266 '1077386569': { cost: 264, commission: 0.3, delivery: 90 },
267 '342033120': { cost: 186, commission: 0.3, delivery: 90 },
268 '327212556': { cost: 88.8, commission: 0.3, delivery: 90 },
269 '257870717': { cost: 133.476, commission: 0.3, delivery: 90 },
270 '523338483': { cost: 165.6, commission: 0.3, delivery: 90 },
271 '955212015': { cost: 175.2, commission: 0.3, delivery: 90 },
272 '2937015180': { cost: 229.2, commission: 0.3, delivery: 90 },
273 '322211321': { cost: 123.6, commission: 0.3, delivery: 90 },
274 '821056923': { cost: 127.2, commission: 0.3, delivery: 90 },
275 '607930809': { cost: 218.4, commission: 0.3, delivery: 90 },
276 '605645338': { cost: 144, commission: 0.3, delivery: 90 },
277 '309603422': { cost: 194.4, commission: 0.3, delivery: 90 },
278 '496192068': { cost: 118.8, commission: 0.3, delivery: 90 },
279 '1225837045': { cost: 192, commission: 0.3, delivery: 90 },
280 '2975092428': { cost: 0, commission: 0.3, delivery: 90 },
281 '2925851542': { cost: 0, commission: 0.3, delivery: 90 },
282 '256499007': { cost: 124.8, commission: 0.3, delivery: 90 },
283 '631642752': { cost: 97.2, commission: 0.3, delivery: 90 },
284 '772649191': { cost: 80.4, commission: 0.3, delivery: 90 },
285 '1609663001': { cost: 217.2, commission: 0.3, delivery: 90 },
286 '530615077': { cost: 108, commission: 0.3, delivery: 90 },
287 '879257026': { cost: 160.8, commission: 0.3, delivery: 90 },
288 '1074695072': { cost: 176.4, commission: 0.3, delivery: 90 },
289 '697745723': { cost: 121.2, commission: 0.3, delivery: 90 },
290 '736971656': { cost: 111.6, commission: 0.3, delivery: 90 },
291 '951571515': { cost: 145.2, commission: 0.3, delivery: 90 },
292 '1773186071': { cost: 220.8, commission: 0.3, delivery: 90 },
293 '203461072': { cost: 204, commission: 0.3, delivery: 90 },
294 '496104827': { cost: 114, commission: 0.3, delivery: 90 },
295 '292190882': { cost: 102, commission: 0.3, delivery: 90 },
296 '1653722295': { cost: 218.4, commission: 0.3, delivery: 90 },
297 '1122871855': { cost: 216, commission: 0.3, delivery: 90 },
298 '1931646505': { cost: 136.8, commission: 0.3, delivery: 90 },
299 '439682438': { cost: 69.6, commission: 0.3, delivery: 90 },
300 '496210001': { cost: 72, commission: 0.3, delivery: 90 },
301 '1620775285': { cost: 112.8, commission: 0.3, delivery: 90 },
302 '1617839244': { cost: 114, commission: 0.3, delivery: 90 },
303 '1787676082': { cost: 243.6, commission: 0.3, delivery: 90 },
304 '631658690': { cost: 91.2, commission: 0.3, delivery: 90 },
305 '257885915': { cost: 133.488, commission: 0.3, delivery: 90 },
306 '1805680376': { cost: 58.8, commission: 0.3, delivery: 90 },
307 '540355209': { cost: 159.6, commission: 0.3, delivery: 90 },
308 '1742877411': { cost: 208.8, commission: 0.3, delivery: 90 },
309 '1418275068': { cost: 158.4, commission: 0.3, delivery: 90 },
310 '2899612149': { cost: 145.2, commission: 0.3, delivery: 90 },
311 '2963203543': { cost: 129.948, commission: 0.3, delivery: 90 },
312 '607968165': { cost: 103.2, commission: 0.3, delivery: 90 },
313 '1162462310': { cost: 180, commission: 0.3, delivery: 90 },
314 '1507555262': { cost: 114, commission: 0.3, delivery: 90 },
315 '2925821091': { cost: 0, commission: 0.3, delivery: 90 },
316 '608298877': { cost: 136.8, commission: 0.3, delivery: 90 },
317 '1239708831': { cost: 169.2, commission: 0.3, delivery: 90 },
318 '1724749801': { cost: 180, commission: 0.3, delivery: 90 },
319 '439673824': { cost: 140.4, commission: 0.3, delivery: 90 },
320 '496542249': { cost: 81.6, commission: 0.3, delivery: 90 },
321 '1417508954': { cost: 122.4, commission: 0.3, delivery: 90 },
322 '372062053': { cost: 140.4, commission: 0.3, delivery: 90 },
323 '235824492': { cost: 105.6, commission: 0.3, delivery: 90 },
324 '912117351': { cost: 153.6, commission: 0.3, delivery: 90 },
325 '1162577837': { cost: 117.6, commission: 0.3, delivery: 90 },
326 '838273559': { cost: 163.2, commission: 0.3, delivery: 90 },
327 '953470782': { cost: 134.4, commission: 0.3, delivery: 90 },
328 '1591125774': { cost: 328.8, commission: 0.3, delivery: 90 },
329 '1239742307': { cost: 139.2, commission: 0.3, delivery: 90 },
330 '422559168': { cost: 159.6, commission: 0.3, delivery: 90 },
331 '1581558117': { cost: 117.6, commission: 0.3, delivery: 90 },
332 '697870104': { cost: 92.4, commission: 0.3, delivery: 90 },
333 '561033759': { cost: 112.8, commission: 0.3, delivery: 90 },
334 '1696256652': { cost: 115.2, commission: 0.3, delivery: 90 },
335 '2925806092': { cost: 0, commission: 0.3, delivery: 90 },
336 '1911238277': { cost: 103.2, commission: 0.3, delivery: 90 },
337 '282398558': { cost: 123.6, commission: 0.3, delivery: 90 },
338 '1880336894': { cost: 340.8, commission: 0.3, delivery: 90 },
339 '1394598690': { cost: 127.2, commission: 0.3, delivery: 90 },
340 '1161957584': { cost: 141.6, commission: 0.3, delivery: 90 },
341 '697830627': { cost: 182.4, commission: 0.3, delivery: 90 },
342 '307964008': { cost: 94.8, commission: 0.3, delivery: 90 },
343 '254895676': { cost: 112.8, commission: 0.3, delivery: 90 },
344 '1417478667': { cost: 124.8, commission: 0.3, delivery: 90 },
345 '1609652145': { cost: 144, commission: 0.3, delivery: 90 },
346 '1010086939': { cost: 141.6, commission: 0.3, delivery: 90 },
347 '952853707': { cost: 128.4, commission: 0.3, delivery: 90 },
348 '1665302366': { cost: 224.4, commission: 0.3, delivery: 90 },
349 '302361373': { cost: 104.4, commission: 0.3, delivery: 90 },
350 '952754284': { cost: 144.54, commission: 0.3, delivery: 90 },
351 '273926369': { cost: 88.8, commission: 0.3, delivery: 90 },
352 '838115048': { cost: 164.4, commission: 0.3, delivery: 90 },
353 '838251281': { cost: 103.788, commission: 0.3, delivery: 90 },
354 '732238010': { cost: 148.8, commission: 0.3, delivery: 90 },
355 '445989855': { cost: 85.2, commission: 0.3, delivery: 90 },
356 '269076077': { cost: 301.452, commission: 0.3, delivery: 90 },
357 '257568474': { cost: 73.2, commission: 0.3, delivery: 90 },
358 '954664346': { cost: 129.6, commission: 0.3, delivery: 90 },
359 '254600991': { cost: 132, commission: 0.3, delivery: 90 },
360 '772640606': { cost: 106.8, commission: 0.3, delivery: 90 },
361 '955283837': { cost: 182.4, commission: 0.3, delivery: 90 },
362 '254686077': { cost: 174, commission: 0.3, delivery: 90 },
363 '1394711785': { cost: 144, commission: 0.3, delivery: 90 },
364 '257884238': { cost: 130.8, commission: 0.3, delivery: 90 },
365 '1620776878': { cost: 157.2, commission: 0.3, delivery: 90 },
366 '951783485': { cost: 132, commission: 0.3, delivery: 90 },
367 '356177679': { cost: 69.6, commission: 0.3, delivery: 90 },
368 '1394612090': { cost: 159.6, commission: 0.3, delivery: 90 },
369 '2897675749': { cost: 0, commission: 0.3, delivery: 90 },
370 '1162445078': { cost: 186, commission: 0.3, delivery: 90 },
371 '1932949948': { cost: 235.2, commission: 0.3, delivery: 90 },
372 '879083250': { cost: 186, commission: 0.3, delivery: 90 },
373 '256466996': { cost: 200.4, commission: 0.3, delivery: 90 },
374 '3006918397': { cost: 313.2, commission: 0.3, delivery: 90 },
375 '1418277399': { cost: 346.8, commission: 0.3, delivery: 90 },
376 '496419419': { cost: 258, commission: 0.3, delivery: 90 },
377 '1007822873': { cost: 134.4, commission: 0.3, delivery: 90 },
378 '608351238': { cost: 120, commission: 0.3, delivery: 90 },
379 '1413857256': { cost: 159.6, commission: 0.3, delivery: 90 },
380 '1787673702': { cost: 150, commission: 0.3, delivery: 90 },
381 '1162216260': { cost: 145.2, commission: 0.3, delivery: 90 },
382 '1413837107': { cost: 87.6, commission: 0.3, delivery: 90 },
383 '768941637': { cost: 171.6, commission: 0.3, delivery: 90 },
384 '1507653510': { cost: 96, commission: 0.3, delivery: 90 },
385 '1007787669': { cost: 132, commission: 0.3, delivery: 90 },
386 '2352209417': { cost: 283.752, commission: 0.3, delivery: 90 },
387 '1397098991': { cost: 331.2, commission: 0.3, delivery: 90 },
388 '1507630430': { cost: 99.6, commission: 0.3, delivery: 90 },
389 '1189589105': { cost: 130.8, commission: 0.3, delivery: 90 },
390 '1506037461': { cost: 470.4, commission: 0.3, delivery: 90 },
391 '1162348932': { cost: 194.4, commission: 0.3, delivery: 90 },
392 '2975103256': { cost: 163.056, commission: 0.3, delivery: 90 },
393 '1506047716': { cost: 171.6, commission: 0.3, delivery: 90 },
394 '911292485': { cost: 112.8, commission: 0.3, delivery: 90 },
395 '1880333546': { cost: 364.8, commission: 0.3, delivery: 90 },
396 '1608793966': { cost: 132, commission: 0.3, delivery: 90 },
397 '1880315436': { cost: 344.4, commission: 0.3, delivery: 90 },
398 '2352282340': { cost: 283.752, commission: 0.3, delivery: 90 },
399 '709600226': { cost: 129.6, commission: 0.3, delivery: 90 },
400 '3016287234': { cost: 274.8, commission: 0.3, delivery: 90 },
401 '3016266763': { cost: 240, commission: 0.3, delivery: 90 },
402 '3026343193': { cost: 283.2, commission: 0.3, delivery: 90 },
403 '3011261105': { cost: 208.8, commission: 0.3, delivery: 90 },
404 '3026384906': { cost: 309.6, commission: 0.3, delivery: 90 },
405 '1303951724': { cost: 67.2, commission: 0.3, delivery: 90 },
406 '3026360459': { cost: 310.8, commission: 0.3, delivery: 90 },
407 '951614842': { cost: 116.4, commission: 0.3, delivery: 90 },
408 '1758263220': { cost: 776.4, commission: 0.3, delivery: 90 },
409 '1162674702': { cost: 152.4, commission: 0.3, delivery: 90 },
410 '236327983': { cost: 120, commission: 0.3, delivery: 90 },
411 '256416463': { cost: 104.4, commission: 0.3, delivery: 90 },
412 '1790465865': { cost: 62.4, commission: 0.3, delivery: 90 },
413 '2975215156': { cost: 163.056, commission: 0.3, delivery: 90 },
414 '1005952104': { cost: 94.8, commission: 0.3, delivery: 90 },
415 '399215466': { cost: 102, commission: 0.3, delivery: 90 },
416 '1162300125': { cost: 116.4, commission: 0.3, delivery: 90 },
417 '1005952110': { cost: 93.6, commission: 0.3, delivery: 90 },
418 '496044796': { cost: 116.4, commission: 0.3, delivery: 90 },
419 '203389028': { cost: 166.8, commission: 0.3, delivery: 90 },
420 '1005550551': { cost: 96, commission: 0.3, delivery: 90 },
421 '203480472': { cost: 94.8, commission: 0.3, delivery: 90 },
422 '203473290': { cost: 121.2, commission: 0.3, delivery: 90 },
423 '1620772534': { cost: 321.6, commission: 0.3, delivery: 90 },
424 '282469378': { cost: 100.8, commission: 0.3, delivery: 90 },
425 '1805671797': { cost: 58.8, commission: 0.3, delivery: 90 },
426 '203350791': { cost: 134.4, commission: 0.3, delivery: 90 },
427 '1394625221': { cost: 129.6, commission: 0.3, delivery: 90 },
428 '301908659': { cost: 111.6, commission: 0.3, delivery: 90 },
429 '1507673120': { cost: 253.2, commission: 0.36, delivery: 200 },
430 '1239609957': { cost: 123.6, commission: 0.36, delivery: 200 },
431 '1006163518': { cost: 188.4, commission: 0.36, delivery: 200 },
432 '1422925703': { cost: 252, commission: 0.36, delivery: 200 },
433 '1572975136': { cost: 547.2, commission: 0.36, delivery: 200 },
434 '2394561998': { cost: 106.8, commission: 0.36, delivery: 200 },
435 '838220448': { cost: 136.8, commission: 0.36, delivery: 200 },
436 '543848911': { cost: 228, commission: 0.36, delivery: 200 },
437 '1020579419': { cost: 88.8, commission: 0.36, delivery: 200 },
438 '557726786': { cost: 217.2, commission: 0.36, delivery: 200 },
439 '1608785751': { cost: 176.4, commission: 0.36, delivery: 200 },
440 '1020578939': { cost: 92.4, commission: 0.36, delivery: 200 },
441 '519816761': { cost: 231.6, commission: 0.36, delivery: 200 },
442 '1673659402': { cost: 1267, commission: 0.36, delivery: 200 },
443 '501441185': { cost: 216, commission: 0.36, delivery: 200 },
444 '860254877': { cost: 223.2, commission: 0.36, delivery: 200 },
445 '774068400': { cost: 358.8, commission: 0.36, delivery: 200 },
446 '519740742': { cost: 216, commission: 0.36, delivery: 200 },
447 '1673650914': { cost: 1537, commission: 0.36, delivery: 200 },
448 '519734762': { cost: 234, commission: 0.36, delivery: 200 },
449 '1558177759': { cost: 366, commission: 0.36, delivery: 200 },
450 '1558176776': { cost: 421.2, commission: 0.36, delivery: 200 },
451 '643844171': { cost: 112.8, commission: 0.36, delivery: 200 },
452 '519732586': { cost: 98.4, commission: 0.36, delivery: 200 },
453 '1019659629': { cost: 91.2, commission: 0.36, delivery: 200 },
454 '1673649384': { cost: 1517, commission: 0.36, delivery: 200 },
455 '860261495': { cost: 240, commission: 0.36, delivery: 200 },
456 '1880450237': { cost: 271.2, commission: 0.36, delivery: 200 },
457 '1558174139': { cost: 372, commission: 0.36, delivery: 200 },
458 '1845802065': { cost: 768, commission: 0.36, delivery: 200 },
459 '1673647587': { cost: 1497, commission: 0.36, delivery: 200 },
460 '557670932': { cost: 392.4, commission: 0.36, delivery: 200 },
461 '1979738121': { cost: 637.2, commission: 0.36, delivery: 200 },
462 '1020579926': { cost: 88.8, commission: 0.36, delivery: 200 },
463 '1880440770': { cost: 273.6, commission: 0.36, delivery: 200 },
464 '1979755177': { cost: 662.4, commission: 0.36, delivery: 200 },
465 '2898154255': { cost: 124.8, commission: 0.36, delivery: 200 },
466 '2899553513': { cost: 145.2, commission: 0.36, delivery: 200 },
467 '1846354292': { cost: 768, commission: 0.36, delivery: 200 },
468 '1880410608': { cost: 285.6, commission: 0.36, delivery: 200 },
469 '2898115411': { cost: 136.8, commission: 0.36, delivery: 200 },
470 '1880442651': { cost: 270, commission: 0.36, delivery: 200 },
471 '501438861': { cost: 220.8, commission: 0.36, delivery: 200 },
472 '1397099331': { cost: 372, commission: 0.36, delivery: 200 },
473 '1846363096': { cost: 768, commission: 0.36, delivery: 200 },
474 '2631471568': { cost: 685.2, commission: 0.36, delivery: 200 },
475 '1911710527': { cost: 276, commission: 0.36, delivery: 200 },
476 '1911695310': { cost: 267.6, commission: 0.36, delivery: 200 },
477 '1911704413': { cost: 264, commission: 0.36, delivery: 200 },
478 '1020579746': { cost: 90, commission: 0.36, delivery: 200 },
479 '335431876': { cost: 51.6, commission: 0.36, delivery: 200 },
480 '439685200': { cost: 55.2, commission: 0.36, delivery: 200 },
481 '1019698970': { cost: 88.8, commission: 0.36, delivery: 200 },
482 '439692165': { cost: 55.2, commission: 0.36, delivery: 200 },
483 '335431975': { cost: 54, commission: 0.36, delivery: 200 },
484 '335446268': { cost: 52.8, commission: 0.36, delivery: 200 },
485 '1846315219': { cost: 768, commission: 0.36, delivery: 200 },
486 '335440088': { cost: 61.2, commission: 0.36, delivery: 200 },
487 '838167533': { cost: 106.8, commission: 0.3, delivery: 90 },
488 '878312141': { cost: 265.2, commission: 0.36, delivery: 200 },
489 '1572991087': { cost: 290.4, commission: 0.36, delivery: 200 },
490 '1521855158': { cost: 104.4, commission: 0.3, delivery: 90 },
491 '646434083': { cost: 252, commission: 0.3, delivery: 90 },
492 '646441179': { cost: 273.6, commission: 0.3, delivery: 90 },
493 '2352128160': { cost: 340.8, commission: 0.36, delivery: 200 },
494 '446010027': { cost: 344.4, commission: 0.3, delivery: 90 },
495 '1773177807': { cost: 192, commission: 0.36, delivery: 200 },
496 '1980328072': { cost: 338.4, commission: 0.36, delivery: 200 },
497 '646441673': { cost: 258, commission: 0.3, delivery: 90 },
498 '772524320': { cost: 131.64, commission: 0.3, delivery: 90 },
499 '302573933': { cost: 258, commission: 0.3, delivery: 90 },
500 '824213857': { cost: 172.8, commission: 0.3, delivery: 90 },
501 '1609643510': { cost: 175.2, commission: 0.3, delivery: 90 },
502 '439749049': { cost: 357.6, commission: 0.3, delivery: 90 },
503 '496064096': { cost: 230.052, commission: 0.3, delivery: 90 },
504 '732341657': { cost: 205.2, commission: 0.36, delivery: 200 },
505 '955327749': { cost: 146.4, commission: 0.3, delivery: 90 },
506 '1787680920': { cost: 230.4, commission: 0.3, delivery: 90 },
507 '2352063264': { cost: 285.6, commission: 0.36, delivery: 200 },
508 '2352107373': { cost: 321.6, commission: 0.36, delivery: 200 },
509 '1521854115': { cost: 177.6, commission: 0.3, delivery: 90 },
510 '240602548': { cost: 128.4, commission: 0.3, delivery: 90 },
511 '1239644265': { cost: 207.6, commission: 0.3, delivery: 90 },
512 '203465007': { cost: 124.8, commission: 0.36, delivery: 200 },
513 '1937534346': { cost: 144, commission: 0.3, delivery: 90 },
514 '1979698296': { cost: 324, commission: 0.36, delivery: 200 },
515 '256430704': { cost: 99.6, commission: 0.3, delivery: 90 },
516 '1505980019': { cost: 183.6, commission: 0.3, delivery: 90 }
517 };
518
519 // Extract product ID from URL
520 function getProductId() {
521 const match = window.location.href.match(/product\/[^\/]+-(\d+)/);
522 return match ? match[1] : null;
523 }
524
525 // Get short SKU from full product ID (last 5 digits)
526 function getShortSku(fullProductId) {
527 if (PRODUCT_DATA[fullProductId]) {
528 return fullProductId;
529 }
530
531 const last5 = fullProductId.slice(-5);
532 if (PRODUCT_DATA[last5]) {
533 return last5;
534 }
535
536 const last6 = fullProductId.slice(-6);
537 if (PRODUCT_DATA[last6]) {
538 return last6;
539 }
540
541 return null;
542 }
543
544 // Extract daily price and sales data from MP Stats chart
545 async function extractDailyDataFromChart(widget) {
546 console.log('>>> extractDailyDataFromChart CALLED (v2.0.1) <<<');
547
548 try {
549 const dailyData = [];
550 const svgs = widget.querySelectorAll('svg.apexcharts-svg');
551 console.log('Found ApexCharts SVG elements:', svgs.length);
552
553 if (svgs.length === 0) {
554 console.log('No SVG charts found');
555 return dailyData;
556 }
557
558 const salesSvg = svgs[0];
559 console.log('Analyzing sales chart (SVG 0)...');
560
561 const bars = salesSvg.querySelectorAll('path.apexcharts-bar-area');
562 console.log(`Found ${bars.length} bar elements`);
563
564 if (bars.length === 0) {
565 console.log('No bars found in sales chart');
566 return dailyData;
567 }
568
569 console.log('Starting tooltip extraction...');
570
571 const indices = [];
572 for (let i = 1; i < bars.length; i++) {
573 indices.push(i);
574 }
575 indices.push(0);
576
577 for (const index of indices) {
578 const bar = bars[index];
579 const rect = bar.getBoundingClientRect();
580 const centerX = rect.left + rect.width / 2;
581 const centerY = rect.top + rect.height / 2;
582
583 console.log(`Day ${index + 1}: rect:`, { left: rect.left, top: rect.top, width: rect.width, height: rect.height });
584
585 bar.dispatchEvent(new MouseEvent('mousemove', {
586 bubbles: true,
587 cancelable: true,
588 view: window,
589 clientX: centerX,
590 clientY: centerY
591 }));
592
593 const waitTime = index <= 2 ? 800 : 200;
594 await new Promise(resolve => setTimeout(resolve, waitTime));
595
596 let tooltip = document.querySelector('.chart-custom-tooltip');
597 let retries = 0;
598 const maxRetries = index === 0 ? 8 : 5;
599
600 while (!tooltip && retries < maxRetries) {
601 console.log(`Day ${index + 1}: Tooltip not found, retry ${retries + 1}/${maxRetries}`);
602
603 bar.dispatchEvent(new MouseEvent('mousemove', {
604 bubbles: true,
605 cancelable: true,
606 view: window,
607 clientX: centerX,
608 clientY: centerY
609 }));
610
611 await new Promise(resolve => setTimeout(resolve, 400));
612 tooltip = document.querySelector('.chart-custom-tooltip');
613 retries++;
614 }
615
616 if (!tooltip) {
617 console.log(`Day ${index + 1}: Tooltip not found after ${maxRetries} retries, skipping`);
618 continue;
619 }
620
621 const tooltipText = tooltip.textContent;
622 console.log(`Day ${index + 1}: Tooltip text:`, tooltipText);
623
624 const salesMatch = tooltipText.match(/Продажи:\s*(\d+)\s*шт/);
625 const sales = salesMatch ? parseInt(salesMatch[1]) : null;
626
627 const priceMatch = tooltipText.match(/Цена:\s*(\d+)\s*₽/);
628 const price = priceMatch ? parseInt(priceMatch[1]) : null;
629
630 if (sales !== null && price !== null && sales > 0 && price > 0) {
631 dailyData.push({
632 day: index + 1,
633 sales: sales,
634 price: price
635 });
636 } else {
637 console.log(`Day ${index + 1}: Invalid data - sales: ${sales}, price: ${price}`);
638 }
639 }
640
641 console.log(`Total extracted daily data points: ${dailyData.length}`);
642 console.log('Sample data - First 5 days:', dailyData.slice(0, 5));
643 console.log('Sample data - Last 5 days:', dailyData.slice(-5));
644 return dailyData;
645
646 } catch (error) {
647 console.error('Error extracting daily data:', error);
648 return [];
649 }
650 }
651
652 // Extract stock data from MP Stats stock chart
653 async function extractStockData(widget) {
654 console.log('Extracting stock data from MP Stats...');
655
656 if (!widget) {
657 console.error('Widget not provided to extractStockData');
658 return null;
659 }
660
661 const svgs = widget.querySelectorAll('svg.apexcharts-svg');
662 console.log(`Found ${svgs.length} SVG charts for stock data`);
663
664 if (!svgs || svgs.length < 4) {
665 console.error(`Stock chart SVG not found - only ${svgs ? svgs.length : 0} charts available, need at least 4`);
666 return null;
667 }
668
669 const stockSvg = svgs[3];
670 console.log('Analyzing stock chart (SVG 3)...');
671
672 const stockBars = stockSvg.querySelectorAll('path.apexcharts-bar-area');
673 console.log(`Found ${stockBars.length} stock bars in fourth chart`);
674
675 if (!stockBars || stockBars.length === 0) {
676 console.error('No stock bars found in fourth chart');
677 return null;
678 }
679
680 const stockPoints = [];
681 console.log('Starting stock tooltip extraction...');
682
683 const indices = [];
684 for (let i = 1; i < stockBars.length; i++) {
685 indices.push(i);
686 }
687 indices.push(0);
688
689 for (const index of indices) {
690 const bar = stockBars[index];
691 const rect = bar.getBoundingClientRect();
692 const centerX = rect.left + rect.width / 2;
693 const centerY = rect.top + rect.height / 2;
694
695 console.log(`Stock Day ${index + 1}: rect:`, { left: rect.left, top: rect.top, width: rect.width, height: rect.height });
696
697 bar.dispatchEvent(new MouseEvent('mousemove', {
698 bubbles: true,
699 cancelable: true,
700 view: window,
701 clientX: centerX,
702 clientY: centerY
703 }));
704
705 const waitTime = index <= 2 ? 800 : 200;
706 await new Promise(resolve => setTimeout(resolve, waitTime));
707
708 let tooltip = document.querySelector('.chart-custom-tooltip');
709 let retries = 0;
710 const maxRetries = index === 0 ? 8 : 5;
711
712 while (!tooltip && retries < maxRetries) {
713 console.log(`Stock Day ${index + 1}: Tooltip not found, retry ${retries + 1}/${maxRetries}`);
714
715 bar.dispatchEvent(new MouseEvent('mousemove', {
716 bubbles: true,
717 cancelable: true,
718 view: window,
719 clientX: centerX,
720 clientY: centerY
721 }));
722
723 await new Promise(resolve => setTimeout(resolve, 400));
724 tooltip = document.querySelector('.chart-custom-tooltip');
725 retries++;
726 }
727
728 if (!tooltip) {
729 console.log(`Stock Day ${index + 1}: Tooltip not found after ${maxRetries} retries, skipping`);
730 continue;
731 }
732
733 const tooltipText = tooltip.textContent;
734 console.log(`Stock Day ${index + 1}: Tooltip text:`, tooltipText);
735
736 const stockMatch = tooltipText.match(/Остаток:\s*(\d+)\s*шт/) || tooltipText.match(/(\d+)\s*шт/);
737 const stock = stockMatch ? parseInt(stockMatch[1]) : null;
738
739 if (stock !== null && stock >= 0) {
740 stockPoints.push({
741 day: index + 1,
742 stock: stock
743 });
744 console.log(`Stock Day ${index + 1}: Extracted stock: ${stock}`);
745 } else {
746 console.log(`Stock Day ${index + 1}: Invalid stock data - stock: ${stock}`);
747 }
748 }
749
750 console.log(`Extracted ${stockPoints.length} days of stock data from chart`);
751 console.log('Stock data sample - First 5 days:', stockPoints.slice(0, 5));
752 console.log('Stock data sample - Last 5 days:', stockPoints.slice(-5));
753 return stockPoints;
754 }
755
756 // NEW: Extract competitors from search query
757 async function extractCompetitorsFromSearch(keyQuery) {
758 console.log('=== START extractCompetitorsFromSearch ===');
759 console.log('Key query:', keyQuery);
760
761 try {
762 // Encode query for URL
763 const encodedQuery = encodeURIComponent(keyQuery);
764 const searchUrl = `https://www.ozon.ru/search/?text=${encodedQuery}&from_global=true`;
765
766 console.log('Opening search URL in new tab:', searchUrl);
767 console.log('⚠️ ВАЖНО: Новая вкладка откроется для сбора данных о конкурентах. Пожалуйста, не закрывайте её - она закроется автоматически.');
768
769 // Open search page in new tab (in foreground so user can see it)
770 await GM.openInTab(searchUrl, true);
771
772 // Wait for new tab to load and process
773 console.log('Waiting for search page to load...');
774
775 // The new tab will handle extraction and save to storage
776 // This function just initiates the process
777 return null;
778
779 } catch (error) {
780 console.error('Error in extractCompetitorsFromSearch:', error);
781 return null;
782 }
783 }
784
785 // NEW: Extract competitors in search page context
786 async function extractCompetitorsInSearchPage() {
787 console.log('=== extractCompetitorsInSearchPage CALLED ===');
788
789 // Check if we're on a search or category page
790 if (!window.location.href.includes('/search/') && !window.location.href.includes('/category/')) {
791 console.log('Not on search/category page, skipping competitor extraction');
792 return;
793 }
794
795 // Check if we have a pending analysis state (meaning we came from product page)
796 const stateJson = await GM.getValue('analysis_state', null);
797 if (!stateJson) {
798 console.log('No analysis state found, not extracting competitors');
799 return;
800 }
801
802 const state = JSON.parse(stateJson);
803
804 // Check if state is recent (within 2 minutes)
805 if (Date.now() - state.timestamp > 120000) {
806 console.log('Analysis state is too old, ignoring');
807 await GM.deleteValue('analysis_state');
808 return;
809 }
810
811 console.log('Found analysis state, will extract competitors from this search page');
812
813 // Wait for MPStats widget to load
814 console.log('Waiting for MPStats widget to load...');
815 let mpstatsWidget = null;
816 let attempts = 0;
817 const maxAttempts = 30;
818
819 while (attempts < maxAttempts && !mpstatsWidget) {
820 await new Promise(resolve => setTimeout(resolve, 1000));
821 mpstatsWidget = document.getElementById('mpstat-ozone-search-result');
822 attempts++;
823 console.log(`Attempt ${attempts}/${maxAttempts} to find MPStats widget...`);
824 }
825
826 if (!mpstatsWidget) {
827 console.error('MPStats widget not found in search results after 30 seconds');
828 // Mark as failed and close tab
829 await GM.setValue('competitors_extracted', 'failed');
830 window.close();
831 return;
832 }
833
834 console.log('MPStats widget found, waiting for data to load...');
835
836 // Wait for table rows to appear
837 let rows = [];
838 attempts = 0;
839
840 while (attempts < 20 && rows.length === 0) {
841 await new Promise(resolve => setTimeout(resolve, 1000));
842 rows = mpstatsWidget.querySelectorAll('tbody tr');
843 attempts++;
844 console.log(`Attempt ${attempts}/20: Found ${rows.length} rows`);
845 }
846
847 if (rows.length === 0) {
848 console.error('No rows found in MPStats table after waiting');
849 await GM.setValue('competitors_extracted', 'failed');
850 window.close();
851 return;
852 }
853
854 console.log(`Found ${rows.length} rows in MPStats table, extracting competitor data...`);
855
856 const competitors = [];
857
858 for (let i = 0; i < rows.length; i++) {
859 const row = rows[i];
860
861 try {
862 // Extract SKU from the third column
863 const skuCell = row.querySelectorAll('td')[2];
864 if (!skuCell) continue;
865
866 const skuText = skuCell.textContent.trim();
867 const sku = skuText.match(/\d+/) ? skuText.match(/\d+/)[0] : null;
868
869 if (!sku) continue;
870
871 // Extract brand from the fourth column
872 const brandCell = row.querySelectorAll('td')[3];
873 if (!brandCell) continue;
874
875 const brand = brandCell.textContent.trim();
876
877 if (!brand || brand.length < 2) continue;
878
879 // Extract price from the fifth column
880 const priceCell = row.querySelectorAll('td')[4];
881 if (!priceCell) continue;
882
883 const priceText = priceCell.textContent.trim();
884 const priceMatch = priceText.match(/(\d[\d\s]*)\s*₽/);
885 if (!priceMatch) continue;
886
887 const price = parseInt(priceMatch[1].replace(/\s/g, ''));
888 if (isNaN(price) || price < 100 || price > 100000) continue;
889
890 // Extract sales from the sixth column (orders per month)
891 const salesCell = row.querySelectorAll('td')[5];
892 if (!salesCell) continue;
893
894 const salesText = salesCell.textContent.trim();
895 const sales = parseInt(salesText.replace(/\s/g, ''));
896 if (isNaN(sales)) continue;
897
898 // Calculate revenue
899 const revenue = price * sales;
900
901 if (revenue > 0) {
902 competitors.push({
903 brand: brand,
904 sku: sku,
905 price: price,
906 sales30days: sales,
907 revenue30days: revenue
908 });
909
910 console.log(`Competitor: ${brand} - Price: ${price}₽, Sales: ${sales}, Revenue: ${revenue}₽`);
911 }
912
913 } catch (error) {
914 console.error(`Error extracting competitor from row ${i}:`, error);
915 }
916 }
917
918 // Sort by revenue (descending)
919 competitors.sort((a, b) => b.revenue30days - a.revenue30days);
920
921 console.log(`Extracted ${competitors.length} competitors from search`);
922 console.log('>>> COMPETITOR PRICES EXTRACTED <<<');
923 console.log('Competitor count:', competitors.length);
924 if (competitors.length > 0) {
925 const prices = competitors.map(c => c.price);
926 const minCompPrice = Math.min(...prices);
927 const maxCompPrice = Math.max(...prices);
928 const avgCompPrice = Math.round(prices.reduce((a, b) => a + b, 0) / prices.length);
929 console.log('Price range:', minCompPrice, '-', maxCompPrice, '₽');
930 console.log('Average price:', avgCompPrice, '₽');
931 console.log('All competitor prices:', prices);
932 console.log('Top 5 competitors by revenue:', competitors.slice(0, 5).map(c => `${c.brand}: ${c.price}₽, ${c.sales30days} шт, ${c.revenue30days}₽`));
933 }
934
935 // Save competitors to storage
936 await GM.setValue('temp_competitors', JSON.stringify(competitors));
937 console.log('Competitors saved to storage');
938
939 // Mark that extraction is complete
940 await GM.setValue('competitors_extracted', 'true');
941
942 // Close the tab after successful extraction
943 console.log('Closing competitor search tab...');
944 setTimeout(() => {
945 window.close();
946 }, 1000);
947 }
948
949 // Extract summary data from MP Stats widget
950 async function extractMPStatsData() {
951 console.log('Extracting MP Stats summary data...');
952
953 const widget = document.querySelector('.mps-sidebar');
954 if (!widget) {
955 console.error('MP Stats widget not found');
956 return null;
957 }
958
959 const revenueText = widget.textContent.match(/Выручка за 30 суток\s*([\d\s]+)/);
960 const salesText = widget.textContent.match(/Продаж за 30 суток\s*([\d\s]+)/);
961 const currentStockText = widget.textContent.match(/Текущий остаток\s*([\d\s]+)/);
962
963 if (revenueText && salesText) {
964 const revenue = parseInt(revenueText[1].replace(/\s/g, ''));
965 const sales = parseInt(salesText[1].replace(/\s/g, ''));
966
967 let avgPrice = revenue / sales;
968 const webPriceWidget = document.querySelector('[data-widget="webPrice"]');
969 if (webPriceWidget) {
970 const priceSpans = webPriceWidget.querySelectorAll('span');
971 const prices = [];
972 priceSpans.forEach(span => {
973 const text = span.textContent.trim();
974 const match = text.match(/^(\d[\d\s]*)\s*₽$/);
975 if (match) {
976 const priceText = match[1].replace(/\s/g, '');
977 const parsedPrice = parseInt(priceText);
978 if (!isNaN(parsedPrice) && parsedPrice > 100 && parsedPrice < 100000) {
979 prices.push(parsedPrice);
980 }
981 }
982 });
983 if (prices.length > 0) {
984 avgPrice = Math.min(...prices);
985 console.log('Found prices in webPrice:', prices, 'Using minimum:', avgPrice);
986 }
987 }
988
989 const currentStock = currentStockText ? parseInt(currentStockText[1].replace(/\s/g, '')) : 0;
990
991 const dataPoints = await extractDailyDataFromChart(widget);
992
993 if (!dataPoints || dataPoints.length === 0) {
994 console.error('Failed to extract chart data');
995 return null;
996 }
997
998 const stockPoints = await extractStockData(widget);
999 console.log('Stock points extracted:', stockPoints);
1000
1001 const totalSales = dataPoints.reduce((sum, d) => sum + d.sales, 0);
1002 const avgDailySales = totalSales / dataPoints.length;
1003
1004 let matchedStockPoints = null;
1005 if (stockPoints && stockPoints.length > 0) {
1006 console.log('Matching stock data with sales data...');
1007 matchedStockPoints = dataPoints.map(salesDay => {
1008 const stockDay = stockPoints.find(s => s.day === salesDay.day);
1009 return {
1010 day: salesDay.day,
1011 sales: salesDay.sales,
1012 price: salesDay.price,
1013 stock: stockDay ? stockDay.stock : null
1014 };
1015 });
1016 console.log('Matched stock data - First 5 days:', matchedStockPoints.slice(0, 5));
1017 console.log('Matched stock data - Last 5 days:', matchedStockPoints.slice(-5));
1018 } else {
1019 console.log('No stock data to match');
1020 }
1021
1022 console.log('Extracted summary data:', {
1023 revenue,
1024 sales,
1025 avgPrice,
1026 currentStock,
1027 avgDailySales,
1028 daysOfData: dataPoints.length,
1029 stockDataPoints: stockPoints ? stockPoints.length : 0,
1030 matchedDataPoints: matchedStockPoints ? matchedStockPoints.length : 0
1031 });
1032
1033 return {
1034 revenue,
1035 sales,
1036 avgPrice,
1037 currentStock,
1038 avgDailySales,
1039 dataPoints,
1040 stockPoints,
1041 matchedStockPoints
1042 };
1043 }
1044
1045 return null;
1046 }
1047
1048 // Calculate demand elasticity from historical data
1049 function calculateDemandElasticity(dataPoints, matchedStockPoints) {
1050 console.log('=== CALCULATING DEMAND ELASTICITY (Bayesian Log-Linear) ===');
1051 console.log('dataPoints:', dataPoints);
1052 console.log('matchedStockPoints:', matchedStockPoints);
1053
1054 // Filter out days with zero stock and the day before zero stock
1055 let validPoints = dataPoints;
1056 if (matchedStockPoints && matchedStockPoints.length > 0) {
1057 const zeroStockDays = new Set();
1058 matchedStockPoints.forEach((d, index) => {
1059 if (d.stock === 0) {
1060 zeroStockDays.add(d.day);
1061 // Also exclude the day before zero stock
1062 if (index > 0) {
1063 zeroStockDays.add(matchedStockPoints[index - 1].day);
1064 }
1065 }
1066 });
1067 validPoints = matchedStockPoints.filter(d => !zeroStockDays.has(d.day));
1068 console.log(`Excluded ${zeroStockDays.size} days (zero stock and day before)`);
1069 }
1070
1071 console.log(`Using ${validPoints.length} days for elasticity calculation`);
1072 console.log('Valid points sample:', validPoints.slice(0, 5));
1073
1074 if (validPoints.length < 5) {
1075 console.log('Not enough data points for elasticity calculation, using default -1.5');
1076 return { elasticity: -1.5, reliability: 'bad', r2: 0, uniquePrices: 0, daysUsed: validPoints.length };
1077 }
1078
1079 // === LOG-LINEAR REGRESSION ===
1080 // Transform to log space: log(sales) = intercept + elasticity * log(price)
1081 const logPrices = validPoints.map(d => Math.log(d.price));
1082 const logSales = validPoints.map(d => Math.log(d.sales + 1)); // +1 for stability when sales = 0
1083
1084 const n = validPoints.length;
1085 const sumX = logPrices.reduce((a, b) => a + b, 0);
1086 const sumY = logSales.reduce((a, b) => a + b, 0);
1087 const sumXY = logPrices.reduce((a, b, i) => a + b * logSales[i], 0);
1088 const sumX2 = logPrices.reduce((a, b) => a + b * b, 0);
1089
1090 console.log(`Log-linear regression: n=${n}, sumX=${sumX.toFixed(2)}, sumY=${sumY.toFixed(2)}, sumXY=${sumXY.toFixed(2)}, sumX2=${sumX2.toFixed(2)}`);
1091
1092 const denominator = n * sumX2 - sumX * sumX;
1093 if (denominator === 0) {
1094 console.log('⚠️ Denominator is zero, using default elasticity');
1095 return { elasticity: -1.5, reliability: 'bad', r2: 0, uniquePrices: 0, daysUsed: n };
1096 }
1097
1098 const slope = (n * sumXY - sumX * sumY) / denominator; // This is the elasticity
1099 const intercept = (sumY - slope * sumX) / n;
1100
1101 console.log(`OLS results: slope (elasticity) = ${slope.toFixed(4)}, intercept = ${intercept.toFixed(4)}`);
1102
1103 // Calculate R² (coefficient of determination)
1104 const yMean = sumY / n;
1105 const ssTotal = logSales.reduce((s, y) => s + Math.pow(y - yMean, 2), 0);
1106 const predicted = logPrices.map(x => slope * x + intercept);
1107 const ssResidual = logSales.reduce((s, y, i) => s + Math.pow(y - predicted[i], 2), 0);
1108 const r2 = ssTotal > 0 ? 1 - (ssResidual / ssTotal) : 0;
1109
1110 console.log(`R²: ssTotal=${ssTotal.toFixed(4)}, ssResidual=${ssResidual.toFixed(4)}, R²=${r2.toFixed(4)}`);
1111
1112 // === BAYESIAN CORRECTION WITH INFORMATIVE PRIOR ===
1113 const priorElasticity = -1.5; // Typical value for Ozon products
1114 const priorStrength = 10; // "Equivalent to 10 days of observations"
1115
1116 console.log(`Prior: elasticity=${priorElasticity}, strength=${priorStrength} days`);
1117
1118 const dataStrength = n; // Weight of actual data
1119 const posteriorElasticity = (slope * dataStrength + priorElasticity * priorStrength) / (dataStrength + priorStrength);
1120
1121 console.log(`Posterior elasticity (simple Bayesian average): ${posteriorElasticity.toFixed(4)}`);
1122
1123 // If we have a lot of data and high R² - trust the data more
1124 const weightData = Math.min(1, n / 20) * Math.sqrt(Math.max(0, r2));
1125 const finalElasticity = slope * weightData + posteriorElasticity * (1 - weightData);
1126
1127 console.log(`Data weight: ${weightData.toFixed(4)} (based on n=${n} and R²=${r2.toFixed(4)})`);
1128 console.log(`Final elasticity (weighted): ${finalElasticity.toFixed(4)}`);
1129
1130 // Clamp to reasonable range
1131 const clampedElasticity = Math.max(-3.0, Math.min(-0.3, finalElasticity));
1132
1133 if (clampedElasticity !== finalElasticity) {
1134 console.log(`Elasticity ${finalElasticity.toFixed(4)} clamped to ${clampedElasticity.toFixed(4)}`);
1135 }
1136
1137 // Determine reliability based on R², number of unique prices, and days
1138 const uniquePrices = [...new Set(validPoints.map(d => d.price))];
1139
1140 let reliability = 'bad';
1141 if (r2 >= 0.7 && uniquePrices.length >= 8 && n >= 20) {
1142 reliability = 'good';
1143 } else if (r2 >= 0.4 && uniquePrices.length >= 4 && n >= 10) {
1144 reliability = 'satisfactory';
1145 }
1146
1147 console.log(`=== FINAL ELASTICITY: ${clampedElasticity.toFixed(4)}, R²=${r2.toFixed(4)}, Reliability=${reliability} ===`);
1148 console.log(`Raw OLS elasticity: ${slope.toFixed(4)}, Posterior: ${posteriorElasticity.toFixed(4)}, Final: ${clampedElasticity.toFixed(4)}`);
1149
1150 return {
1151 elasticity: clampedElasticity,
1152 reliability: reliability,
1153 r2: r2,
1154 uniquePrices: uniquePrices.length,
1155 daysUsed: n,
1156 rawElasticity: slope,
1157 posteriorElasticity: posteriorElasticity
1158 };
1159 }
1160
1161 // AI-powered Bayesian price optimization
1162 async function bayesianPriceOptimizationWithAI(historicalData, productData, competitorData, coinvest, drr) {
1163 console.log('=== ENTERED bayesianPriceOptimizationWithAI (v2.0.1) ===');
1164 console.log('Historical data:', historicalData);
1165 console.log('Product data:', productData);
1166 console.log('Competitor data:', competitorData);
1167 console.log('Coinvest:', coinvest, '%, DRR:', drr, '%');
1168
1169 try {
1170 console.log('>>> ABOUT TO CALL calculateDemandElasticity <<<');
1171 console.log('>>> dataPoints:', historicalData.dataPoints);
1172 console.log('>>> matchedStockPoints:', historicalData.matchedStockPoints);
1173
1174 const elasticityResult = calculateDemandElasticity(historicalData.dataPoints, historicalData.matchedStockPoints);
1175 const realElasticity = elasticityResult.elasticity;
1176
1177 console.log(`>>> AFTER calculateDemandElasticity, realElasticity = ${realElasticity} <<<`);
1178 console.log(`Real demand elasticity calculated: ${realElasticity.toFixed(4)}, R²=${elasticityResult.r2.toFixed(4)}, Reliability=${elasticityResult.reliability}`);
1179
1180 // Prepare historical data summary
1181 const uniquePrices = [...new Set(historicalData.dataPoints.map(d => d.price))];
1182
1183 console.log(`Historical data: ${historicalData.dataPoints.length} days, ${uniquePrices.length} unique prices:`, uniquePrices);
1184
1185 // Prepare competitor analysis
1186 let competitorAnalysis = '';
1187 if (competitorData && competitorData.length > 0) {
1188 const prices = competitorData.map(c => c.price);
1189 const minCompPrice = Math.min(...prices);
1190 const maxCompPrice = Math.max(...prices);
1191 const avgCompPrice = Math.round(prices.reduce((a, b) => a + b, 0) / prices.length);
1192
1193 const topCompetitors = competitorData.slice(0, 3).map(c =>
1194 `${c.price}₽ (${c.sales30days ? (c.sales30days / 30).toFixed(1) + ' шт/день' : 'н/д'})`
1195 ).join(', ');
1196
1197 competitorAnalysis = `\nКОНКУРЕНТЫ (${competitorData.length} шт): цены ${minCompPrice}-${maxCompPrice}₽, средняя ${avgCompPrice}₽. Топ-3 по выручке: ${topCompetitors}.`;
1198
1199 console.log('>>> COMPETITOR PRICES BEING SENT TO AI <<<');
1200 console.log('Competitor count:', competitorData.length);
1201 console.log('Price range:', minCompPrice, '-', maxCompPrice, '₽');
1202 console.log('Average price:', avgCompPrice, '₽');
1203 console.log('Top 3 competitors:', topCompetitors);
1204 console.log('All competitor prices:', prices);
1205 console.log('Competitor analysis text for AI:', competitorAnalysis);
1206 } else {
1207 console.log('>>> NO COMPETITOR DATA - AI WILL NOT CONSIDER COMPETITORS <<<');
1208 }
1209
1210 const commissionPercent = Math.round(productData.commission * 100);
1211
1212 // Calculate actual price from current display price using user's coinvest value
1213 const displayPriceMultiplier = (100 - coinvest) / 100;
1214 const actualPrice = historicalData.avgPrice / displayPriceMultiplier;
1215 const minBreakevenActualPrice = (productData.cost + productData.delivery) / (1 - productData.commission - drr / 100);
1216 const minBreakevenDisplayPrice = Math.ceil(minBreakevenActualPrice * displayPriceMultiplier);
1217
1218 // === НОВЫЕ МЕТРИКИ ===
1219
1220 // 1. Weighted average competitor price (взвешенная по продажам)
1221 let weightedAvgCompPrice = 0;
1222 let totalCompSales = 0;
1223 let minCompPrice = Infinity;
1224 let maxCompPrice = 0;
1225 if (competitorData && competitorData.length > 0) {
1226 competitorData.forEach(c => {
1227 if (c.sales30days > 0) {
1228 weightedAvgCompPrice += c.price * c.sales30days;
1229 totalCompSales += c.sales30days;
1230 }
1231 minCompPrice = Math.min(minCompPrice, c.price);
1232 maxCompPrice = Math.max(maxCompPrice, c.price);
1233 });
1234 weightedAvgCompPrice = totalCompSales > 0 ? weightedAvgCompPrice / totalCompSales : historicalData.avgPrice;
1235 } else {
1236 weightedAvgCompPrice = historicalData.avgPrice;
1237 minCompPrice = historicalData.avgPrice;
1238 maxCompPrice = historicalData.avgPrice;
1239 }
1240
1241 // 2. RPI (Relative Price Index)
1242 const currentDisplayPrice = historicalData.avgPrice;
1243 const rpi = (currentDisplayPrice / weightedAvgCompPrice) * 100;
1244
1245 // 3. Popularity Score (твои средние продажи vs средние конкурентов)
1246 let avgCompDailySales = 0;
1247 if (competitorData && competitorData.length > 0) {
1248 avgCompDailySales = competitorData.reduce((sum, c) => sum + (c.sales30days || 0) / 30, 0) / competitorData.length;
1249 }
1250 const popularityScore = avgCompDailySales > 0 ? historicalData.avgDailySales / avgCompDailySales : 1;
1251
1252 // 4. PVBI (Price Value Benefit Index) — proxy ценности
1253 let maxCompValue = 0;
1254 if (competitorData && competitorData.length > 0) {
1255 competitorData.forEach(c => {
1256 const value = c.sales30days > 0 ? (c.sales30days / 30) / c.price : 0;
1257 maxCompValue = Math.max(maxCompValue, value);
1258 });
1259 }
1260 const yourValue = historicalData.avgDailySales / currentDisplayPrice;
1261 const pvbi = maxCompValue > 0 ? yourValue / maxCompValue : 1;
1262
1263 // 5. Days of Supply
1264 const daysOfSupply = historicalData.avgDailySales > 0 ? historicalData.currentStock / historicalData.avgDailySales : 999;
1265
1266 // 6. Топ-3 конкурентов для промпта
1267 const topCompetitors = competitorData && competitorData.length > 0
1268 ? competitorData.slice(0, 3).map(c => `${c.brand}: ${c.price}₽ (${(c.sales30days / 30).toFixed(1)} шт/день)`).join(', ')
1269 : 'нет данных';
1270
1271 console.log('=== НОВЫЕ МЕТРИКИ ===');
1272 console.log('Weighted Avg Comp Price:', weightedAvgCompPrice.toFixed(0), '₽');
1273 console.log('RPI:', rpi.toFixed(1), '%');
1274 console.log('Popularity Score:', popularityScore.toFixed(2));
1275 console.log('PVBI:', pvbi.toFixed(2));
1276 console.log('Days of Supply:', daysOfSupply.toFixed(1));
1277
1278 const aiPrompt = `Ты — эксперт по динамическому ценообразованию на маркетплейсах (Ozon). Твоя задача — найти цену реализации, которая МАКСИМИЗИРУЕТ ДНЕВНУЮ ПРИБЫЛЬ с учётом всех рисков.
1279
1280КРИТИЧЕСКИ ВАЖНО:
1281• Цена реализации — это цена, которую видит покупатель (после соинвеста ${coinvest}% от Ozon).
1282• Фактическая цена = Цена реализации / ${displayPriceMultiplier.toFixed(2)}.
1283• Все расходы (комиссия, ДРР) считаются от фактической цены.
1284• Прибыль = (Факт. цена - Себестоимость - Комиссия - Доставка - Реклама) × Прогноз продаж.
1285
1286ТЕКУЩАЯ СИТУАЦИЯ:
1287• Текущая цена реализации: ${Math.round(historicalData.avgPrice)} ₽
1288• Фактическая цена: ${Math.round(actualPrice)} ₽
1289• Средние продажи: ${historicalData.avgDailySales.toFixed(1)} шт/день
1290• Остаток: ${historicalData.currentStock} шт (Days of Supply: ${daysOfSupply.toFixed(1)} дней)
1291• Popularity Score (относительно конкурентов): ${popularityScore.toFixed(2)} (выше 1 — популярнее рынка)
1292• RPI (Relative Price Index): ${rpi.toFixed(1)}% (100% — средняя цена конкурентов по выручке)
1293• PVBI (Price Value Benefit Index): ${pvbi.toFixed(2)} (выше 1 — выше воспринимаемая ценность)
1294
1295ЭЛАСТИЧНОСТЬ СПРОСА (лог-линейная модель, ${historicalData.dataPoints.length} дней, ${uniquePrices.length} уникальных цен):
1296• Коэффициент эластичности: ${realElasticity.toFixed(4)}
1297• Формула прогноза: Прогноз продаж = ${historicalData.avgDailySales.toFixed(1)} × (цена / ${Math.round(historicalData.avgPrice)}) ** ${realElasticity.toFixed(4)}
1298• Достоверность: R² = ${elasticityResult.r2.toFixed(4)}, ${elasticityResult.reliability}
1299
1300СТРУКТУРА РАСХОДОВ:
1301• Себестоимость: ${productData.cost} ₽
1302• Комиссия Ozon: ${commissionPercent}% от факт. цены
1303• Доставка: ${productData.delivery} ₽
1304• ДРР: ${drr}% от факт. цены
1305• Минимальная безубыточная цена реализации: ${minBreakevenDisplayPrice} ₽
1306
1307КОНКУРЕНТЫ (${competitorData ? competitorData.length : 0} шт):
1308• Взвешенная средняя цена: ${Math.round(weightedAvgCompPrice)} ₽
1309• Диапазон: ${minCompPrice === Infinity ? 'н/д' : minCompPrice + '–' + maxCompPrice + ' ₽'}
1310• Топ-3 по выручке: ${topCompetitors}
1311
1312ДОПОЛНИТЕЛЬНЫЕ КОРРЕКТИРОВКИ (обязательно применяй):
13131. RPI-корректировка: если RPI > 110 — уменьши прогноз продаж на (RPI-100)/50; если RPI < 90 — увеличь на (100-RPI)/50.
13142. Остатки: если Days of Supply < 10 — повысь цену (меньше чувствительность к скидкам); если > 60 — можно агрессивнее снижать.
13153. Popularity Score: если > 1.2 — можно устанавливать цену выше рынка без сильного падения продаж.
1316
1317ТВОЯ ЗАДАЧА:
13181. Рассчитай прибыль для цен реализации от ${minBreakevenDisplayPrice} ₽ до ${Math.round(historicalData.avgPrice * 1.6)} ₽ с шагом 10 ₽.
13192. Для каждой цены применяй ВСЕ корректировки выше (RPI, остатки, популярность).
13203. Используй лог-линейную формулу: Прогноз = ${historicalData.avgDailySales.toFixed(1)} × (цена / ${Math.round(historicalData.avgPrice)}) ** ${realElasticity.toFixed(4)}
13214. Выбери цену с МАКСИМАЛЬНОЙ дневной прибылью.
13225. Также предложи 3 альтернативные цены для исследования (explorationPrices) — чуть ниже/выше оптимума.
1323
1324ВАЖНО: Не бойся рекомендовать изменение цены! Если расчеты показывают, что другая цена даст больше прибыли - рекомендуй её!
1325
1326Верни JSON строго по схеме с оптимальной ценой РЕАЛИЗАЦИИ (той, что дает максимальную прибыль/день).`;
1327
1328 console.log('>>> CALLING RM.aiCall <<<');
1329 const aiResponse = await RM.aiCall(aiPrompt, {
1330 type: 'json_schema',
1331 json_schema: {
1332 name: 'price_optimization',
1333 schema: {
1334 type: 'object',
1335 properties: {
1336 optimalPrice: { type: 'number' },
1337 minPrice: { type: 'number' },
1338 maxPrice: { type: 'number' },
1339 confidence: { type: 'number' },
1340 reasoning: { type: 'string' },
1341 explorationPrices: {
1342 type: 'array',
1343 items: { type: 'number' },
1344 minItems: 3,
1345 maxItems: 3
1346 }
1347 },
1348 required: ['optimalPrice', 'minPrice', 'maxPrice', 'confidence', 'reasoning', 'explorationPrices']
1349 }
1350 }
1351 });
1352
1353 console.log('>>> AI RESPONSE RECEIVED <<<');
1354 console.log('AI response:', aiResponse);
1355 console.log('AI optimalPrice:', aiResponse.optimalPrice);
1356 console.log('AI reasoning:', aiResponse.reasoning);
1357 console.log('AI confidence:', aiResponse.confidence);
1358 console.log('AI explorationPrices:', aiResponse.explorationPrices);
1359
1360 // Calculate all possible prices and find the one with maximum profit
1361 const priceRange = [];
1362 const step = (aiResponse.maxPrice - aiResponse.minPrice) / 20;
1363
1364 for (let price = aiResponse.minPrice; price <= aiResponse.maxPrice; price += step) {
1365 priceRange.push(Math.round(price));
1366 }
1367
1368 aiResponse.explorationPrices.forEach(p => {
1369 if (!priceRange.includes(Math.round(p))) {
1370 priceRange.push(Math.round(p));
1371 }
1372 });
1373
1374 priceRange.sort((a, b) => a - b);
1375
1376 const results = priceRange.map(displayPrice => {
1377 let estimatedSales = historicalData.avgDailySales * Math.pow(displayPrice / historicalData.avgPrice, realElasticity);
1378
1379 // Calculate actual price from display price using user's coinvest
1380 const actualPrice = displayPrice / displayPriceMultiplier;
1381 const commission = actualPrice * productData.commission;
1382 const advertisingCost = actualPrice * (drr / 100);
1383 const delivery = productData.delivery;
1384 const profitPerUnit = actualPrice - productData.cost - commission - delivery - advertisingCost;
1385 const profit = profitPerUnit * estimatedSales;
1386 const margin = (profitPerUnit / actualPrice) * 100;
1387
1388 return {
1389 price: Math.round(displayPrice * 10) / 10,
1390 actualPrice: Math.round(actualPrice * 10) / 10,
1391 estimatedDailySales: Math.round(estimatedSales * 10) / 10,
1392 estimatedDailyProfit: Math.round(profit * 10) / 10,
1393 profitMargin: Math.round(margin * 10) / 10,
1394 confidence: aiResponse.confidence,
1395 commission: Math.round(commission * 10) / 10,
1396 advertisingCost: Math.round(advertisingCost * 10) / 10,
1397 delivery: delivery
1398 };
1399 });
1400
1401 // Find the price with maximum profit from our calculations
1402 const maxProfitResult = results.reduce((max, current) =>
1403 current.estimatedDailyProfit > max.estimatedDailyProfit ? current : max
1404 );
1405
1406 console.log('>>> CALCULATED OPTIMAL PRICE <<<');
1407 console.log('Current price:', historicalData.avgPrice, '₽');
1408 console.log('AI suggested:', aiResponse.optimalPrice, '₽');
1409 console.log('Math optimal:', maxProfitResult.price, '₽ with profit:', maxProfitResult.estimatedDailyProfit, '₽/day');
1410
1411 // Log all results sorted by profit
1412 const sortedByProfit = [...results].sort((a, b) => b.estimatedDailyProfit - a.estimatedDailyProfit);
1413 console.log('>>> TOP 10 PRICES BY PROFIT <<<');
1414 sortedByProfit.slice(0, 10).forEach((r, i) => {
1415 console.log(`${i + 1}. Price: ${r.price}₽, Profit: ${r.estimatedDailyProfit}₽/day, Sales: ${r.estimatedDailySales} шт/day, Margin: ${r.profitMargin}%`);
1416 });
1417
1418 console.log('>>> BOTTOM 5 PRICES BY PROFIT <<<');
1419 sortedByProfit.slice(-5).forEach((r, i) => {
1420 console.log(`${sortedByProfit.length - 4 + i}. Price: ${r.price}₽, Profit: ${r.estimatedDailyProfit}₽/day, Sales: ${r.estimatedDailySales} шт/day`);
1421 });
1422
1423 // Use the mathematically optimal price instead of AI suggestion
1424 let optimalResult = maxProfitResult;
1425
1426 // Update AI reasoning to reflect the correction - FIX: use currentDailyProfit instead of 0
1427 if (Math.abs(maxProfitResult.price - aiResponse.optimalPrice) > 5) {
1428 const aiSuggestedResult = results.find(r => Math.abs(r.price - aiResponse.optimalPrice) < 1);
1429 const aiSuggestedProfit = aiSuggestedResult ? aiSuggestedResult.estimatedDailyProfit : 0;
1430
1431 // Calculate current profit correctly
1432 const displayPriceMultiplier = (100 - coinvest) / 100;
1433 const currentActualPrice = historicalData.avgPrice / displayPriceMultiplier;
1434 const currentCommission = currentActualPrice * productData.commission;
1435 const currentAdvertisingCost = currentActualPrice * (drr / 100);
1436 const currentDelivery = productData.delivery;
1437 const currentProfitPerUnit = currentActualPrice - productData.cost - currentCommission - currentDelivery - currentAdvertisingCost;
1438 const currentDailyProfit = Math.round(currentProfitPerUnit * historicalData.avgDailySales * 10) / 10;
1439
1440 aiResponse.reasoning = `Математический расчет показал, что оптимальная цена ${maxProfitResult.price}₽ дает прибыль ${maxProfitResult.estimatedDailyProfit}₽/день, что лучше первоначально предложенной ${aiResponse.optimalPrice}₽ (прибыль ${aiSuggestedProfit}₽/день) и текущей ${Math.round(historicalData.avgPrice)}₽ (прибыль ${currentDailyProfit}₽/день). При цене ${maxProfitResult.price}₽: продажи ${maxProfitResult.estimatedDailySales} шт/день, маржа ${maxProfitResult.profitMargin}%, прибыль/шт ${Math.round(maxProfitResult.actualPrice - productData.cost - maxProfitResult.commission - maxProfitResult.delivery - maxProfitResult.advertisingCost)}₽. Эта цена максимизирует дневную прибыль с учетом эластичности спроса и конкурентной среды.`;
1441 console.log('>>> CORRECTED AI SUGGESTION <<<');
1442 console.log('New reasoning:', aiResponse.reasoning);
1443 }
1444
1445 const alternatives = [];
1446 aiResponse.explorationPrices.forEach(explorePrice => {
1447 if (explorePrice !== aiResponse.optimalPrice) {
1448 let altResult = results.find(r => r.price === explorePrice);
1449
1450 if (!altResult) {
1451 const estimatedSales = historicalData.avgDailySales * Math.pow(explorePrice / historicalData.avgPrice, realElasticity);
1452
1453 const actualPrice = explorePrice / displayPriceMultiplier;
1454 const commission = actualPrice * productData.commission;
1455 const advertisingCost = actualPrice * (drr / 100);
1456 const delivery = productData.delivery;
1457 const profit = (actualPrice - productData.cost - commission - delivery - advertisingCost) * estimatedSales;
1458 const margin = ((actualPrice - productData.cost - commission - delivery - advertisingCost) / actualPrice) * 100;
1459
1460 altResult = {
1461 price: Math.round(explorePrice * 10) / 10,
1462 actualPrice: Math.round(actualPrice * 10) / 10,
1463 estimatedDailySales: Math.round(estimatedSales * 10) / 10,
1464 estimatedDailyProfit: Math.round(profit * 10) / 10,
1465 profitMargin: Math.round(margin * 10) / 10,
1466 confidence: aiResponse.confidence,
1467 commission: Math.round(commission * 10) / 10,
1468 advertisingCost: Math.round(advertisingCost * 10) / 10,
1469 delivery: delivery
1470 };
1471 }
1472
1473 alternatives.push(altResult);
1474 }
1475 });
1476
1477 console.log('>>> RETURNING OPTIMIZATION RESULTS <<<');
1478 return {
1479 optimal: optimalResult,
1480 alternatives: alternatives,
1481 currentPrice: Math.round(historicalData.avgPrice),
1482 productData: productData,
1483 allResults: results,
1484 aiReasoning: aiResponse.reasoning,
1485 aiConfidence: aiResponse.confidence,
1486 aiDemandElasticity: realElasticity,
1487 elasticityReliability: elasticityResult.reliability,
1488 elasticityR2: elasticityResult.r2,
1489 elasticityUniquePrices: elasticityResult.uniquePrices,
1490 elasticityDaysUsed: elasticityResult.daysUsed,
1491 explorationPrices: aiResponse.explorationPrices,
1492 competitorData: competitorData,
1493 historicalData: historicalData,
1494 coinvest: coinvest,
1495 drr: drr
1496 };
1497
1498 } catch (error) {
1499 console.error('AI analysis failed:', error);
1500 throw error;
1501 }
1502 }
1503
1504 // Create and inject the analysis widget
1505 function createAnalysisWidget() {
1506 console.log('Creating analysis widget (v2.0.1)...');
1507
1508 if (document.getElementById('bayesian-price-optimizer')) {
1509 console.log('Widget already exists');
1510 return;
1511 }
1512
1513 const widget = document.createElement('div');
1514 widget.id = 'bayesian-price-optimizer';
1515 widget.innerHTML = `
1516 <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1517 color: white;
1518 padding: 15px;
1519 border-radius: 12px;
1520 margin: 15px 0;
1521 box-shadow: 0 4px 15px rgba(0,0,0,0.2);
1522 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1523 width: 100%;
1524 box-sizing: border-box;">
1525 <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
1526 <h3 style="margin: 0; font-size: 16px; font-weight: 600;">
1527 🎯 Оптимизация цены (AI + Bayesian) v2.0.1
1528 </h3>
1529 </div>
1530
1531 <div style="margin-bottom: 12px;">
1532 <label style="display: block; font-size: 12px; margin-bottom: 4px; opacity: 0.9;">
1533 Ключевой запрос для поиска конкурентов
1534 </label>
1535 <input type="text" id="key-query-input" placeholder="Например: крем для лица увлажняющий"
1536 style="width: 100%; padding: 8px; border: none; border-radius: 6px;
1537 font-size: 14px; box-sizing: border-box;">
1538 </div>
1539
1540 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px;">
1541 <div>
1542 <label style="display: block; font-size: 12px; margin-bottom: 4px; opacity: 0.9;">
1543 Соинвест (скидка OZON), %
1544 </label>
1545 <input type="number" id="coinvest-input" value="45" min="0" max="100" step="1"
1546 style="width: 100%; padding: 8px; border: none; border-radius: 6px;
1547 font-size: 14px; box-sizing: border-box; font-weight: 600;">
1548 </div>
1549 <div>
1550 <label style="display: block; font-size: 12px; margin-bottom: 4px; opacity: 0.9;">
1551 ДРР (расходы на рекламу), %
1552 </label>
1553 <input type="number" id="drr-input" value="0" min="0" max="100" step="0.1"
1554 style="width: 100%; padding: 8px; border: none; border-radius: 6px;
1555 font-size: 14px; box-sizing: border-box; font-weight: 600;">
1556 </div>
1557 </div>
1558
1559 <button id="analyze-price-btn" style="background: white;
1560 color: #667eea;
1561 border: none;
1562 padding: 10px 20px;
1563 border-radius: 8px;
1564 cursor: pointer;
1565 font-weight: 600;
1566 font-size: 14px;
1567 transition: all 0.3s;
1568 width: 100%;">
1569 Анализировать
1570 </button>
1571
1572 <div id="analysis-results" style="display: none; margin-top: 15px;">
1573 <div style="background: rgba(255,255,255,0.15);
1574 padding: 12px;
1575 border-radius: 8px;
1576 backdrop-filter: blur(10px);
1577 max-width: 600px; box-sizing: border-box;">
1578 <div id="results-content" style="overflow-wrap: break-word; word-wrap: break-word;"></div>
1579 </div>
1580 </div>
1581 <div id="loading-indicator" style="display: none; text-align: center; padding: 20px;">
1582 <div style="display: inline-block; width: 30px; height: 30px; border: 3px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 1s linear infinite;"></div>
1583 <p style="margin-top: 10px; font-size: 13px;">Анализируем данные с помощью AI...</p>
1584 </div>
1585 </div>
1586 `;
1587
1588 const style = document.createElement('style');
1589 style.textContent = `
1590 @keyframes spin {
1591 to { transform: rotate(360deg); }
1592 }
1593 #analyze-price-btn:hover {
1594 transform: translateY(-2px);
1595 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
1596 }
1597 #bayesian-price-optimizer * {
1598 box-sizing: border-box;
1599 }
1600 `;
1601 document.head.appendChild(style);
1602
1603 const mpsWidget = document.querySelector('.mps-sidebar');
1604 if (mpsWidget) {
1605 mpsWidget.parentElement.insertBefore(widget, mpsWidget.nextSibling);
1606 console.log('Widget inserted after MP Stats');
1607 } else {
1608 document.body.appendChild(widget);
1609 console.log('Widget inserted at body end');
1610 }
1611
1612 const analyzeBtn = document.getElementById('analyze-price-btn');
1613 analyzeBtn.addEventListener('click', performAnalysis);
1614 }
1615
1616 // Perform the price analysis
1617 async function performAnalysis() {
1618 console.log('>>> performAnalysis CALLED (v2.0.1) <<<');
1619
1620 const loadingIndicator = document.getElementById('loading-indicator');
1621 const resultsDiv = document.getElementById('analysis-results');
1622
1623 loadingIndicator.style.display = 'block';
1624 resultsDiv.style.display = 'none';
1625
1626 try {
1627 // Get user input values
1628 const coinvestInput = document.getElementById('coinvest-input');
1629 const drrInput = document.getElementById('drr-input');
1630 const keyQueryInput = document.getElementById('key-query-input');
1631
1632 const coinvest = parseFloat(coinvestInput.value) || 45;
1633 const drr = parseFloat(drrInput.value) || 0;
1634 const keyQuery = keyQueryInput.value.trim();
1635
1636 console.log('User inputs - Coinvest:', coinvest, '%, DRR:', drr, '%, Key Query:', keyQuery);
1637
1638 // Validate inputs
1639 if (coinvest < 0 || coinvest > 100) {
1640 throw new Error('Соинвест должен быть от 0 до 100%');
1641 }
1642 if (drr < 0 || drr > 100) {
1643 throw new Error('ДРР должен быть от 0 до 100%');
1644 }
1645 if (!keyQuery) {
1646 throw new Error('Пожалуйста, введите ключевой запрос для поиска конкурентов');
1647 }
1648
1649 const productId = getProductId();
1650 console.log('Product ID:', productId);
1651
1652 if (!productId) {
1653 throw new Error('Не удалось определить ID товара');
1654 }
1655
1656 const shortSku = getShortSku(productId);
1657 console.log('Short SKU:', shortSku);
1658
1659 if (!shortSku) {
1660 throw new Error('Данные для этого товара не найдены');
1661 }
1662
1663 const productData = PRODUCT_DATA[shortSku];
1664 console.log('Product data:', productData);
1665
1666 if (!productData) {
1667 throw new Error('Данные для этого товара не найдены');
1668 }
1669
1670 console.log('>>> CALLING extractMPStatsData <<<');
1671 const mpStatsData = await extractMPStatsData();
1672 console.log('>>> extractMPStatsData FINISHED <<<');
1673
1674 if (!mpStatsData) {
1675 throw new Error('Не удалось извлечь данные из MP Stats');
1676 }
1677
1678 // Save analysis state before opening search tab
1679 await GM.setValue('analysis_state', JSON.stringify({
1680 coinvest,
1681 drr,
1682 keyQuery,
1683 productId,
1684 shortSku,
1685 mpStatsData,
1686 productData,
1687 timestamp: Date.now()
1688 }));
1689 console.log('Analysis state saved, opening search tab...');
1690
1691 // Clear previous competitor data
1692 await GM.deleteValue('temp_competitors');
1693 await GM.deleteValue('competitors_extracted');
1694
1695 console.log('>>> CALLING extractCompetitorsFromSearch <<<');
1696 await extractCompetitorsFromSearch(keyQuery);
1697
1698 // Wait for competitors to be extracted (poll for data)
1699 console.log('Waiting for competitor data...');
1700 let attempts = 0;
1701 const maxAttempts = 90; // 90 seconds max (increased from 60)
1702
1703 while (attempts < maxAttempts) {
1704 await new Promise(resolve => setTimeout(resolve, 1000));
1705
1706 const extracted = await GM.getValue('competitors_extracted', null);
1707 if (extracted === 'true') {
1708 console.log('Competitors extracted successfully, continuing analysis...');
1709 break;
1710 }
1711
1712 if (extracted === 'failed') {
1713 throw new Error('Не удалось извлечь данные о конкурентах из MPStats');
1714 }
1715
1716 attempts++;
1717 if (attempts % 10 === 0) {
1718 console.log(`Waiting for competitors... ${attempts}/${maxAttempts} seconds`);
1719 }
1720 }
1721
1722 if (attempts >= maxAttempts) {
1723 throw new Error('Timeout: не удалось получить данные о конкурентах за 90 секунд');
1724 }
1725
1726 // Get competitors from storage
1727 const competitorsJson = await GM.getValue('temp_competitors', null);
1728 if (!competitorsJson) {
1729 throw new Error('Не удалось получить данные о конкурентах');
1730 }
1731
1732 const competitorData = JSON.parse(competitorsJson);
1733 console.log('Competitors loaded:', competitorData);
1734
1735 // Clear state
1736 await GM.deleteValue('analysis_state');
1737 await GM.deleteValue('temp_competitors');
1738 await GM.deleteValue('competitors_extracted');
1739
1740 console.log('>>> CALLING bayesianPriceOptimizationWithAI <<<');
1741 const optimization = await bayesianPriceOptimizationWithAI(
1742 mpStatsData,
1743 productData,
1744 competitorData,
1745 coinvest,
1746 drr
1747 );
1748 console.log('>>> bayesianPriceOptimizationWithAI FINISHED <<<');
1749
1750 if (!optimization) {
1751 throw new Error('Ошибка при оптимизации цены');
1752 }
1753
1754 displayResults(optimization, coinvest, drr);
1755
1756 } catch (error) {
1757 console.error('Analysis error:', error);
1758 console.error('Error message:', error.message);
1759 console.error('Error stack:', error.stack);
1760 const resultsContent = document.getElementById('results-content');
1761 resultsContent.innerHTML = `
1762 <div style="color: #fee; padding: 10px; text-align: center;">
1763 <strong>⚠️ Ошибка:</strong><br>
1764 ${error.message || 'Неизвестная ошибка'}
1765 </div>
1766 `;
1767 resultsDiv.style.display = 'block';
1768 } finally {
1769 loadingIndicator.style.display = 'none';
1770 }
1771 }
1772
1773 // Display optimization results
1774 function displayResults(optimization, coinvest, drr) {
1775 const resultsDiv = document.getElementById('analysis-results');
1776 const resultsContent = document.getElementById('results-content');
1777
1778 const { optimal, alternatives, currentPrice, productData, allResults, aiReasoning, competitorData, historicalData } = optimization;
1779
1780 const currentDailySales = historicalData.avgDailySales;
1781
1782 // Calculate actual price from display price using user's coinvest value
1783 const displayPriceMultiplier = (100 - coinvest) / 100;
1784 const currentActualPrice = currentPrice / displayPriceMultiplier;
1785 const currentCommission = currentActualPrice * productData.commission;
1786 const currentAdvertisingCost = currentActualPrice * (drr / 100);
1787 const currentDelivery = productData.delivery;
1788 const currentDailyProfit = Math.round((currentActualPrice - productData.cost - currentCommission - currentDelivery - currentAdvertisingCost) * currentDailySales * 10) / 10;
1789 const currentDailyRevenue = Math.round(currentPrice * currentDailySales * 10) / 10;
1790 const currentMargin = Math.round(((currentActualPrice - productData.cost - currentCommission - currentDelivery - currentAdvertisingCost) / currentActualPrice) * 100 * 10) / 10;
1791 const currentProfitPerUnit = Math.round(currentActualPrice - productData.cost - currentCommission - currentDelivery - currentAdvertisingCost);
1792
1793 // Calculate percentages for current price
1794 const currentCommissionPercent = Math.round((currentCommission / currentActualPrice) * 100 * 10) / 10;
1795 const currentDeliveryPercent = Math.round((currentDelivery / currentActualPrice) * 100 * 10) / 10;
1796 const currentCostPercent = Math.round((productData.cost / currentActualPrice) * 100 * 10) / 10;
1797 const currentDrrPercent = Math.round((currentAdvertisingCost / currentActualPrice) * 100 * 10) / 10;
1798
1799 const optimalDailyRevenue = Math.round(optimal.price * optimal.estimatedDailySales * 10) / 10;
1800 const optimalProfitPerUnit = Math.round(optimal.actualPrice - productData.cost - optimal.commission - optimal.delivery - optimal.advertisingCost);
1801
1802 // Calculate percentages for optimal price
1803 const optimalCommissionPercent = Math.round((optimal.commission / optimal.actualPrice) * 100 * 10) / 10;
1804 const optimalDeliveryPercent = Math.round((optimal.delivery / optimal.actualPrice) * 100 * 10) / 10;
1805 const optimalCostPercent = Math.round((productData.cost / optimal.actualPrice) * 100 * 10) / 10;
1806 const optimalDrrPercent = Math.round((optimal.advertisingCost / optimal.actualPrice) * 100 * 10) / 10;
1807
1808 const priceChange = Math.round(((optimal.price - currentPrice) / currentPrice) * 100);
1809 const priceDiff = optimal.price - currentPrice;
1810 const actualPriceChange = Math.round(((optimal.actualPrice - currentActualPrice) / currentActualPrice) * 100);
1811 const actualPriceDiff = Math.round(optimal.actualPrice - currentActualPrice);
1812 const profitChange = Math.round(((optimal.estimatedDailyProfit - currentDailyProfit) / Math.abs(currentDailyProfit)) * 100);
1813 const profitDiff = Math.round(optimal.estimatedDailyProfit - currentDailyProfit);
1814 const revenueChange = Math.round(((optimalDailyRevenue - currentDailyRevenue) / currentDailyRevenue) * 100);
1815 const revenueDiff = Math.round(optimalDailyRevenue - currentDailyRevenue);
1816 const salesChange = Math.round(((optimal.estimatedDailySales - currentDailySales) / currentDailySales) * 100);
1817 const salesDiff = Math.round((optimal.estimatedDailySales - currentDailySales) * 10) / 10;
1818 const marginChange = Math.round((optimal.profitMargin - currentMargin) * 10) / 10;
1819 const profitPerUnitChange = currentProfitPerUnit > 0 ? Math.round(((optimalProfitPerUnit - currentProfitPerUnit) / currentProfitPerUnit) * 100) : 0;
1820 const profitPerUnitDiff = optimalProfitPerUnit - currentProfitPerUnit;
1821
1822 // Calculate percentage changes for costs
1823 const commissionPercentChange = Math.round((optimalCommissionPercent - currentCommissionPercent) * 10) / 10;
1824 const deliveryPercentChange = Math.round((optimalDeliveryPercent - currentDeliveryPercent) * 10) / 10;
1825 const costPercentChange = Math.round((optimalCostPercent - currentCostPercent) * 10) / 10;
1826 const drrPercentChange = Math.round((optimalDrrPercent - currentDrrPercent) * 10) / 10;
1827
1828 // Format AI reasoning with line breaks
1829 const formattedReasoning = aiReasoning
1830 .replace(/\. /g, '.<br>• ')
1831 .replace(/^/, '• ');
1832
1833 // Determine elasticity reliability color and text
1834 let reliabilityColor = '#ef4444'; // red for bad
1835 let reliabilityText = 'Плохо';
1836 let reliabilityIcon = '⚠️';
1837
1838 if (optimization.elasticityReliability === 'good') {
1839 reliabilityColor = '#10b981'; // green
1840 reliabilityText = 'Хорошо';
1841 reliabilityIcon = '✅';
1842 } else if (optimization.elasticityReliability === 'satisfactory') {
1843 reliabilityColor = '#f59e0b'; // orange
1844 reliabilityText = 'Удовлетворительно';
1845 reliabilityIcon = '⚡';
1846 }
1847
1848 let html = `
1849 <div style="background: rgba(59, 130, 246, 0.2); padding: 10px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #3b82f6;">
1850 <div style="font-size: 13px; font-weight: 600; margin-bottom: 6px; opacity: 0.9;">📊 Эластичность спроса:</div>
1851 <div style="font-size: 14px; line-height: 1.6; opacity: 0.95;">
1852 <div style="margin-bottom: 4px;">
1853 <strong>Коэффициент:</strong> ${optimization.aiDemandElasticity.toFixed(4)}
1854 <span style="font-size: 13px; opacity: 0.8;">(при изменении цены на 1%, продажи изменятся на ${(optimization.aiDemandElasticity * 100).toFixed(1)}%)</span>
1855 </div>
1856 <div style="margin-bottom: 4px;">
1857 <strong>Достоверность:</strong>
1858 <span style="color: ${reliabilityColor}; font-weight: 600;">${reliabilityIcon} ${reliabilityText}</span>
1859 </div>
1860 <div style="font-size: 12px; opacity: 0.8;">
1861 R² = ${optimization.elasticityR2.toFixed(4)} • ${optimization.elasticityUniquePrices} уникальных цен • ${optimization.elasticityDaysUsed} дней данных
1862 </div>
1863 </div>
1864 </div>
1865
1866 <div style="background: rgba(16, 185, 129, 0.2); padding: 10px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #10b981;">
1867 <div style="font-size: 13px; font-weight: 600; margin-bottom: 6px; opacity: 0.9;">🤖 Рекомендация AI:</div>
1868 <div style="font-size: 14px; line-height: 1.6; opacity: 0.95;">${formattedReasoning}</div>
1869 </div>
1870 `;
1871
1872 if (competitorData && competitorData.length > 0) {
1873 // Calculate total revenue for percentage
1874 const totalRevenue = competitorData.reduce((sum, c) => sum + c.revenue30days, 0);
1875
1876 html += `
1877 <div style="background: rgba(139, 92, 246, 0.2); padding: 10px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #8b5cf6;">
1878 <div style="font-size: 13px; font-weight: 600; margin-bottom: 8px; opacity: 0.9;">
1879 🏪 Конкуренты (${competitorData.length} шт):
1880 </div>
1881 <div style="overflow-x: auto;">
1882 <table style="width: 100%; border-collapse: collapse; font-size: 12px;">
1883 <thead>
1884 <tr style="border-bottom: 1px solid rgba(255,255,255,0.3);">
1885 <th style="text-align: left; padding: 6px; opacity: 0.9;">#</th>
1886 <th style="text-align: left; padding: 6px; opacity: 0.9;">Бренд</th>
1887 <th style="text-align: right; padding: 6px; opacity: 0.9;">Цена</th>
1888 <th style="text-align: right; padding: 6px; opacity: 0.9;">Продажи</th>
1889 <th style="text-align: right; padding: 6px; opacity: 0.9;">Выручка</th>
1890 <th style="text-align: right; padding: 6px; opacity: 0.9;">% выручки</th>
1891 </tr>
1892 </thead>
1893 <tbody>
1894 `;
1895
1896 competitorData.slice(0, 10).forEach((comp, index) => {
1897 const competitorUrl = comp.sku ? `https://www.ozon.ru/product/${comp.sku}/` : '#';
1898 const salesInfo = comp.sales30days ? `${comp.sales30days} шт` : 'н/д';
1899 const revenueInfo = comp.revenue30days ? `${comp.revenue30days.toLocaleString()} ₽` : 'н/д';
1900 const revenuePercent = totalRevenue > 0 ? ((comp.revenue30days / totalRevenue) * 100).toFixed(1) : '0.0';
1901
1902 html += `
1903 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
1904 <td style="padding: 6px; opacity: 0.8;">${index + 1}</td>
1905 <td style="padding: 6px;">
1906 <a href="${competitorUrl}" target="_blank" style="color: white; text-decoration: none; opacity: 0.9;">
1907 ${comp.brand || 'N/A'}
1908 </a>
1909 </td>
1910 <td style="padding: 6px; text-align: right; font-weight: 600;">${comp.price.toLocaleString()} ₽</td>
1911 <td style="padding: 6px; text-align: right;">${salesInfo}</td>
1912 <td style="padding: 6px; text-align: right; font-weight: 600;">${revenueInfo}</td>
1913 <td style="padding: 6px; text-align: right; opacity: 0.9;">${revenuePercent}%</td>
1914 </tr>
1915 `;
1916 });
1917
1918 html += `
1919 </tbody>
1920 </table>
1921 </div>
1922 </div>
1923 `;
1924
1925 // === NEW: Calculate recommended prices based on competitors ===
1926
1927 // ШАГ 1: Средневзвешенная цена (вес 40%)
1928 let weightedSum = 0;
1929 let totalWeight = 0;
1930 competitorData.forEach((comp, index) => {
1931 const position = index + 1;
1932 const weight = position <= 5 ? comp.revenue30days * 2 : comp.revenue30days;
1933 weightedSum += comp.price * weight;
1934 totalWeight += weight;
1935 });
1936 const weightedAvgPrice = totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 0;
1937
1938 // ШАГ 2: Цена с лучшей конверсией (вес 30%)
1939 const competitorsWithConversion = competitorData.map(comp => ({
1940 ...comp,
1941 conversion: comp.sales30days > 0 ? comp.sales30days / comp.price : 0
1942 }));
1943 competitorsWithConversion.sort((a, b) => b.conversion - a.conversion);
1944 const top3Conversion = competitorsWithConversion.slice(0, 3);
1945 const bestConversionPrice = top3Conversion.length > 0
1946 ? Math.round(top3Conversion.reduce((sum, c) => sum + c.price, 0) / top3Conversion.length)
1947 : 0;
1948
1949 // ШАГ 3: Медианная цена топ-10 (вес 30%)
1950 const top10Prices = competitorData.slice(0, 10).map(c => c.price).sort((a, b) => a - b);
1951 let medianPrice = 0;
1952 if (top10Prices.length > 0) {
1953 const mid = Math.floor(top10Prices.length / 2);
1954 medianPrice = top10Prices.length % 2 === 0
1955 ? Math.round((top10Prices[mid - 1] + top10Prices[mid]) / 2)
1956 : top10Prices[mid];
1957 }
1958
1959 // ШАГ 3.5: Эластичность запроса (на основе данных конкурентов)
1960 let queryElasticity = -1.5; // default
1961 let queryElasticityR2 = 0;
1962
1963 if (competitorData.length >= 5) {
1964 console.log('=== CALCULATING QUERY ELASTICITY (Cross-Sectional) ===');
1965 console.log('Using competitor data:', competitorData.map(c => `${c.brand}: ${c.price}₽, ${c.sales30days} шт/30д`));
1966
1967 // Используем лог-линейную регрессию для конкурентов
1968 const logPrices = competitorData.map(c => Math.log(c.price));
1969 const logSales = competitorData.map(c => Math.log(c.sales30days + 1));
1970
1971 console.log('Log prices:', logPrices.map(p => p.toFixed(4)));
1972 console.log('Log sales:', logSales.map(s => s.toFixed(4)));
1973
1974 const n = competitorData.length;
1975 const sumX = logPrices.reduce((a, b) => a + b, 0);
1976 const sumY = logSales.reduce((a, b) => a + b, 0);
1977 const sumXY = logPrices.reduce((a, b, i) => a + b * logSales[i], 0);
1978 const sumX2 = logPrices.reduce((a, b) => a + b * b, 0);
1979
1980 console.log(`Query elasticity regression: n=${n}, sumX=${sumX.toFixed(2)}, sumY=${sumY.toFixed(2)}, sumXY=${sumXY.toFixed(2)}, sumX2=${sumX2.toFixed(2)}`);
1981
1982 const denominator = n * sumX2 - sumX * sumX;
1983 console.log('Denominator:', denominator.toFixed(4));
1984
1985 if (denominator !== 0) {
1986 const slope = (n * sumXY - sumX * sumY) / denominator;
1987 const intercept = (sumY - slope * sumX) / n;
1988
1989 console.log(`Query OLS results: slope (elasticity) = ${slope.toFixed(4)}, intercept = ${intercept.toFixed(4)}`);
1990
1991 // Calculate R²
1992 const yMean = sumY / n;
1993 const ssTotal = logSales.reduce((s, y) => s + Math.pow(y - yMean, 2), 0);
1994 const predicted = logPrices.map(x => slope * x + intercept);
1995 const ssResidual = logSales.reduce((s, y, i) => s + Math.pow(y - predicted[i], 2), 0);
1996 const r2 = ssTotal > 0 ? 1 - (ssResidual / ssTotal) : 0;
1997
1998 console.log(`Query R²: ssTotal=${ssTotal.toFixed(4)}, ssResidual=${ssResidual.toFixed(4)}, R²=${r2.toFixed(4)}`);
1999
2000 // Clamp to reasonable range
2001 queryElasticity = Math.max(-3.0, Math.min(-0.3, slope));
2002 queryElasticityR2 = r2;
2003
2004 console.log('Query elasticity calculated:', queryElasticity.toFixed(4), 'R²:', r2.toFixed(4));
2005 console.log('⚠️ NOTE: Cross-sectional elasticity may differ from time-series elasticity due to brand effects, quality differences, and search position');
2006 } else {
2007 console.log('⚠️ Query elasticity: denominator is zero, using default -1.5');
2008 }
2009 }
2010
2011 // ШАГ 4: Базовая оптимальная цена
2012 const baseOptimalPrice = Math.round(
2013 weightedAvgPrice * 0.4 +
2014 bestConversionPrice * 0.3 +
2015 medianPrice * 0.3
2016 );
2017
2018 // ШАГ 6: Три стратегии
2019 const marketCapturePrice = Math.round(baseOptimalPrice * 0.85);
2020 const optimalStrategyPrice = baseOptimalPrice;
2021 const premiumPrice = Math.round(baseOptimalPrice * 1.15);
2022
2023 html += `
2024 <div style="background: rgba(236, 72, 153, 0.2); padding: 10px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #ec4899;">
2025 <div style="font-size: 13px; font-weight: 600; margin-bottom: 8px; opacity: 0.9;">
2026 💰 Рекомендованные цены для запроса
2027 </div>
2028
2029 <div style="margin-bottom: 10px; padding: 8px; background: rgba(255,255,255,0.1); border-radius: 6px;">
2030 <div style="font-size: 12px; font-weight: 600; margin-bottom: 6px; opacity: 0.9;">📈 Базовые метрики:</div>
2031 <div style="font-size: 13px; line-height: 1.8;">
2032 <div>• <strong>Средневзвешенная цена:</strong> ${weightedAvgPrice} ₽ <span style="opacity: 0.7;">(вес 40%)</span></div>
2033 <div>• <strong>Цена с лучшей конверсией:</strong> ${bestConversionPrice} ₽ <span style="opacity: 0.7;">(вес 30%)</span></div>
2034 <div>• <strong>Медианная цена топ-10:</strong> ${medianPrice} ₽ <span style="opacity: 0.7;">(вес 30%)</span></div>
2035 <div>• <strong>Эластичность запроса:</strong> ${queryElasticity.toFixed(4)} <span style="opacity: 0.7;">(R² = ${queryElasticityR2.toFixed(3)}, ${competitorData.length} конкурентов)</span></div>
2036 </div>
2037 </div>
2038
2039 <div style="font-size: 12px; font-weight: 600; margin-bottom: 6px; opacity: 0.9;">🎯 Стратегии ценообразования:</div>
2040 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;">
2041 <div style="background: rgba(239, 68, 68, 0.3); padding: 8px; border-radius: 6px; text-align: center;">
2042 <div style="font-size: 11px; opacity: 0.8; margin-bottom: 4px;">🔥 Захват рынка</div>
2043 <div style="font-size: 18px; font-weight: 700;">${marketCapturePrice} ₽</div>
2044 <div style="font-size: 10px; opacity: 0.7; margin-top: 2px;">-15% от базовой</div>
2045 </div>
2046 <div style="background: rgba(16, 185, 129, 0.3); padding: 8px; border-radius: 6px; text-align: center;">
2047 <div style="font-size: 11px; opacity: 0.8; margin-bottom: 4px;">⚖️ Оптимальная</div>
2048 <div style="font-size: 18px; font-weight: 700;">${optimalStrategyPrice} ₽</div>
2049 <div style="font-size: 10px; opacity: 0.7; margin-top: 2px;">базовая цена</div>
2050 </div>
2051 <div style="background: rgba(245, 158, 11, 0.3); padding: 8px; border-radius: 6px; text-align: center;">
2052 <div style="font-size: 11px; opacity: 0.8; margin-bottom: 4px;">👑 Премиум</div>
2053 <div style="font-size: 18px; font-weight: 700;">${premiumPrice} ₽</div>
2054 <div style="font-size: 10px; opacity: 0.7; margin-top: 2px;">+15% от базовой</div>
2055 </div>
2056 </div>
2057 `;
2058 }
2059
2060 html += `
2061 <div style="background: #10b981; padding: 5px 10px; border-radius: 6px; font-size: 13px; font-weight: 600; display: inline-block; margin-bottom: 10px;">
2062 ✨ ОПТИМАЛЬНАЯ ЦЕНА
2063 </div>
2064
2065 <table style="width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 14px;">
2066 <thead>
2067 <tr style="border-bottom: 2px solid rgba(255,255,255,0.3);">
2068 <th style="text-align: left; padding: 6px; font-size: 14px; opacity: 0.9;">Показатель</th>
2069 <th style="text-align: right; padding: 6px; font-size: 14px; opacity: 0.9;">Текущая</th>
2070 <th style="text-align: right; padding: 6px; font-size: 14px; opacity: 0.9;">Рекомендуемая</th>
2071 <th style="text-align: right; padding: 6px; font-size: 14px; opacity: 0.9;">Δ</th>
2072 </tr>
2073 </thead>
2074 <tbody>
2075 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2076 <td style="padding: 8px 6px; font-size: 14px;">Цена реализации</td>
2077 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentPrice} ₽</td>
2078 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2079 ${optimal.price} ₽
2080 <span style="color: ${priceChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2081 (${priceChange >= 0 ? '+' : ''}${priceChange}%)
2082 </span>
2083 </td>
2084 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${priceDiff >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2085 ${priceDiff >= 0 ? '+' : ''}${priceDiff} ₽
2086 </td>
2087 </tr>
2088 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2089 <td style="padding: 8px 6px; font-size: 14px;">Фактическая цена</td>
2090 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${Math.round(currentActualPrice)} ₽</td>
2091 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2092 ${Math.round(optimal.actualPrice)} ₽
2093 <span style="color: ${actualPriceChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2094 (${actualPriceChange >= 0 ? '+' : ''}${actualPriceChange}%)
2095 </span>
2096 </td>
2097 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${actualPriceDiff >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2098 ${actualPriceDiff >= 0 ? '+' : ''}${actualPriceDiff} ₽
2099 </td>
2100 </tr>
2101 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2102 <td style="padding: 8px 6px; font-size: 14px;">Прибыль/день</td>
2103 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentDailyProfit} ₽</td>
2104 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2105 ${optimal.estimatedDailyProfit} ₽
2106 <span style="color: ${profitChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2107 (${profitChange >= 0 ? '+' : ''}${profitChange}%)
2108 </span>
2109 </td>
2110 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${profitDiff >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2111 ${profitDiff >= 0 ? '+' : ''}${profitDiff} ₽
2112 </td>
2113 </tr>
2114 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2115 <td style="padding: 8px 6px; font-size: 14px;">Выручка/день</td>
2116 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentDailyRevenue} ₽</td>
2117 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2118 ${optimalDailyRevenue} ₽
2119 <span style="color: ${revenueChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2120 (${revenueChange >= 0 ? '+' : ''}${revenueChange}%)
2121 </span>
2122 </td>
2123 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${revenueDiff >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2124 ${revenueDiff >= 0 ? '+' : ''}${revenueDiff} ₽
2125 </td>
2126 </tr>
2127 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2128 <td style="padding: 8px 6px; font-size: 14px;">Продажи/день</td>
2129 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentDailySales.toFixed(1)} шт</td>
2130 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2131 ${optimal.estimatedDailySales} шт
2132 <span style="color: ${salesChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2133 (${salesChange >= 0 ? '+' : ''}${salesChange}%)
2134 </span>
2135 </td>
2136 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${salesDiff >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2137 ${salesDiff >= 0 ? '+' : ''}${salesDiff} шт
2138 </td>
2139 </tr>
2140 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2141 <td style="padding: 8px 6px; font-size: 14px;">Маржа %</td>
2142 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentMargin}%</td>
2143 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2144 ${optimal.profitMargin}%
2145 <span style="color: ${marginChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2146 (${marginChange >= 0 ? '+' : ''}${marginChange}%)
2147 </span>
2148 </td>
2149 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${marginChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2150 ${marginChange >= 0 ? '+' : ''}${marginChange}%
2151 </td>
2152 </tr>
2153 <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
2154 <td style="padding: 8px 6px; font-size: 14px;">Прибыль/шт</td>
2155 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentProfitPerUnit} ₽</td>
2156 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2157 ${optimalProfitPerUnit} ₽
2158 <span style="color: ${profitPerUnitChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2159 (${profitPerUnitChange >= 0 ? '+' : ''}${profitPerUnitChange}%)
2160 </span>
2161 </td>
2162 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${profitPerUnitDiff >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2163 ${profitPerUnitDiff >= 0 ? '+' : ''}${profitPerUnitDiff} ₽
2164 </td>
2165 </tr>
2166 <tr>
2167 <td style="padding: 8px 6px; font-size: 14px;">Комиссия %</td>
2168 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentCommissionPercent}%</td>
2169 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2170 ${optimalCommissionPercent}%
2171 <span style="color: ${commissionPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2172 (${commissionPercentChange >= 0 ? '+' : ''}${commissionPercentChange}%)
2173 </span>
2174 </td>
2175 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${commissionPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2176 ${commissionPercentChange >= 0 ? '+' : ''}${commissionPercentChange}%
2177 </td>
2178 </tr>
2179 <tr>
2180 <td style="padding: 8px 6px; font-size: 14px;">Доставка %</td>
2181 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentDeliveryPercent}%</td>
2182 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2183 ${optimalDeliveryPercent}%
2184 <span style="color: ${deliveryPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2185 (${deliveryPercentChange >= 0 ? '+' : ''}${deliveryPercentChange}%)
2186 </span>
2187 </td>
2188 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${deliveryPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2189 ${deliveryPercentChange >= 0 ? '+' : ''}${deliveryPercentChange}%
2190 </td>
2191 </tr>
2192 <tr>
2193 <td style="padding: 8px 6px; font-size: 14px;">Себестоимость %</td>
2194 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentCostPercent}%</td>
2195 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2196 ${optimalCostPercent}%
2197 <span style="color: ${costPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2198 (${costPercentChange >= 0 ? '+' : ''}${costPercentChange}%)
2199 </span>
2200 </td>
2201 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${costPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2202 ${costPercentChange >= 0 ? '+' : ''}${costPercentChange}%
2203 </td>
2204 </tr>
2205 <tr>
2206 <td style="padding: 8px 6px; font-size: 14px;">ДРР %</td>
2207 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">${currentDrrPercent}%</td>
2208 <td style="padding: 8px 6px; text-align: right; font-weight: 600;">
2209 ${optimalDrrPercent}%
2210 <span style="color: ${drrPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 4px;">
2211 (${drrPercentChange >= 0 ? '+' : ''}${drrPercentChange}%)
2212 </span>
2213 </td>
2214 <td style="padding: 8px 6px; text-align: right; font-weight: 600; color: ${drrPercentChange <= 0 ? '#10b981' : '#ef4444'}; font-size: 13px;">
2215 ${drrPercentChange >= 0 ? '+' : ''}${drrPercentChange}%
2216 </td>
2217 </tr>
2218 </tbody>
2219 </table>
2220 `;
2221
2222 if (alternatives && alternatives.length > 0) {
2223 html += `
2224 <div style="margin-top: 12px; padding: 8px; background: rgba(255,255,255,0.1); border-radius: 6px; font-size: 12px; opacity: 0.8;">
2225 💡 Анализ: ${allResults.length} вариантов цен. Уверенность: ${Math.round(optimal.confidence * 100)}%
2226 </div>
2227 `;
2228 }
2229
2230 resultsContent.innerHTML = html;
2231 resultsDiv.style.display = 'block';
2232
2233 setTimeout(() => createPriceChart(optimization), 100);
2234 }
2235
2236 // Create price analysis chart
2237 function createPriceChart(optimization) {
2238 const oldChart = document.getElementById('price-analysis-chart');
2239 if (oldChart) {
2240 oldChart.parentElement.remove();
2241 }
2242
2243 const canvas = document.createElement('canvas');
2244 canvas.id = 'price-analysis-chart';
2245 canvas.style.cssText = 'width: 100% !important; height: 250px !important;';
2246
2247 const chartContainer = document.createElement('div');
2248 chartContainer.style.cssText = 'margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.1); border-radius: 8px; max-width: 600px; box-sizing: border-box;';
2249 chartContainer.innerHTML = `
2250 <div style="font-size: 14px; margin-bottom: 8px; opacity: 0.9; font-weight: 600;">
2251 📈 График зависимости от цены
2252 </div>
2253 `;
2254 chartContainer.appendChild(canvas);
2255
2256 const resultsContent = document.getElementById('results-content');
2257 resultsContent.appendChild(chartContainer);
2258
2259 const sortedResults = [...optimization.allResults].sort((a, b) => a.price - b.price);
2260 const prices = sortedResults.map(r => r.price);
2261 const sales = sortedResults.map(r => r.estimatedDailySales);
2262 const revenue = sortedResults.map(r => Math.round(r.price * r.estimatedDailySales));
2263 const profit = sortedResults.map(r => r.estimatedDailyProfit);
2264
2265 const ctx = canvas.getContext('2d');
2266 window.priceAnalysisChart = new Chart(ctx, {
2267 type: 'line',
2268 data: {
2269 labels: prices,
2270 datasets: [
2271 {
2272 label: 'Продажи (шт/д)',
2273 data: sales,
2274 borderColor: '#10b981',
2275 backgroundColor: 'rgba(16, 185, 129, 0.1)',
2276 yAxisID: 'y',
2277 tension: 0.4,
2278 borderWidth: 2
2279 },
2280 {
2281 label: 'Выручка (₽/д)',
2282 data: revenue,
2283 borderColor: '#f59e0b',
2284 backgroundColor: 'rgba(245, 158, 11, 0.1)',
2285 yAxisID: 'y1',
2286 tension: 0.4,
2287 borderWidth: 2
2288 },
2289 {
2290 label: 'Прибыль (₽/д)',
2291 data: profit,
2292 borderColor: '#ef4444',
2293 backgroundColor: 'rgba(239, 68, 68, 0.1)',
2294 yAxisID: 'y1',
2295 tension: 0.4,
2296 borderWidth: 3
2297 }
2298 ]
2299 },
2300 options: {
2301 responsive: true,
2302 maintainAspectRatio: true,
2303 aspectRatio: 2.2,
2304 interaction: {
2305 mode: 'index',
2306 intersect: false,
2307 },
2308 plugins: {
2309 legend: {
2310 position: 'top',
2311 labels: {
2312 usePointStyle: true,
2313 padding: 12,
2314 font: { size: 12 },
2315 color: 'white',
2316 boxWidth: 8,
2317 boxHeight: 8
2318 }
2319 },
2320 tooltip: {
2321 backgroundColor: 'rgba(0, 0, 0, 0.8)',
2322 padding: 10,
2323 titleFont: { size: 13 },
2324 bodyFont: { size: 12 },
2325 callbacks: {
2326 title: function(context) {
2327 return 'Цена: ' + context[0].label + ' ₽';
2328 },
2329 label: function(context) {
2330 let label = context.dataset.label || '';
2331 if (label) {
2332 label += ': ';
2333 }
2334 if (context.parsed.y !== null) {
2335 if (label.includes('шт')) {
2336 label += context.parsed.y.toFixed(1);
2337 } else {
2338 label += Math.round(context.parsed.y);
2339 }
2340 }
2341 return label;
2342 }
2343 }
2344 }
2345 },
2346 scales: {
2347 x: {
2348 title: {
2349 display: true,
2350 text: 'Цена (₽)',
2351 font: { size: 12, weight: 'bold' },
2352 color: 'white'
2353 },
2354 ticks: {
2355 maxTicksLimit: 8,
2356 font: { size: 11 },
2357 color: 'rgba(255, 255, 255, 0.8)'
2358 },
2359 grid: {
2360 color: 'rgba(255, 255, 255, 0.1)'
2361 }
2362 },
2363 y: {
2364 type: 'linear',
2365 display: true,
2366 position: 'left',
2367 title: {
2368 display: true,
2369 text: 'Продажи',
2370 color: '#10b981',
2371 font: { size: 12 }
2372 },
2373 ticks: {
2374 font: { size: 11 },
2375 color: 'rgba(255, 255, 255, 0.8)'
2376 },
2377 grid: {
2378 color: 'rgba(255, 255, 255, 0.1)'
2379 }
2380 },
2381 y1: {
2382 type: 'linear',
2383 display: true,
2384 position: 'right',
2385 title: {
2386 display: true,
2387 text: 'Выручка/Прибыль',
2388 color: '#f59e0b',
2389 font: { size: 12 }
2390 },
2391 grid: {
2392 drawOnChartArea: false,
2393 },
2394 ticks: {
2395 font: { size: 11 },
2396 color: 'rgba(255, 255, 255, 0.8)'
2397 }
2398 }
2399 }
2400 }
2401 });
2402
2403 console.log('График создан успешно');
2404 }
2405
2406 // Wait for MP Stats widget to load
2407 function waitForMPStats() {
2408 console.log('Waiting for MP Stats widget and page hydration...');
2409
2410 let checkCount = 0;
2411 const maxChecks = 30;
2412
2413 const checkInterval = setInterval(() => {
2414 checkCount++;
2415 console.log(`Check ${checkCount}/${maxChecks} for MP Stats widget...`);
2416
2417 const mpsWidget = document.querySelector('.mps-sidebar');
2418 const chartSvg = mpsWidget ? mpsWidget.querySelector('.vue-apexcharts svg') : null;
2419 const bars = chartSvg ? chartSvg.querySelectorAll('.apexcharts-bar-area') : null;
2420
2421 if (mpsWidget && chartSvg && bars && bars.length > 0) {
2422 console.log(`MP Stats widget and chart found with ${bars.length} bars, waiting for hydration to complete...`);
2423 clearInterval(checkInterval);
2424 setTimeout(() => {
2425 console.log('Creating analysis widget now...');
2426 createAnalysisWidget();
2427 }, 3000);
2428 } else if (checkCount >= maxChecks) {
2429 clearInterval(checkInterval);
2430 console.log('Stopped waiting for MP Stats widget - timeout reached');
2431 }
2432 }, 1000);
2433 }
2434
2435 // Initialize
2436 function init() {
2437 console.log('OZON Price Optimizer initialized (v2.0.1)');
2438 console.log('Current URL:', window.location.href);
2439
2440 // Check if we're on a search page and need to extract competitors
2441 if (window.location.href.includes('/search/') || window.location.href.includes('/category/')) {
2442 console.log('Search/category page detected, checking for competitor extraction...');
2443 extractCompetitorsInSearchPage();
2444 return;
2445 }
2446
2447 const productId = getProductId();
2448 if (!productId) {
2449 console.log('Not a product page, skipping...');
2450 return;
2451 }
2452
2453 console.log('Product page detected, waiting for MP Stats widget...');
2454
2455 if (document.readyState === 'loading') {
2456 document.addEventListener('DOMContentLoaded', waitForMPStats);
2457 } else {
2458 waitForMPStats();
2459 }
2460 }
2461
2462 init();
2463})();