File size: 23,133 Bytes
d80bf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3638939
 
 
 
 
 
 
d80bf0f
 
 
 
 
 
3ed53b8
d80bf0f
 
403c184
 
d80bf0f
 
403c184
d80bf0f
 
 
 
 
3ed53b8
d80bf0f
3ed53b8
d80bf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
403c184
d80bf0f
 
 
 
 
3ed53b8
3638939
d80bf0f
3ed53b8
d80bf0f
 
c4a34a5
d80bf0f
63bd99c
 
c4a34a5
63bd99c
c4a34a5
9c6c702
 
c4a34a5
9c6c702
 
 
 
 
 
 
 
 
 
c4a34a5
63bd99c
c4a34a5
63bd99c
 
9c6c702
 
 
c4a34a5
 
63bd99c
c4a34a5
 
63bd99c
 
 
3638939
 
d80bf0f
 
3638939
 
 
 
 
 
e9be307
3638939
 
 
 
 
 
 
d80bf0f
3638939
 
 
 
d80bf0f
 
63bd99c
 
3638939
 
 
 
 
d80bf0f
c4a34a5
d80bf0f
 
 
 
c4a34a5
d80bf0f
3ed53b8
d80bf0f
 
 
3ed53b8
d80bf0f
a758bd8
 
 
 
 
 
 
 
 
 
 
 
 
d80bf0f
 
a758bd8
d80bf0f
a758bd8
 
 
 
 
 
 
 
d80bf0f
 
 
 
 
 
 
 
 
a758bd8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d80bf0f
 
 
 
 
a758bd8
d80bf0f
a758bd8
d80bf0f
a758bd8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dba493e
a758bd8
 
 
d80bf0f
 
 
 
 
a758bd8
 
d80bf0f
 
 
 
 
 
 
 
a758bd8
d80bf0f
a758bd8
d80bf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
a758bd8
 
d80bf0f
3ed53b8
 
 
403c184
3ed53b8
 
 
d80bf0f
 
 
 
 
 
 
8140f70
 
8eb6e9f
 
8140f70
d80bf0f
 
 
 
 
 
 
 
1572a5e
 
 
 
d80bf0f
 
3ed53b8
d80bf0f
3ed53b8
 
 
403c184
 
 
 
3ed53b8
 
d80bf0f
403c184
d80bf0f
 
 
3ed53b8
 
 
 
 
 
 
d80bf0f
 
 
 
 
 
 
 
 
 
 
 
3ed53b8
3638939
 
 
 
 
 
 
d80bf0f
c4a34a5
 
 
 
 
 
 
 
 
d80bf0f
 
 
3ed53b8
c4a34a5
d80bf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ed53b8
d80bf0f
dba493e
d80bf0f
 
 
 
 
 
 
3ed53b8
d80bf0f
 
 
3638939
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d80bf0f
 
 
 
 
 
403c184
 
 
 
d80bf0f
 
 
e7c4b2b
 
 
 
d80bf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c4b2b
 
 
 
 
d80bf0f
e7c4b2b
 
 
 
 
d80bf0f
 
 
 
 
 
 
 
 
 
403c184
 
 
d80bf0f
 
 
 
 
 
 
 
be65ff7
d80bf0f
 
be65ff7
1e80531
d80bf0f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# Wildberries Analytics Dashboard
# Updated for Hugging Face Spaces deployment

import gradio as gr
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import os
import json
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots

# Import custom modules
from wildberries_client import WildberriesAPI
from forecasting import InventoryForecaster
from dashboard import (
    create_sales_dashboard, 
    create_inventory_dashboard,
    create_wb_kpi_cards,
    create_commission_analysis_chart,
    validate_and_process_wb_data
)
from config import get_config
import utils

# Initialize configuration
config = get_config()

def initialize_wb_client(api_token=None):
    """Initialize Wildberries API client with error handling"""
    try:
        # Only use token provided through Gradio interface
        if not api_token or api_token.strip() == "":
            return None
        
        client = WildberriesAPI(api_token.strip())
        return client
    except Exception as e:
        gr.Error(f"Failed to initialize Wildberries client: {str(e)}")
        return None

