OZON Price Optimizer with Bayesian Analysis

Analyzes MP Stats data and suggests optimal pricing using Bayesian methods to maximize profit

Size

65.0 KB

Version

1.2.77

Created

Dec 13, 2025

Updated

about 2 months ago

1// ==UserScript==
2// @name		OZON Price Optimizer with Bayesian Analysis
3// @description		Analyzes MP Stats data and suggests optimal pricing using Bayesian methods to maximize profit
4// @version		1.2.77
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    // Product data mapping (артикул -> себестоимость, комиссия, доставка)
16    const PRODUCT_DATA = {
17        '1740824669': { cost: 146.4, commission: 0.39, delivery: 105 },
18        '240637697': { cost: 159.6, commission: 0.39, delivery: 105 },
19      
20    };
21
22    // Extract product ID from URL
23    function getProductId() {
24        const match = window.location.href.match(/product\/[^\/]+-(\d+)/);
25        return match ? match[1] : null;
26    }
27
28    // Get short SKU from full product ID (last 5 digits)
29    function getShortSku(fullProductId) {
30        // Try to find exact match first
31        if (PRODUCT_DATA[fullProductId]) {
32            return fullProductId;
33        }
34        
35        // Try last 5 digits
36        const last5 = fullProductId.slice(-5);
37        if (PRODUCT_DATA[last5]) {
38            return last5;
39        }
40        
41        // Try last 6 digits
42        const last6 = fullProductId.slice(-6);
43        if (PRODUCT_DATA[last6]) {
44            return last6;
45        }
46        
47        return null;
48    }
49
50    // Extract daily price and sales data from MP Stats chart
51    async function extractDailyDataFromChart(widget) {
52        console.log('>>> extractDailyDataFromChart CALLED (v1.2.21 - REAL COORDINATES) <<<');
53        
54        try {
55            const dailyData = [];
56            
57            // Ищем SVG графики
58            const svgs = widget.querySelectorAll('svg.apexcharts-svg');
59            console.log('Found ApexCharts SVG elements:', svgs.length);
60            
61            if (svgs.length === 0) {
62                console.log('No SVG charts found');
63                return dailyData;
64            }
65            
66            // Берем первый график (график продаж и цены)
67            const salesSvg = svgs[0];
68            console.log('Analyzing sales chart (SVG 0)...');
69            
70            // Ищем все path элементы с классом apexcharts-bar-area
71            const bars = salesSvg.querySelectorAll('path.apexcharts-bar-area');
72            console.log(`Found ${bars.length} bar elements`);
73            
74            if (bars.length === 0) {
75                console.log('No bars found in sales chart');
76                return dailyData;
77            }
78            
79            console.log('Starting tooltip extraction with real coordinates...');
80            
81            // Извлекаем данные через симуляцию наведения мыши с реальными координатами
82            for (let index = 0; index < bars.length; index++) {
83                const bar = bars[index];
84                
85                // Получаем координаты центра столбца
86                const rect = bar.getBoundingClientRect();
87                const centerX = rect.left + rect.width / 2;
88                const centerY = rect.top + rect.height / 2;
89                
90                // Симулируем наведение мыши с реальными координатами
91                bar.dispatchEvent(new MouseEvent('mousemove', { 
92                    bubbles: true, 
93                    cancelable: true, 
94                    view: window,
95                    clientX: centerX,
96                    clientY: centerY
97                }));
98                
99                // Ждем появления и обновления тултипа
100                await new Promise(resolve => setTimeout(resolve, 150));
101                
102                // Ищем кастомный тултип MP Stats
103                const tooltip = document.querySelector('.chart-custom-tooltip');
104                
105                if (!tooltip) {
106                    console.log(`Day ${index + 1}: Custom tooltip not found`);
107                    continue;
108                }
109                
110                const tooltipText = tooltip.textContent;
111                
112                // Извлекаем продажи
113                const salesMatch = tooltipText.match(/Продажи:\s*(\d+)\s*шт/);
114                const sales = salesMatch ? parseInt(salesMatch[1]) : null;
115                
116                // Извлекаем цену
117                const priceMatch = tooltipText.match(/Цена:\s*(\d+)\s*₽/);
118                const price = priceMatch ? parseInt(priceMatch[1]) : null;
119                
120                if (sales !== null && price !== null && sales > 0 && price > 0) {
121                    dailyData.push({
122                        day: index + 1,
123                        sales: sales,
124                        price: price
125                    });
126                } else {
127                    console.log(`Day ${index + 1}: Invalid data - sales: ${sales}, price: ${price}`);
128                }
129            }
130            
131            console.log(`Total extracted daily data points: ${dailyData.length}`);
132            console.log('Sample data - First 5 days:', dailyData.slice(0, 5));
133            console.log('Sample data - Last 5 days:', dailyData.slice(-5));
134            return dailyData;
135            
136        } catch (error) {
137            console.error('Error extracting daily data:', error);
138            return [];
139        }
140    }
141
142    // Extract stock data from MP Stats stock chart
143    function extractStockData() {
144        console.log('Extracting stock data from MP Stats...');
145        
146        const mpsWidget = document.querySelector('.mps-sidebar');
147        if (!mpsWidget) {
148            console.error('MP Stats widget not found');
149            return null;
150        }
151        
152        // Find all SVG charts
153        const svgs = mpsWidget.querySelectorAll('.vue-apexcharts svg');
154        if (!svgs || svgs.length < 2) {
155            console.error('Stock chart SVG not found');
156            return null;
157        }
158        
159        // Second chart is the stock chart
160        const stockSvg = svgs[1];
161        const stockBars = stockSvg.querySelectorAll('.apexcharts-bar-area');
162        
163        if (!stockBars || stockBars.length === 0) {
164            console.error('No stock bars found');
165            return null;
166        }
167
168        const stockPoints = [];
169        stockBars.forEach((bar, index) => {
170            const stock = parseInt(bar.getAttribute('val'));
171            if (!isNaN(stock)) {
172                stockPoints.push({
173                    day: index + 1,
174                    stock: stock
175                });
176            }
177        });
178
179        console.log(`Extracted ${stockPoints.length} days of stock data from chart`);
180        return stockPoints;
181    }
182
183    // Extract competitor data from product page
184    async function extractCompetitorData() {
185        console.log('=== START extractCompetitorData ===');
186        try {
187            console.log('Extracting competitor data...');
188            
189            // Get current product name to identify competitors
190            console.log('Looking for product name element...');
191            const productNameElement = document.querySelector('[data-widget="webProductHeading"] h1');
192            console.log('Product name element found:', !!productNameElement);
193            
194            if (!productNameElement) {
195                console.error('Product name not found');
196                return null;
197            }
198            
199            const productName = productNameElement.textContent.trim();
200            console.log('Product name:', productName);
201            
202            const words = productName.split(' ');
203            console.log('Words:', words);
204            
205            // Get first 2 words for comparison
206            const firstWord = words[0].toLowerCase();
207            const secondWord = words[1].toLowerCase();
208            console.log('Searching for competitors with first word:', firstWord, 'and second word:', secondWord);
209            
210            const competitors = [];
211            
212            // Look for competitor products in recommendation sections
213            // Find all divs with class containing "bq03" (product name containers)
214            const productNameDivs = document.querySelectorAll('div[class*="bq03"]');
215            console.log(`Found ${productNameDivs.length} product name divs on page`);
216            
217            const processedSkus = new Set();
218            const currentProductId = getProductId();
219            
220            productNameDivs.forEach((nameDiv, index) => {
221                try {
222                    // Get product name from span inside
223                    const nameSpan = nameDiv.querySelector('span.tsBody500Medium');
224                    if (!nameSpan) {
225                        console.log(`Div ${index}: No name span found`);
226                        return;
227                    }
228                    
229                    const competitorName = nameSpan.textContent.trim();
230                    const competitorWords = competitorName.split(' ');
231                    const competitorFirstWord = competitorWords[0].toLowerCase();
232                    const competitorSecondWord = competitorWords[1] ? competitorWords[1].toLowerCase() : '';
233                    
234                    console.log(`Div ${index}: ${competitorName} (first 2 words: ${competitorFirstWord} ${competitorSecondWord})`);
235                    
236                    // Check if it's a competitor (same first 2 words)
237                    if (competitorFirstWord !== firstWord || competitorSecondWord !== secondWord) {
238                        console.log(`Div ${index}: Skipping - different words (${competitorFirstWord} ${competitorSecondWord} vs ${firstWord} ${secondWord})`);
239                        return;
240                    }
241                    
242                    // Find the product link to get SKU
243                    const container = nameDiv.closest('div[class*="tile"]') || nameDiv.closest('div');
244                    if (!container) {
245                        console.log(`Div ${index}: No container found`);
246                        return;
247                    }
248                    
249                    const productLink = container.querySelector('a[href*="/product/"]');
250                    if (!productLink) {
251                        console.log(`Div ${index}: No product link found`);
252                        return;
253                    }
254                    
255                    // Get SKU from link
256                    const match = productLink.href.match(/product\/[^\/]+-(\d+)/);
257                    if (!match) {
258                        console.log(`Div ${index}: No SKU in link`);
259                        return;
260                    }
261                    
262                    const sku = match[1];
263                    
264                    // Skip current product and already processed
265                    if (sku === currentProductId || processedSkus.has(sku)) {
266                        console.log(`Div ${index}: Skipping - current product or already processed`);
267                        return;
268                    }
269                    processedSkus.add(sku);
270                    
271                    // Find price in the same container
272                    const priceSpans = container.querySelectorAll('span');
273                    let price = null;
274                    
275                    for (const span of priceSpans) {
276                        const text = span.textContent.trim();
277                        // Look for price pattern (digits with optional spaces and ₽)
278                        if (text.match(/^\d[\d\s]*₽?$/)) {
279                            const priceText = text.replace(/[^\d]/g, '');
280                            const parsedPrice = parseInt(priceText);
281                            if (!isNaN(parsedPrice) && parsedPrice > 100 && parsedPrice < 10000) {
282                                price = parsedPrice;
283                                break;
284                            }
285                        }
286                    }
287                    
288                    if (!price) {
289                        console.log(`Div ${index}: No valid price found`);
290                        return;
291                    }
292                    
293                    // Try to get MP Stats data
294                    let sales30days = null;
295                    let revenue30days = null;
296                    
297                    const mpstatsContainer = container.querySelector('[class*="container_ihu0b"], .mpstats-loader');
298                    if (mpstatsContainer) {
299                        const text = mpstatsContainer.textContent;
300                        
301                        // Look for sales data
302                        const salesMatch = text.match(/(\d+)\s*шт/);
303                        if (salesMatch) {
304                            sales30days = parseInt(salesMatch[1]);
305                            // Calculate revenue as price x sales
306                            revenue30days = price * sales30days;
307                        }
308                    }
309                    
310                    competitors.push({
311                        name: competitorName,
312                        price: price,
313                        sales30days: sales30days,
314                        revenue30days: revenue30days,
315                        sku: sku
316                    });
317                    
318                    console.log(`Div ${index}: Added competitor - ${competitorName}, Price: ${price}, Sales: ${sales30days}, Revenue: ${revenue30days}, SKU: ${sku}`);
319                } catch (error) {
320                    console.error(`Div ${index}: Error extracting competitor data:`, error);
321                }
322            });
323            
324            console.log(`Found ${competitors.length} competitors on product page`);
325            
326            // If we found competitors on the page, return them
327            if (competitors.length > 0) {
328                console.log(`Total competitors extracted from page: ${competitors.length}`);
329                
330                // Sort competitors by revenue (highest first)
331                competitors.sort((a, b) => {
332                    const revenueA = a.revenue30days || 0;
333                    const revenueB = b.revenue30days || 0;
334                    return revenueB - revenueA;
335                });
336                
337                return competitors;
338            }
339            
340            console.log('No competitors found on page');
341            return null;
342            
343        } catch (error) {
344            console.error('Error in extractCompetitorData:', error);
345            return null;
346        }
347    }
348
349    // Extract summary data from MP Stats widget
350    async function extractMPStatsData() {
351        console.log('Extracting MP Stats summary data...');
352        
353        const widget = document.querySelector('.mps-sidebar');
354        if (!widget) {
355            console.error('MP Stats widget not found');
356            return null;
357        }
358
359        // Extract summary data
360        const revenueText = widget.textContent.match(/Выручка за 30 суток\s*([\d\s]+)/);
361        const salesText = widget.textContent.match(/Продаж за 30 суток\s*([\d\s]+)/);
362        const currentStockText = widget.textContent.match(/Текущий остаток\s*([\d\s]+)/);
363        
364        if (revenueText && salesText) {
365            const revenue = parseInt(revenueText[1].replace(/\s/g, ''));
366            const sales = parseInt(salesText[1].replace(/\s/g, ''));
367            
368            // Get minimum price from webPrice widget
369            let avgPrice = revenue / sales; // fallback
370            const webPriceWidget = document.querySelector('[data-widget="webPrice"]');
371            if (webPriceWidget) {
372                const priceSpans = webPriceWidget.querySelectorAll('span');
373                const prices = [];
374                priceSpans.forEach(span => {
375                    const text = span.textContent.trim();
376                    // Look for price pattern (digits with optional spaces and ₽)
377                    const match = text.match(/^(\d[\d\s]*)\s*₽$/);
378                    if (match) {
379                        const priceText = match[1].replace(/\s/g, '');
380                        const parsedPrice = parseInt(priceText);
381                        if (!isNaN(parsedPrice) && parsedPrice > 100 && parsedPrice < 100000) {
382                            prices.push(parsedPrice);
383                        }
384                    }
385                });
386                if (prices.length > 0) {
387                    avgPrice = Math.min(...prices);
388                    console.log('Found prices in webPrice:', prices, 'Using minimum:', avgPrice);
389                }
390            }
391            
392            const currentStock = currentStockText ? parseInt(currentStockText[1].replace(/\s/g, '')) : 0;
393            
394            // Extract daily data from chart
395            const dataPoints = await extractDailyDataFromChart(widget);
396            
397            if (!dataPoints || dataPoints.length === 0) {
398                console.error('Failed to extract chart data');
399                return null;
400            }
401
402            // Extract stock data from second chart
403            const stockPoints = extractStockData();
404
405            // Calculate average daily sales from actual data
406            const totalSales = dataPoints.reduce((sum, d) => sum + d.sales, 0);
407            const avgDailySales = totalSales / dataPoints.length;
408            
409            console.log('Extracted summary data:', { 
410                revenue, 
411                sales, 
412                avgPrice, 
413                currentStock, 
414                avgDailySales,
415                daysOfData: dataPoints.length,
416                stockDataPoints: stockPoints ? stockPoints.length : 0
417            });
418            
419            return {
420                revenue,
421                sales,
422                avgPrice,
423                currentStock,
424                avgDailySales,
425                dataPoints,
426                stockPoints
427            };
428        }
429
430        return null;
431    }
432
433    // AI-powered Bayesian price optimization
434    async function bayesianPriceOptimizationWithAI(historicalData, productData, competitorData) {
435        console.log('Running AI-powered Bayesian price optimization...');
436        console.log('Historical data:', historicalData);
437        console.log('Product data:', productData);
438        console.log('Competitor data:', competitorData);
439        
440        try {
441            // Prepare daily sales data for AI
442            const dailySalesInfo = historicalData.dataPoints
443                .map(d => `День ${d.day}: ${d.sales} продаж по цене ${d.price}`)
444                .join('\n');
445            
446            // Calculate unique prices used
447            const uniquePrices = [...new Set(historicalData.dataPoints.map(d => d.price))];
448            const priceChanges = uniquePrices.length;
449            
450            console.log(`Historical data contains ${historicalData.dataPoints.length} days with ${priceChanges} unique prices:`, uniquePrices);
451            
452            // Prepare stock data for AI if available
453            let stockInfo = '';
454            if (historicalData.stockPoints && historicalData.stockPoints.length > 0) {
455                stockInfo = '\n\nДАННЫЕ ОБ ОСТАТКАХ ПО ДНЯМ:\n' + 
456                    historicalData.stockPoints
457                        .map(d => `День ${d.day}: ${d.stock} шт на складе`)
458                        .join('\n');
459            }
460
461            // Prepare competitor data for AI if available
462            let competitorInfo = '';
463            if (competitorData && competitorData.length > 0) {
464                competitorInfo = '\n\nДАННЫЕ КОНКУРЕНТОВ (товары с тем же первым словом в названии):\n';
465                competitorData.forEach((comp, index) => {
466                    competitorInfo += `${index + 1}. Цена: ${comp.price}`;
467                    if (comp.sales30days) {
468                        competitorInfo += `, Продажи за 30 дней: ${comp.sales30days} шт`;
469                        const avgDailySales = (comp.sales30days / 30).toFixed(1);
470                        competitorInfo += ` (${avgDailySales} шт/день)`;
471                    }
472                    if (comp.revenue30days) {
473                        competitorInfo += `, Выручка за 30 дней: ${comp.revenue30days}`;
474                    }
475                    if (comp.sku) {
476                        competitorInfo += `, SKU: ${comp.sku}`;
477                    }
478                    competitorInfo += '\n';
479                });
480                
481                // Calculate competitor statistics
482                const prices = competitorData.map(c => c.price);
483                const minCompPrice = Math.min(...prices);
484                const maxCompPrice = Math.max(...prices);
485                const avgCompPrice = Math.round(prices.reduce((a, b) => a + b, 0) / prices.length);
486                
487                competitorInfo += '\nСтатистика конкурентов:\n';
488                competitorInfo += `- Минимальная цена: ${minCompPrice} ₽\n`;
489                competitorInfo += `- Максимальная цена: ${maxCompPrice} ₽\n`;
490                competitorInfo += `- Средняя цена: ${avgCompPrice} ₽\n`;
491                competitorInfo += `- Количество конкурентов: ${competitorData.length}\n`;
492            }
493
494            const commissionPercent = Math.round(productData.commission * 100);
495            
496            const aiPrompt = `Ты эксперт по ценообразованию на маркетплейсах. Проанализируй данные товара и предложи оптимальную цену для МАКСИМИЗАЦИИ ОБЩЕЙ ПРИБЫЛИ В ДЕНЬ.
497
498ДАННЫЕ ТОВАРА:
499- Выручка за 30 дней: ${historicalData.revenue}500- Продаж за 30 дней: ${historicalData.sales} шт
501- Текущая цена: ${Math.round(historicalData.avgPrice)}502- Текущий остаток: ${historicalData.currentStock} ед
503- Среднее кол-во продаж в день: ${historicalData.avgDailySales.toFixed(1)} шт
504- Себестоимость: ${productData.cost}505- Комиссия маркетплейса: ${commissionPercent}% от цены
506- Стоимость доставки: ${productData.delivery} ₽ за единицу
507
508ДЕТАЛЬНЫЕ ДАННЫЕ ПО ДНЯМ (последние 30 дней):
509${dailySalesInfo}${stockInfo}${competitorInfo}
510
511КРИТИЧЕСКИ ВАЖНО - ФОРМУЛА РАСЧЁТА ПРИБЫЛИ:
512Чистая цена = Цена - (Цена × ${productData.commission}) - ${productData.delivery}
513Прибыль с единицы = Чистая цена - Себестоимость
514ОБЩАЯ ПРИБЫЛЬ В ДЕНЬ = Прибыль с единицы × Количество продаж в день
515
516ПРИМЕР РАСЧЁТА:
517При цене 504₽:
518- Чистая цена = 504 - (504×${productData.commission}) - ${productData.delivery} = 504 - ${Math.round(504 * productData.commission)} - ${productData.delivery} = ${Math.round(504 - 504 * productData.commission - productData.delivery)}519- Прибыль с единицы = ${Math.round(504 - 504 * productData.commission - productData.delivery - productData.cost)}520- При продажах ${historicalData.avgDailySales.toFixed(1)} шт/день
521- ОБЩАЯ прибыль = ${Math.round(504 - 504 * productData.commission - productData.delivery - productData.cost)}₽ × ${historicalData.avgDailySales.toFixed(1)} = ${Math.round((504 - 504 * productData.commission - productData.delivery - productData.cost) * historicalData.avgDailySales)}₽/день
522
523ТВОЯ ЗАДАЧА: Найти цену, которая максимизирует ОБЩУЮ ПРИБЫЛЬ В ДЕНЬ, а не выручку или количество продаж!
524
525Проанализируй используя продвинутые методы:
5261. Адаптивную байесовскую модель для определения эластичности спроса
5272. Томпсоновское сэмплирование для учёта неопределённости
5283. Гауссовские процессы для моделирования зависимости продаж от цены
5294. Multi-Armed Bandit подход для балансировки exploration/exploitation
5305. Учти динамику продаж по дням - если видишь тренд снижения или роста
5316. ВАЖНО: Проанализируй корреляцию между остатками и продажами - низкие остатки могут ограничивать продажи
5327. ВАЖНО: Учти конкурентное окружение - цены и продажи конкурентов помогут определить оптимальную позицию на рынке
5338. КРИТИЧЕСКИ ВАЖНО: Из-за высокой себестоимости (${productData.cost}₽) и фиксированных расходов (комиссия ${commissionPercent}% + доставка ${productData.delivery}₽), снижение цены очень сильно снижает прибыль с единицы. Рост продаж должен компенсировать это снижение!
534
535Верни JSON с рекомендациями:
536- optimalPrice: оптимальная цена для максимизации прибыли
537- demandElasticity: коэффициент эластичности спроса (обычно от -0.5 до -3.0)
538- minPrice: минимальная рекомендуемая цена (должна покрывать все расходы)
539- maxPrice: максимальная рекомендуемая цена
540- confidence: уровень уверенности (0-1)
541- reasoning: подробное объяснение логики (2-3 предложения, почему именно эта цена максимизирует ОБЩУЮ прибыль, с конкретными цифрами прибыли)
542- explorationPrices: массив из 3 цен для дополнительного тестирования (exploration)`;
543
544            const aiResponse = await RM.aiCall(aiPrompt, {
545                type: 'json_schema',
546                json_schema: {
547                    name: 'price_optimization',
548                    schema: {
549                        type: 'object',
550                        properties: {
551                            optimalPrice: { 
552                                type: 'number',
553                                description: 'Оптимальная цена для максимизации прибыли'
554                            },
555                            demandElasticity: { 
556                                type: 'number',
557                                description: 'Коэффициент эластичности спроса (обычно от -0.5 до -3.0)'
558                            },
559                            minPrice: { 
560                                type: 'number',
561                                description: 'Минимальная рекомендуемая цена'
562                            },
563                            maxPrice: { 
564                                type: 'number',
565                                description: 'Максимальная рекомендуемая цена'
566                            },
567                            confidence: { 
568                                type: 'number',
569                                description: 'Уровень уверенности в рекомендации (0-1)'
570                            },
571                            reasoning: { 
572                                type: 'string',
573                                description: 'Подробное объяснение логики расчёта (2-3 предложения)'
574                            },
575                            explorationPrices: {
576                                type: 'array',
577                                items: { type: 'number' },
578                                description: 'Массив из 3 цен для дополнительного тестирования'
579                            }
580                        },
581                        required: ['optimalPrice', 'demandElasticity', 'minPrice', 'maxPrice', 'confidence', 'reasoning', 'explorationPrices']
582                    }
583                }
584            });
585
586            console.log('AI response:', aiResponse);
587
588            // Generate price range based on AI recommendations
589            const priceRange = [];
590            const step = (aiResponse.maxPrice - aiResponse.minPrice) / 20;
591            
592            for (let price = aiResponse.minPrice; price <= aiResponse.maxPrice; price += step) {
593                priceRange.push(Math.round(price));
594            }
595            
596            // Add exploration prices from AI
597            aiResponse.explorationPrices.forEach(p => {
598                if (!priceRange.includes(Math.round(p))) {
599                    priceRange.push(Math.round(p));
600                }
601            });
602            
603            priceRange.sort((a, b) => a - b);
604
605            // Рассчитываем показатели для каждой цены с учётом эластичности от ИИ
606            const results = priceRange.map(price => {
607                const priceRatio = price / historicalData.avgPrice;
608                const demandMultiplier = Math.pow(priceRatio, aiResponse.demandElasticity);
609                const estimatedSales = historicalData.avgDailySales * demandMultiplier;
610                
611                // Расчёт с учётом комиссии 30% и доставки 90₽
612                const commission = price * 0.30;
613                const delivery = 90;
614                const netPrice = price - commission - delivery;
615                const profit = (netPrice - productData.cost) * estimatedSales;
616                const margin = ((netPrice - productData.cost) / price) * 100;
617                
618                return {
619                    price: Math.round(price * 10) / 10,
620                    estimatedDailySales: Math.round(estimatedSales * 10) / 10,
621                    estimatedDailyProfit: Math.round(profit * 10) / 10,
622                    profitMargin: Math.round(margin * 10) / 10,
623                    confidence: aiResponse.confidence,
624                    commission: Math.round(commission * 10) / 10,
625                    delivery: delivery,
626                    netPrice: Math.round(netPrice * 10) / 10
627                };
628            });
629
630            // Find the optimal price recommended by AI
631            let optimalResult = results.find(r => r.price === aiResponse.optimalPrice);
632            
633            // If exact price not found, calculate it
634            if (!optimalResult) {
635                const price = aiResponse.optimalPrice;
636                const priceRatio = price / historicalData.avgPrice;
637                const demandMultiplier = Math.pow(priceRatio, aiResponse.demandElasticity);
638                const estimatedSales = historicalData.avgDailySales * demandMultiplier;
639                
640                const commission = price * 0.30;
641                const delivery = 90;
642                const netPrice = price - commission - delivery;
643                const profit = (netPrice - productData.cost) * estimatedSales;
644                const margin = ((netPrice - productData.cost) / price) * 100;
645                
646                optimalResult = {
647                    price: Math.round(price * 10) / 10,
648                    estimatedDailySales: Math.round(estimatedSales * 10) / 10,
649                    estimatedDailyProfit: Math.round(profit * 10) / 10,
650                    profitMargin: Math.round(margin * 10) / 10,
651                    confidence: aiResponse.confidence,
652                    commission: Math.round(commission * 10) / 10,
653                    delivery: delivery,
654                    netPrice: Math.round(netPrice * 10) / 10
655                };
656            }
657
658            // Get alternatives from AI's exploration prices
659            const alternatives = [];
660            aiResponse.explorationPrices.forEach(explorePrice => {
661                if (explorePrice !== aiResponse.optimalPrice) {
662                    let altResult = results.find(r => r.price === explorePrice);
663                    
664                    if (!altResult) {
665                        const priceRatio = explorePrice / historicalData.avgPrice;
666                        const demandMultiplier = Math.pow(priceRatio, aiResponse.demandElasticity);
667                        const estimatedSales = historicalData.avgDailySales * demandMultiplier;
668                        
669                        const commission = explorePrice * 0.30;
670                        const delivery = 90;
671                        const netPrice = explorePrice - commission - delivery;
672                        const profit = (netPrice - productData.cost) * estimatedSales;
673                        const margin = ((netPrice - productData.cost) / explorePrice) * 100;
674                        
675                        altResult = {
676                            price: Math.round(explorePrice * 10) / 10,
677                            estimatedDailySales: Math.round(estimatedSales * 10) / 10,
678                            estimatedDailyProfit: Math.round(profit * 10) / 10,
679                            profitMargin: Math.round(margin * 10) / 10,
680                            confidence: aiResponse.confidence,
681                            commission: Math.round(commission * 10) / 10,
682                            delivery: delivery,
683                            netPrice: Math.round(netPrice * 10) / 10
684                        };
685                    }
686                    
687                    alternatives.push(altResult);
688                }
689            });
690
691            return {
692                optimal: optimalResult,
693                alternatives: alternatives,
694                currentPrice: Math.round(historicalData.avgPrice),
695                productData: productData,
696                allResults: results,
697                aiReasoning: aiResponse.reasoning,
698                aiConfidence: aiResponse.confidence,
699                aiDemandElasticity: aiResponse.demandElasticity,
700                explorationPrices: aiResponse.explorationPrices,
701                competitorData: competitorData,
702                historicalData: historicalData
703            };
704
705        } catch (error) {
706            console.error('AI analysis failed:', error);
707            throw error;
708        }
709    }
710
711    // Create and inject the analysis widget
712    function createAnalysisWidget() {
713        console.log('OZON Price Optimizer initialized');
714        
715        // Check if widget already exists
716        if (document.getElementById('bayesian-price-optimizer')) {
717            console.log('Widget already exists');
718            return;
719        }
720
721        const widget = document.createElement('div');
722        widget.id = 'bayesian-price-optimizer';
723        widget.innerHTML = `
724            <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 
725                        color: white; 
726                        padding: 15px; 
727                        border-radius: 12px; 
728                        margin: 15px 0; 
729                        box-shadow: 0 4px 15px rgba(0,0,0,0.2);
730                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
731                        max-width: 40%;
732                        box-sizing: border-box;">
733                <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
734                    <h3 style="margin: 0; font-size: 16px; font-weight: 600;">
735                        🎯 Оптимизация цены (AI + Bayesian)
736                    </h3>
737                    <button id="analyze-price-btn" style="background: white; 
738                                                           color: #667eea; 
739                                                           border: none; 
740                                                           padding: 8px 16px; 
741                                                           border-radius: 8px; 
742                                                           cursor: pointer; 
743                                                           font-weight: 600;
744                                                           font-size: 13px;
745                                                           transition: all 0.3s;
746                                                           flex-shrink: 0;">
747                        Анализировать
748                    </button>
749                </div>
750                <div id="analysis-results" style="display: none; margin-top: 15px;">
751                    <div style="background: rgba(255,255,255,0.15); 
752                                padding: 12px; 
753                                border-radius: 8px; 
754                                backdrop-filter: blur(10px);
755                                max-width: 100%;
756                                overflow: hidden;
757                                box-sizing: border-box;">
758                        <div id="results-content" style="max-width: 100%; overflow-wrap: break-word; word-wrap: break-word;"></div>
759                    </div>
760                </div>
761                <div id="loading-indicator" style="display: none; text-align: center; padding: 20px;">
762                    <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>
763                    <p style="margin-top: 10px; font-size: 13px;">Анализируем данные с помощью AI...</p>
764                </div>
765            </div>
766        `;
767
768        // Add CSS animation
769        const style = document.createElement('style');
770        style.textContent = `
771            @keyframes spin {
772                to { transform: rotate(360deg); }
773            }
774            #analyze-price-btn:hover {
775                transform: translateY(-2px);
776                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
777            }
778            #bayesian-price-optimizer * {
779                box-sizing: border-box;
780            }
781        `;
782        document.head.appendChild(style);
783
784        // Find MP Stats widget and insert our widget after it
785        const mpsWidget = document.querySelector('.mps-sidebar');
786        if (mpsWidget) {
787            mpsWidget.parentElement.insertBefore(widget, mpsWidget.nextSibling);
788            console.log('Widget inserted after MP Stats');
789        } else {
790            document.body.appendChild(widget);
791            console.log('Widget inserted at body end');
792        }
793
794        // Add click handler
795        const analyzeBtn = document.getElementById('analyze-price-btn');
796        analyzeBtn.addEventListener('click', performAnalysis);
797    }
798
799    // Perform the price analysis
800    async function performAnalysis() {
801        console.log('Starting price analysis...');
802        
803        const loadingIndicator = document.getElementById('loading-indicator');
804        const resultsDiv = document.getElementById('analysis-results');
805        const resultsContent = document.getElementById('results-content');
806        
807        loadingIndicator.style.display = 'block';
808        resultsDiv.style.display = 'none';
809
810        try {
811            // Get product ID
812            const productId = getProductId();
813            console.log('Product ID:', productId);
814            
815            if (!productId) {
816                throw new Error('Не удалось определить ID товара');
817            }
818
819            // Get product data using short SKU
820            const shortSku = getShortSku(productId);
821            console.log('Short SKU:', shortSku);
822            
823            if (!shortSku) {
824                throw new Error('Данные для этого товара не найдены');
825            }
826            
827            const productData = PRODUCT_DATA[shortSku];
828            console.log('Product data:', productData);
829            
830            if (!productData) {
831                throw new Error('Данные для этого товара не найдены');
832            }
833
834            // Extract MP Stats data
835            const mpStatsData = await extractMPStatsData();
836            if (!mpStatsData) {
837                throw new Error('Не удалось извлечь данные из MP Stats');
838            }
839
840            // Extract competitor data
841            console.log('About to call extractCompetitorData...');
842            const competitorData = await extractCompetitorData();
843            console.log('Competitor data:', competitorData);
844
845            // Run Bayesian optimization with AI
846            const optimization = await bayesianPriceOptimizationWithAI(mpStatsData, productData, competitorData);
847            
848            if (!optimization) {
849                throw new Error('Ошибка при оптимизации цены');
850            }
851
852            // Display results
853            displayResults(optimization);
854            
855        } catch (error) {
856            console.error('Analysis error:', error);
857            resultsContent.innerHTML = `
858                <div style="color: #fee; padding: 10px; text-align: center;">
859                    <strong>⚠️ Ошибка:</strong><br>
860                    ${error.message}
861                </div>
862            `;
863            resultsDiv.style.display = 'block';
864        } finally {
865            loadingIndicator.style.display = 'none';
866        }
867    }
868
869    // Display optimization results
870    function displayResults(optimization) {
871        const resultsDiv = document.getElementById('analysis-results');
872        const resultsContent = document.getElementById('results-content');
873        
874        const { optimal, alternatives, currentPrice, productData, allResults, aiReasoning, competitorData, historicalData } = optimization;
875        
876        // Calculate current metrics using ACTUAL historical data
877        const currentDailySales = historicalData.avgDailySales; // Use actual historical sales
878        const currentCommission = currentPrice * productData.commission;
879        const currentDelivery = productData.delivery;
880        const currentNetPrice = currentPrice - currentCommission - currentDelivery;
881        const currentDailyProfit = Math.round((currentNetPrice - productData.cost) * currentDailySales);
882        const currentDailyRevenue = Math.round(currentPrice * currentDailySales);
883        const currentMargin = Math.round(((currentNetPrice - productData.cost) / currentPrice) * 100 * 10) / 10;
884        const currentProfitPerUnit = Math.round(currentNetPrice - productData.cost);
885        
886        // Calculate optimal revenue
887        const optimalDailyRevenue = Math.round(optimal.price * optimal.estimatedDailySales);
888        const optimalProfitPerUnit = Math.round(optimal.netPrice - productData.cost);
889        
890        // Calculate changes in percentages
891        const priceChange = Math.round(((optimal.price - currentPrice) / currentPrice) * 100);
892        const priceDiff = optimal.price - currentPrice;
893        const profitChange = currentDailyProfit > 0 ? Math.round(((optimal.estimatedDailyProfit - currentDailyProfit) / currentDailyProfit) * 100) : 0;
894        const profitDiff = optimal.estimatedDailyProfit - currentDailyProfit;
895        const revenueChange = currentDailyRevenue > 0 ? Math.round(((optimalDailyRevenue - currentDailyRevenue) / currentDailyRevenue) * 100) : 0;
896        const revenueDiff = optimalDailyRevenue - currentDailyRevenue;
897        const salesChange = currentDailySales > 0 ? Math.round(((optimal.estimatedDailySales - currentDailySales) / currentDailySales) * 100) : 0;
898        const salesDiff = Math.round((optimal.estimatedDailySales - currentDailySales) * 10) / 10;
899        const marginChange = Math.round((optimal.profitMargin - currentMargin) * 10) / 10;
900        const profitPerUnitChange = currentProfitPerUnit > 0 ? Math.round(((optimalProfitPerUnit - currentProfitPerUnit) / currentProfitPerUnit) * 100) : 0;
901        const profitPerUnitDiff = optimalProfitPerUnit - currentProfitPerUnit;
902        
903        let html = `
904            <div style="background: rgba(16, 185, 129, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 15px; border-left: 4px solid #10b981;">
905                <div style="font-size: 12px; font-weight: 600; margin-bottom: 8px; opacity: 0.9;">🤖 Рекомендация AI:</div>
906                <div style="font-size: 13px; line-height: 1.5; opacity: 0.95; word-wrap: break-word; white-space: normal; overflow-wrap: break-word;">${aiReasoning}</div>
907                <div style="font-size: 11px; margin-top: 8px; opacity: 0.8; border-top: 1px solid rgba(255,255,255,0.2); padding-top: 8px;">
908                    📊 Эластичность спроса: <strong>${historicalData.dataPoints.length} дней данных, ${[...new Set(optimization.historicalData.dataPoints.map(d => d.price))].length} уникальных цен</strong> → коэффициент <strong>${Math.abs(optimization.aiDemandElasticity).toFixed(2)}</strong>
909                </div>
910            </div>
911        `;
912
913        // Add competitor info if available
914        if (competitorData && competitorData.length > 0) {
915            html += `
916                <div style="background: rgba(59, 130, 246, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 15px; border-left: 4px solid #3b82f6;">
917                    <div style="font-size: 12px; font-weight: 600; margin-bottom: 4px; opacity: 0.9;">
918                        🏪 Анализ конкурентов (${competitorData.length} товаров):
919                    </div>
920            `;
921            
922            // Show ALL competitors with links, sales, and revenue
923            competitorData.forEach((comp, index) => {
924                const competitorUrl = comp.sku ? `https://www.ozon.ru/product/${comp.sku}/` : '#';
925                const salesInfo = comp.sales30days ? `${comp.sales30days} шт/30д (${(comp.sales30days / 30).toFixed(1)} шт/день)` : '';
926                const revenueInfo = comp.revenue30days ? `${comp.revenue30days.toLocaleString('ru-RU')} ₽/30д` : '';
927                
928                html += `
929                    <div style="font-size: 12px; padding: 6px 0; opacity: 0.9;">
930                        ${index + 1}. <a href="${competitorUrl}" target="_blank" style="color: white; text-decoration: underline;">${comp.price} ₽</a>${salesInfo}${revenueInfo}
931                    </div>
932                `;
933            });
934            
935            html += '</div>';
936        } else {
937            html += `
938                <div style="background: rgba(156, 163, 175, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 15px; border-left: 4px solid #9ca3af;">
939                    <div style="font-size: 12px; font-weight: 600; margin-bottom: 4px; opacity: 0.9;">
940                        🏪 Анализ конкурентов:
941                    </div>
942                    <div style="font-size: 11px; opacity: 0.8;">
943                        Конкуренты не найдены на странице товара
944                    </div>
945                </div>
946            `;
947        }
948
949        html += `
950            <div style="background: #10b981; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; display: inline-block; margin-bottom: 12px;">
951                ✨ ОПТИМАЛЬНАЯ ЦЕНА
952            </div>
953            
954            <table style="width: 100%; border-collapse: collapse; margin-top: 10px;">
955                <thead>
956                    <tr style="border-bottom: 2px solid rgba(255,255,255,0.3);">
957                        <th style="text-align: left; padding: 8px; font-size: 13px; opacity: 0.9;">Показатель</th>
958                        <th style="text-align: right; padding: 8px; font-size: 13px; opacity: 0.9;">Текущая</th>
959                        <th style="text-align: right; padding: 8px; font-size: 13px; opacity: 0.9;">Рекомендуемая</th>
960                        <th style="text-align: right; padding: 8px; font-size: 13px; opacity: 0.9;">Разница</th>
961                    </tr>
962                </thead>
963                <tbody>
964                    <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
965                        <td style="padding: 10px 8px; font-size: 13px;">Цена</td>
966                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">${currentPrice} ₽</td>
967                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">
968                            ${optimal.price}969                            <span style="color: ${priceChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; margin-left: 5px; font-weight: 700;">
970                                (${priceChange >= 0 ? '+' : ''}${priceChange}%)
971                            </span>
972                        </td>
973                        <td style="padding: 10px 8px; text-align: right; font-weight: 600; color: ${priceDiff >= 0 ? '#10b981' : '#ef4444'};">
974                            ${priceDiff >= 0 ? '+' : ''}${priceDiff}975                        </td>
976                    </tr>
977                    <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
978                        <td style="padding: 10px 8px; font-size: 13px;">Прибыль/день</td>
979                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">${currentDailyProfit} ₽</td>
980                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">
981                            ${optimal.estimatedDailyProfit}982                            <span style="color: ${profitChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; margin-left: 5px; font-weight: 700;">
983                                (${profitChange >= 0 ? '+' : ''}${profitChange}%)
984                            </span>
985                        </td>
986                        <td style="padding: 10px 8px; text-align: right; font-weight: 600; color: ${profitDiff >= 0 ? '#10b981' : '#ef4444'};">
987                            ${profitDiff >= 0 ? '+' : ''}${profitDiff}988                        </td>
989                    </tr>
990                    <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
991                        <td style="padding: 10px 8px; font-size: 13px;">Выручка/день</td>
992                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">${currentDailyRevenue} ₽</td>
993                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">
994                            ${optimalDailyRevenue}995                            <span style="color: ${revenueChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; margin-left: 5px; font-weight: 700;">
996                                (${revenueChange >= 0 ? '+' : ''}${revenueChange}%)
997                            </span>
998                        </td>
999                        <td style="padding: 10px 8px; text-align: right; font-weight: 600; color: ${revenueDiff >= 0 ? '#10b981' : '#ef4444'};">
1000                            ${revenueDiff >= 0 ? '+' : ''}${revenueDiff}1001                        </td>
1002                    </tr>
1003                    <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
1004                        <td style="padding: 10px 8px; font-size: 13px;">Продажи/день</td>
1005                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">${currentDailySales} шт</td>
1006                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">
1007                            ${optimal.estimatedDailySales} шт 
1008                            <span style="color: ${salesChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; margin-left: 5px; font-weight: 700;">
1009                                (${salesChange >= 0 ? '+' : ''}${salesChange}%)
1010                            </span>
1011                        </td>
1012                        <td style="padding: 10px 8px; text-align: right; font-weight: 600; color: ${salesDiff >= 0 ? '#10b981' : '#ef4444'};">
1013                            ${salesDiff >= 0 ? '+' : ''}${salesDiff} шт
1014                        </td>
1015                    </tr>
1016                    <tr style="border-bottom: 1px solid rgba(255,255,255,0.1);">
1017                        <td style="padding: 10px 8px; font-size: 13px;">Маржа %</td>
1018                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">${currentMargin}%</td>
1019                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">
1020                            ${optimal.profitMargin}% 
1021                            <span style="color: ${marginChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; margin-left: 5px; font-weight: 700;">
1022                                (${marginChange >= 0 ? '+' : ''}${marginChange}%)
1023                            </span>
1024                        </td>
1025                        <td style="padding: 10px 8px; text-align: right; font-weight: 600; color: ${marginChange >= 0 ? '#10b981' : '#ef4444'};">
1026                            ${marginChange >= 0 ? '+' : ''}${marginChange}%
1027                        </td>
1028                    </tr>
1029                    <tr>
1030                        <td style="padding: 10px 8px; font-size: 13px;">Прибыль/шт</td>
1031                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">${currentProfitPerUnit} ₽</td>
1032                        <td style="padding: 10px 8px; text-align: right; font-weight: 600;">
1033                            ${optimalProfitPerUnit}1034                            <span style="color: ${profitPerUnitChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 5px;">
1035                                (${profitPerUnitChange >= 0 ? '+' : ''}${profitPerUnitChange}%)
1036                            </span>
1037                        </td>
1038                        <td style="padding: 10px 8px; text-align: right; font-weight: 600; color: ${profitPerUnitDiff >= 0 ? '#10b981' : '#ef4444'};">
1039                            ${profitPerUnitDiff >= 0 ? '+' : ''}${profitPerUnitDiff}1040                        </td>
1041                    </tr>
1042                </tbody>
1043            </table>
1044        `;
1045
1046        if (alternatives && alternatives.length > 0) {
1047            html += `
1048                <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid rgba(255,255,255,0.1); width: 100%; box-sizing: border-box;">
1049                    <div style="font-size: 13px; margin-bottom: 10px; opacity: 0.9; font-weight: 600;">
1050                        📊 Альтернативные варианты:
1051                    </div>
1052            `;
1053            
1054            alternatives.slice(0, 3).forEach((alt, index) => {
1055                const altProfitChange = currentDailyProfit > 0 ? Math.round(((alt.estimatedDailyProfit - currentDailyProfit) / currentDailyProfit) * 100) : 0;
1056                html += `
1057                    <div style="font-size: 14px; padding: 10px; opacity: 0.95; border-radius: 6px; margin-bottom: 8px; background: rgba(255,255,255,0.1);">
1058                        <strong>${index + 2}. ${alt.price} ₽</strong> → <strong>${alt.estimatedDailyProfit} ₽/день</strong>
1059                        <span style="color: ${altProfitChange >= 0 ? '#10b981' : '#ef4444'}; font-size: 13px; font-weight: 700; margin-left: 5px;">
1060                            (${altProfitChange >= 0 ? '+' : ''}${altProfitChange}%)
1061                        </span>
1062                        <span style="opacity: 0.8; margin-left: 5px;">(${alt.estimatedDailySales} шт)</span>
1063                    </div>
1064                `;
1065            });
1066            
1067            html += '</div>';
1068        }
1069
1070        html += `
1071            <div style="margin-top: 15px; padding: 10px; background: rgba(255,255,255,0.1); border-radius: 6px; font-size: 11px; opacity: 0.8;">
1072                💡 Анализ использует AI + байесовский подход с учетом всех ${allResults.length} вариантов цен. 
1073                Уверенность: ${Math.round(optimal.confidence * 100)}%
1074            </div>
1075        `;
1076
1077        resultsContent.innerHTML = html;
1078        resultsDiv.style.display = 'block';
1079        
1080        // Create chart after displaying results
1081        setTimeout(() => createPriceChart(optimization), 100);
1082    }
1083
1084    // Create price analysis chart
1085    function createPriceChart(optimization) {
1086        // Remove old chart if exists
1087        const oldChart = document.getElementById('price-analysis-chart');
1088        if (oldChart) {
1089            oldChart.parentElement.remove();
1090        }
1091        
1092        const canvas = document.createElement('canvas');
1093        canvas.id = 'price-analysis-chart';
1094        canvas.style.cssText = 'width: 100% !important; height: 300px !important;';
1095        
1096        const chartContainer = document.createElement('div');
1097        chartContainer.style.cssText = 'margin-top: 15px; padding: 15px; background: rgba(255,255,255,0.1); border-radius: 8px; width: 100%; box-sizing: border-box;';
1098        chartContainer.innerHTML = `
1099            <div style="font-size: 13px; margin-bottom: 10px; opacity: 0.9; font-weight: 600;">
1100                📈 График зависимости показателей от цены
1101            </div>
1102        `;
1103        chartContainer.appendChild(canvas);
1104        
1105        const resultsContent = document.getElementById('results-content');
1106        resultsContent.appendChild(chartContainer);
1107        
1108        // Prepare data for chart
1109        const sortedResults = [...optimization.allResults].sort((a, b) => a.price - b.price);
1110        const prices = sortedResults.map(r => r.price);
1111        const sales = sortedResults.map(r => r.estimatedDailySales);
1112        const revenue = sortedResults.map(r => Math.round(r.price * r.estimatedDailySales));
1113        const profit = sortedResults.map(r => r.estimatedDailyProfit);
1114        
1115        // Create chart using Chart.js
1116        const ctx = canvas.getContext('2d');
1117        window.priceAnalysisChart = new Chart(ctx, {
1118            type: 'line',
1119            data: {
1120                labels: prices,
1121                datasets: [
1122                    {
1123                        label: 'Продажи (шт/день)',
1124                        data: sales,
1125                        borderColor: '#10b981',
1126                        backgroundColor: 'rgba(16, 185, 129, 0.1)',
1127                        yAxisID: 'y',
1128                        tension: 0.4
1129                    },
1130                    {
1131                        label: 'Выручка (₽/день)',
1132                        data: revenue,
1133                        borderColor: '#f59e0b',
1134                        backgroundColor: 'rgba(245, 158, 11, 0.1)',
1135                        yAxisID: 'y1',
1136                        tension: 0.4
1137                    },
1138                    {
1139                        label: 'Прибыль (₽/день)',
1140                        data: profit,
1141                        borderColor: '#ef4444',
1142                        backgroundColor: 'rgba(239, 68, 68, 0.1)',
1143                        yAxisID: 'y1',
1144                        tension: 0.4,
1145                        borderWidth: 3
1146                    }
1147                ]
1148            },
1149            options: {
1150                responsive: true,
1151                maintainAspectRatio: true,
1152                aspectRatio: 1.5,
1153                interaction: {
1154                    mode: 'index',
1155                    intersect: false,
1156                },
1157                plugins: {
1158                    legend: {
1159                        position: 'top',
1160                        labels: {
1161                            usePointStyle: true,
1162                            padding: 15,
1163                            font: {
1164                                size: 11
1165                            },
1166                            color: 'white'
1167                        }
1168                    },
1169                    tooltip: {
1170                        backgroundColor: 'rgba(0, 0, 0, 0.8)',
1171                        padding: 12,
1172                        titleFont: {
1173                            size: 13
1174                        },
1175                        bodyFont: {
1176                            size: 12
1177                        },
1178                        callbacks: {
1179                            title: function(context) {
1180                                return 'Цена: ' + context[0].label + ' ₽';
1181                            },
1182                            label: function(context) {
1183                                let label = context.dataset.label || '';
1184                                if (label) {
1185                                    label += ': ';
1186                                }
1187                                if (context.parsed.y !== null) {
1188                                    if (label.includes('шт')) {
1189                                        label += context.parsed.y.toFixed(1);
1190                                    } else {
1191                                        label += Math.round(context.parsed.y);
1192                                    }
1193                                }
1194                                return label;
1195                            }
1196                        }
1197                    }
1198                },
1199                scales: {
1200                    x: {
1201                        title: {
1202                            display: true,
1203                            text: 'Цена (₽)',
1204                            font: {
1205                                size: 12,
1206                                weight: 'bold'
1207                            },
1208                            color: 'white'
1209                        },
1210                        ticks: {
1211                            maxTicksLimit: 10,
1212                            font: {
1213                                size: 10
1214                            },
1215                            color: 'rgba(255, 255, 255, 0.8)'
1216                        },
1217                        grid: {
1218                            color: 'rgba(255, 255, 255, 0.1)'
1219                        }
1220                    },
1221                    y: {
1222                        type: 'linear',
1223                        display: true,
1224                        position: 'left',
1225                        title: {
1226                            display: true,
1227                            text: 'Продажи (шт/день)',
1228                            color: '#10b981',
1229                            font: {
1230                                size: 11
1231                            }
1232                        },
1233                        ticks: {
1234                            font: {
1235                                size: 10
1236                            },
1237                            color: 'rgba(255, 255, 255, 0.8)'
1238                        },
1239                        grid: {
1240                            color: 'rgba(255, 255, 255, 0.1)'
1241                        }
1242                    },
1243                    y1: {
1244                        type: 'linear',
1245                        display: true,
1246                        position: 'right',
1247                        title: {
1248                            display: true,
1249                            text: 'Выручка/Прибыль (₽/день)',
1250                            color: '#f59e0b',
1251                            font: {
1252                                size: 11
1253                            }
1254                        },
1255                        grid: {
1256                            drawOnChartArea: false,
1257                        },
1258                        ticks: {
1259                            font: {
1260                                size: 10
1261                            },
1262                            color: 'rgba(255, 255, 255, 0.8)'
1263                        }
1264                    }
1265                }
1266            }
1267        });
1268        
1269        console.log('График создан успешно');
1270    }
1271
1272    // Wait for MP Stats widget to load
1273    function waitForMPStats() {
1274        const checkInterval = setInterval(() => {
1275            const mpsWidget = document.querySelector('.mps-sidebar');
1276            const chartSvg = mpsWidget ? mpsWidget.querySelector('.vue-apexcharts svg') : null;
1277            const bars = chartSvg ? chartSvg.querySelectorAll('.apexcharts-bar-area') : null;
1278            
1279            if (mpsWidget && chartSvg && bars && bars.length > 0) {
1280                console.log('MP Stats widget and chart found, creating analysis widget...');
1281                clearInterval(checkInterval);
1282                setTimeout(createAnalysisWidget, 1000);
1283            }
1284        }, 1000);
1285
1286        // Stop checking after 30 seconds
1287        setTimeout(() => {
1288            clearInterval(checkInterval);
1289            console.log('Stopped waiting for MP Stats widget');
1290        }, 30000);
1291    }
1292
1293    // Initialize
1294    function init() {
1295        console.log('OZON Price Optimizer initialized');
1296        
1297        // Check if we're on a product page
1298        const productId = getProductId();
1299        if (!productId) {
1300            console.log('Not a product page, skipping...');
1301            return;
1302        }
1303
1304        console.log('Product page detected, waiting for MP Stats widget...');
1305        
1306        // Wait for page to load
1307        if (document.readyState === 'loading') {
1308            document.addEventListener('DOMContentLoaded', waitForMPStats);
1309        } else {
1310            waitForMPStats();
1311        }
1312    }
1313
1314    init();
1315})();
OZON Price Optimizer with Bayesian Analysis | Robomonkey