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

3 days 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