def get_sales_data(period, api_token=None, start_date=None, end_date=None):
    """Fetch sales data with fallback to demo data"""
    wb_client = initialize_wb_client(api_token)
    
    if wb_client is None:
        # Use demo data when API is not available
        return utils.load_demo_sales_data(period)
    
    try:
        if period == "week":
            date_from = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
        elif period == "month":
            date_from = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
        else:
            date_from = start_date if start_date else (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
        
        data = wb_client.get_sales(date_from)
        return data
        
    except Exception as e:
        gr.Warning(f"API error: {str(e)}. Using demo data.")
        return utils.load_demo_sales_data(period)

def analyze_sales_performance(period, api_token):
    """Analyze sales performance for the specified period with enhanced Wildberries metrics"""
    try:
        data = get_sales_data(period, api_token)
        
        if data.empty:
            return "No sales data available for the selected period.", None, None, pd.DataFrame()
        
        # Calculate daily revenue data ONCE to ensure consistency between chart and table
        daily_revenue_data = None
        daily_revenue_table = pd.DataFrame()
        
        if 'sale_date' in data.columns and 'total_price' in data.columns:
            # Create aggregation dictionary based on available columns
            agg_dict = {
                'total_price': 'sum',
                'quantity': 'sum'
            }
            
            # Add optional columns if they exist
            if 'sales_commission' in data.columns:
                agg_dict['sales_commission'] = 'sum'
            if 'amount_for_pay' in data.columns:
                agg_dict['amount_for_pay'] = 'sum'
                
            daily_revenue_data = data.groupby(data['sale_date'].dt.date).agg(agg_dict).reset_index()
            
            # Create the daily revenue breakdown table
            daily_revenue_table = pd.DataFrame({
                'Date': daily_revenue_data['sale_date'].astype(str),
                'Revenue (โ‚ฝ)': daily_revenue_data['total_price'].round(2),
                'Orders': daily_revenue_data['quantity'].astype(int),
                'Commission (โ‚ฝ)': (daily_revenue_data['sales_commission'].round(2) if 'sales_commission' in daily_revenue_data.columns else 0),
                'Net Revenue (โ‚ฝ)': (daily_revenue_data['amount_for_pay'].round(2) if 'amount_for_pay' in daily_revenue_data.columns else daily_revenue_data['total_price'].round(2))
            })
            
            # Sort by date descending (most recent first)  
            daily_revenue_table = daily_revenue_table.sort_values('Date', ascending=False)
        
        # Get enhanced KPIs for Wildberries data
        kpis = create_wb_kpi_cards(data)
        
        # Create sales summary with Wildberries-specific metrics
        mode_indicator = "Demo Mode" if not api_token or api_token.strip() == "" else "Live Data"
        
        summary = f"""
        ## Sales Performance - Last {period.capitalize()} ({mode_indicator})
        
        ### ๐Ÿ“Š Core Metrics
        - **Total Revenue**: โ‚ฝ{kpis.get('total_revenue', 0):,.2f}
        - **Total Orders**: {kpis.get('total_orders', 0):,}
        - **Average Order Value**: โ‚ฝ{kpis.get('avg_order_value', 0):.2f}
        - **Best Seller**: {kpis.get('top_product', 'N/A')}
        
        ### ๐Ÿ’ฐ Wildberries Metrics
        - **Total Commission**: โ‚ฝ{kpis.get('total_commission', 0):,.2f}
        - **Commission Rate**: {kpis.get('avg_commission_rate', 0):.1f}%
        - **Net Revenue (After Fees)**: โ‚ฝ{kpis.get('total_payout', 0):,.2f}
        - **Platform Fees**: โ‚ฝ{kpis.get('platform_fees', 0):,.2f}
        - **Net Margin**: {kpis.get('net_margin_percent', 0):.1f}%
        
        ### ๐Ÿšš Operations
        - **Top Office**: {kpis.get('top_office', 'N/A')}
        - **Total Delivery Cost**: โ‚ฝ{kpis.get('total_delivery_cost', 0):,.2f}
        - **Daily Sales Velocity**: {kpis.get('daily_sales_velocity', 0):.1f} orders/day
        """
        
        # Create main sales visualization with pre-calculated daily data
        main_chart = create_sales_dashboard(data, period, daily_revenue_data)
        
        # Create commission analysis if commission data is available
        commission_chart = None
        if 'sales_commission' in data.columns:
            commission_chart = create_commission_analysis_chart(data)
        
        return summary, main_chart, commission_chart, daily_revenue_table
        
    except Exception as e:
        error_msg = f"Error analyzing sales: {str(e)}"
        gr.Error(error_msg)
        return error_msg, None, None, pd.DataFrame()

def calculate_stockout_forecast(method, api_token):
    """Calculate days until stockout for products"""
    try:
        # Get current inventory (demo data if API unavailable)
        wb_client = initialize_wb_client(api_token)
        
        # Determine if we're in demo mode
        use_demo_mode = not api_token or api_token.strip() == ""
        
        if wb_client and not use_demo_mode:
            try:
                inventory_data = wb_client.get_stocks()
                # If API returns empty data, fall back to demo
                if inventory_data.empty:
                    inventory_data = utils.load_demo_inventory_data()
                    use_demo_mode = True
            except Exception:
                inventory_data = utils.load_demo_inventory_data()
                use_demo_mode = True
        else:
            inventory_data = utils.load_demo_inventory_data()
            use_demo_mode = True
        
        # Get sales data for forecasting - ensure consistency with inventory data source
        if use_demo_mode:
            sales_data = utils.load_demo_sales_data("month")
        else:
            sales_data = get_sales_data("month", api_token)
            # If API sales data is empty, fall back to demo
            if sales_data.empty:
                sales_data = utils.load_demo_sales_data("month")
        
        # Initialize forecaster
        forecaster = InventoryForecaster()
        
        # Calculate forecasts
        forecasts = []
        for _, item in inventory_data.iterrows():
            product_sales = sales_data[sales_data['product_id'] == item['product_id']]
            
            # If no sales data for this product, use average daily sales of 1
            if product_sales.empty:
                avg_daily_sales = 1
                max_daily_sales = 1
            else:
                avg_daily_sales = product_sales['quantity'].mean()
                max_daily_sales = product_sales['quantity'].max()
            
            if method == "simple":
                days_left = forecaster.simple_division_method(
                    item['current_stock'], 
                    avg_daily_sales
                )
            elif method == "safety_stock":
                days_left = forecaster.safety_stock_method(
                    item['current_stock'],
                    avg_daily_sales,
                    max_daily_sales,
                    avg_lead_time=7,
                    max_lead_time=14
                )
            elif method == "weighted":
                if not product_sales.empty:
                    days_left = forecaster.weighted_average_method(
                        item['current_stock'],
                        product_sales
                    )
                else:
                    days_left = forecaster.simple_division_method(
                        item['current_stock'],
                        avg_daily_sales
                    )
            else:
                days_left = forecaster.seasonal_adjustment_method(
                    item['current_stock'],
                    avg_daily_sales,
                    seasonal_factor=1.0
                )
            
            # Risk categorization
            if days_left < 7:
                risk_level = "๐Ÿ”ด Critical"
            elif days_left < 14:
                risk_level = "๐ŸŸก Warning"
            else:
                risk_level = "๐ŸŸข Safe"
            
            forecasts.append({
                'Product': item['product_name'],
                'Current Stock': item['current_stock'],
                'Avg Daily Sales': round(avg_daily_sales, 2),
                'Days Until Stockout': round(days_left, 1),
                'Risk Level': risk_level
            })
        
        # Create results DataFrame
        results_df = pd.DataFrame(forecasts)
        
        if results_df.empty:
            # Return all 3 required values for Gradio
            return "No inventory data available.", pd.DataFrame(), None
        
        # Sort by days until stockout
        results_df = results_df.sort_values('Days Until Stockout')
        
        # Create summary
        critical_items = len(results_df[results_df['Days Until Stockout'] < 7])
        warning_items = len(results_df[results_df['Days Until Stockout'].between(7, 14)])
        
        mode_indicator = "Demo Mode" if use_demo_mode else "Live Data"
        summary = f"""
        ## Inventory Forecast - {method.replace('_', ' ').title()} Method ({mode_indicator})
        
        - **Critical Items** (< 7 days): {critical_items}
        - **Warning Items** (7-14 days): {warning_items}
        - **Safe Items** (> 14 days): {len(results_df) - critical_items - warning_items}
        """
        
        # Create visualization
        chart = create_inventory_dashboard(results_df)
        
        return summary, results_df, chart
        
    except Exception as e:
        error_msg = f"Error calculating forecast: {str(e)}"
        gr.Error(error_msg)
        # Return all 3 required values for Gradio
        return error_msg, pd.DataFrame(), None

def update_status(api_token):
    """Update API status based on token"""
    if not api_token or api_token.strip() == "":
        return "๐Ÿ”ด Demo Mode - Enter API token above for live data"
    else:
        return "๐ŸŸข API Token Configured - Ready for live data"

# Create Gradio interface
def create_interface():
    """Create the main Gradio interface"""
    
    with gr.Blocks(
        title="Wildberries Analytics Dashboard",
        theme=gr.themes.Soft(),
        css="""
        footer {visibility: hidden}
        .plot-container {min-height: 1150px !important}
        .gradio-plot {min-height: 1150px !important}
        """
    ) as demo:
        
        gr.Markdown("""
        # ๐Ÿ›๏ธ Wildberries Analytics Dashboard
        
        Monitor your marketplace performance and predict inventory needs with AI-powered analytics.
        
        **Features:**
        - ๐Ÿ“Š Sales performance analysis with automatic return detection
        - ๐Ÿ“ฆ Inventory forecasting with AI-powered predictions
        - โš ๏ธ Stockout risk alerts and notifications
        - ๐Ÿ“ˆ Interactive dashboards with commission analysis
        """)
        
        # API Token Configuration
        with gr.Row():
            with gr.Column(scale=3):
                api_token_input = gr.Textbox(
                    value="",
                    label="๐Ÿ”‘ Wildberries API Token (Required for Real Data)",
                    placeholder="Paste your Wildberries API token here to access live data - leave empty for demo mode",
                    type="password",
                    info="๐Ÿ’ก Get your token from your Wildberries seller account โ†’ Settings โ†’ API"
                )
            with gr.Column(scale=1):
                api_status = gr.Textbox(
                    value="๐Ÿ”ด Demo Mode - Enter API token above for live data",
                    label="Status",
                    interactive=False
                )
        # Update status when token changes
        api_token_input.change(
            fn=update_status,
            inputs=[api_token_input],
            outputs=[api_status]
        )
        
        with gr.Tabs():
            with gr.TabItem("๐Ÿ“Š Sales Analytics"):
                with gr.Row():
                    with gr.Column(scale=1):
                        period_selector = gr.Radio(
                            choices=["week", "month"],
                            value="week",
                            label="Analysis Period"
                        )
                        analyze_btn = gr.Button("๐Ÿ“ˆ Analyze Sales", variant="primary")
                    
                    with gr.Column(scale=3):
                        sales_summary = gr.Markdown("Enter your API token above and select a period, then click 'Analyze Sales' to get started.")
                
                # Sales dashboards
                with gr.Row():
                    sales_chart = gr.Plot(label="Sales Performance Dashboard")
                
                with gr.Row():
                    commission_chart = gr.Plot(label="Commission Analysis Dashboard", visible=False)
                
                # Daily Revenue Table
                with gr.Row():
                    daily_revenue_table = gr.DataFrame(
                        label="๐Ÿ“… Daily Revenue Breakdown",
                        headers=["Date", "Revenue (โ‚ฝ)", "Orders", "Commission (โ‚ฝ)", "Net Revenue (โ‚ฝ)"],
                        datatype=["str", "number", "number", "number", "number"],
                        interactive=False
                    )
                
                # Event handlers
                analyze_btn.click(
                    fn=analyze_sales_performance,
                    inputs=[period_selector, api_token_input],
                    outputs=[sales_summary, sales_chart, commission_chart, daily_revenue_table]
                )
            
            # Inventory Forecasting Tab
            with gr.TabItem("๐Ÿ“ฆ Inventory Forecasting"):
                with gr.Row():
                    with gr.Column(scale=1):
                        forecast_method = gr.Dropdown(
                            choices=[
                                ("Simple Division", "simple"),
                                ("Safety Stock", "safety_stock"),
                                ("Weighted Average", "weighted"),
                                ("Seasonal Adjustment", "seasonal")
                            ],
                            value="simple",
                            label="Forecasting Method"
                        )
                        forecast_btn = gr.Button("๐Ÿ”ฎ Calculate Forecast", variant="primary")
                    
                    with gr.Column(scale=3):
                        forecast_summary = gr.Markdown("Enter your API token above and select a forecasting method, then click 'Calculate Forecast'.")
                        forecast_table = gr.DataFrame(
                            headers=["Product", "Current Stock", "Avg Daily Sales", "Days Until Stockout", "Risk Level"],
                            label="Inventory Forecast Results"
                        )
                        forecast_chart = gr.Plot(label="Inventory Risk Analysis")
                
                # Event handlers
                forecast_btn.click(
                    fn=calculate_stockout_forecast,
                    inputs=[forecast_method, api_token_input],
                    outputs=[forecast_summary, forecast_table, forecast_chart]
                )
            
            # Data Validation Tab
            with gr.TabItem("๐Ÿ” Data Validation"):
                with gr.Row():
                    with gr.Column(scale=1):
                        validation_btn = gr.Button("๐Ÿ” Validate Data Consistency", variant="primary")
                    
                    with gr.Column(scale=3):
                        validation_results = gr.Markdown("Click 'Validate Data Consistency' to check for data quality issues.")
                
                # Event handlers
                def validate_wb_data_interface(api_token):
                    """Interface function for data validation"""
                    try:
                        weekly_data = get_sales_data("week", api_token)
                        monthly_data = get_sales_data("month", api_token)
                        
                        processed_data = validate_and_process_wb_data(weekly_data, monthly_data)
                        validation = processed_data["validation"]
                        
                        mode_indicator = "Demo Mode" if not api_token or api_token.strip() == "" else "Live Data"
                        
                        results_md = f"""
                        ## Data Validation Results ({mode_indicator})
                        
                        **Status**: {"โœ… " + validation["status"].upper() if validation["status"] == "valid" else "โŒ " + validation["status"].upper()}
                        
                        ### ๐Ÿ“Š Data Summary
                        - **Weekly Records**: {len(weekly_data):,}
                        - **Monthly Records**: {len(monthly_data):,}
                        
                        ### โš ๏ธ Warnings ({len(validation["warnings"])})
                        """
                        
                        for warning in validation["warnings"]:
                            results_md += f"- {warning}\n"
                        
                        if validation["errors"]:
                            results_md += f"\n### โŒ Errors ({len(validation['errors'])})\n"
                            for error in validation["errors"]:
                                results_md += f"- {error}\n"
                        
                        if not validation["warnings"] and not validation["errors"]:
                            results_md += "\nโœ… **No data quality issues detected!**"
                        
                        return results_md
                        
                    except Exception as e:
                        return f"โŒ **Validation Error**: {str(e)}"
                
                validation_btn.click(
                    fn=validate_wb_data_interface,
                    inputs=[api_token_input],
                    outputs=[validation_results]
                )
            
            # Documentation Tab
            with gr.TabItem("๐Ÿ“– Documentation"):
                gr.Markdown("""
                ## How to Use This Dashboard
                
                ### ๐Ÿ”ง Setup
                1. **API Token**: Enter your Wildberries API token in the field above (required for real data)
                2. **Get Token**: Login to your Wildberries seller account โ†’ Settings โ†’ API โ†’ Generate Token
                3. **Permissions**: Ensure your token has access to Analytics and Statistics APIs
                4. **Demo Mode**: Leave token field empty to explore with sample data
                
                ### ๐Ÿ“Š Sales Analytics
                - **Week Analysis**: Shows sales data for the last 7 days
                - **Month Analysis**: Shows sales data for the last 30 days  
                - **Enhanced Metrics**: Commission analysis, net revenue, platform fees
                - **Commission Dashboard**: Detailed commission breakdown by products
                - **Pagination**: Automatically handles large datasets (80,000+ records)
                
                ### ๐Ÿ“ฆ Inventory Forecasting
                Choose from multiple forecasting methods:
                
                - **Simple Division**: Current stock รท average daily sales
                - **Safety Stock**: Includes buffer for demand variability
                - **Weighted Average**: Recent sales weighted more heavily
                - **Seasonal Adjustment**: Accounts for seasonal demand patterns
                
                ### ๐Ÿšจ Risk Levels
                - ๐Ÿ”ด **Critical** (< 7 days): Immediate action required
                - ๐ŸŸก **Warning** (7-14 days): Monitor closely
                - ๐ŸŸข **Safe** (> 14 days): Adequate stock levels
                
                ### ๐Ÿ” Data Validation
                - **Consistency Checks**: Automatic validation of data quality
                - **Duplicate Detection**: Identifies duplicate sales records
                - **Data Aggregation**: Performance optimization for large datasets
                
                ### ๐Ÿ”— API Information
                This dashboard uses the [Wildberries API](https://dev.wildberries.ru/en/openapi/api-information):
                - **Sales Endpoint**: `/api/v1/supplier/sales` (with automatic pagination)
                - **Stocks Endpoint**: `/api/v1/supplier/stocks`  
                - **Rate Limits**: 300 requests/minute (respected automatically)
                - **Data Retention**: Sales data available for 90 days
                
                ### ๐Ÿ› ๏ธ Technical Details
                - **Framework**: Gradio + FastMCP
                - **Deployment**: Hugging Face Spaces
                - **Data Processing**: Pandas + NumPy
                - **Visualization**: Plotly
                """)
        
        gr.Markdown("""
        ---
        ๐Ÿ’ก **Note**: This dashboard starts in demo mode with sample data. To access your real Wildberries data, enter your API token in the field above.
        
        ๐Ÿ”’ **Security**: Your token is only used during this session and is never stored or logged.
        """)
    
    return demo

# Launch the application
if __name__ == "__main__":
    demo = create_interface()
    demo.launch(
        share=True,  # Set to False for Hugging Face Spaces
        server_name="0.0.0.0",  # Required for Spaces
        server_port=7860,  # Default Gradio port
        show_error=True,
        #mcp_server=True
    )