bakyt92 commited on
Commit
a758bd8
·
1 Parent(s): fb181af

update config

Browse files
Files changed (2) hide show
  1. app.py +74 -40
  2. config.py +10 -4
app.py CHANGED
@@ -97,13 +97,31 @@ def calculate_stockout_forecast(method, api_token):
97
  # Get current inventory (demo data if API unavailable)
98
  wb_client = initialize_wb_client(api_token)
99
 
100
- if wb_client:
101
- inventory_data = wb_client.get_stocks()
 
 
 
 
 
 
 
 
 
 
 
102
  else:
103
  inventory_data = utils.load_demo_inventory_data()
 
104
 
105
- # Get sales data for forecasting
106
- sales_data = get_sales_data("month", api_token)
 
 
 
 
 
 
107
 
108
  # Initialize forecaster
109
  forecaster = InventoryForecaster()
@@ -113,52 +131,66 @@ def calculate_stockout_forecast(method, api_token):
113
  for _, item in inventory_data.iterrows():
114
  product_sales = sales_data[sales_data['product_id'] == item['product_id']]
115
 
116
- if not product_sales.empty:
117
- if method == "simple":
118
- days_left = forecaster.simple_division_method(
119
- item['current_stock'],
120
- product_sales['quantity'].mean()
121
- )
122
- elif method == "safety_stock":
123
- days_left = forecaster.safety_stock_method(
124
- item['current_stock'],
125
- product_sales['quantity'].mean(),
126
- product_sales['quantity'].max(),
127
- avg_lead_time=7,
128
- max_lead_time=14
129
- )
130
- elif method == "weighted":
 
 
 
 
 
 
 
 
131
  days_left = forecaster.weighted_average_method(
132
  item['current_stock'],
133
  product_sales
134
  )
135
  else:
136
- days_left = forecaster.seasonal_adjustment_method(
137
  item['current_stock'],
138
- product_sales['quantity'].mean(),
139
- seasonal_factor=1.0
140
  )
141
-
142
- # Risk categorization
143
- if days_left < 7:
144
- risk_level = "🔴 Critical"
145
- elif days_left < 14:
146
- risk_level = "🟡 Warning"
147
- else:
148
- risk_level = "🟢 Safe"
149
-
150
- forecasts.append({
151
- 'Product': item['product_name'],
152
- 'Current Stock': item['current_stock'],
153
- 'Days Until Stockout': round(days_left, 1),
154
- 'Risk Level': risk_level
155
- })
 
 
 
 
 
 
156
 
157
  # Create results DataFrame
158
  results_df = pd.DataFrame(forecasts)
159
 
160
  if results_df.empty:
161
- return "No inventory data available.", None
 
162
 
163
  # Sort by days until stockout
164
  results_df = results_df.sort_values('Days Until Stockout')
@@ -167,8 +199,9 @@ def calculate_stockout_forecast(method, api_token):
167
  critical_items = len(results_df[results_df['Days Until Stockout'] < 7])
168
  warning_items = len(results_df[results_df['Days Until Stockout'].between(7, 14)])
169
 
 
170
  summary = f"""
171
- ## Inventory Forecast - {method.replace('_', ' ').title()} Method
172
 
173
  - **Critical Items** (< 7 days): {critical_items}
174
  - **Warning Items** (7-14 days): {warning_items}
@@ -183,7 +216,8 @@ def calculate_stockout_forecast(method, api_token):
183
  except Exception as e:
184
  error_msg = f"Error calculating forecast: {str(e)}"
185
  gr.Error(error_msg)
186
- return error_msg, None, None
 
187
 
188
  def update_status(api_token):
189
  """Update API status based on token"""
 
97
  # Get current inventory (demo data if API unavailable)
98
  wb_client = initialize_wb_client(api_token)
99
 
100
+ # Determine if we're in demo mode
101
+ use_demo_mode = not api_token or api_token.strip() == ""
102
+
103
+ if wb_client and not use_demo_mode:
104
+ try:
105
+ inventory_data = wb_client.get_stocks()
106
+ # If API returns empty data, fall back to demo
107
+ if inventory_data.empty:
108
+ inventory_data = utils.load_demo_inventory_data()
109
+ use_demo_mode = True
110
+ except Exception:
111
+ inventory_data = utils.load_demo_inventory_data()
112
+ use_demo_mode = True
113
  else:
114
  inventory_data = utils.load_demo_inventory_data()
115
+ use_demo_mode = True
116
 
117
+ # Get sales data for forecasting - ensure consistency with inventory data source
118
+ if use_demo_mode:
119
+ sales_data = utils.load_demo_sales_data("month")
120
+ else:
121
+ sales_data = get_sales_data("month", api_token)
122
+ # If API sales data is empty, fall back to demo
123
+ if sales_data.empty:
124
+ sales_data = utils.load_demo_sales_data("month")
125
 
126
  # Initialize forecaster
127
  forecaster = InventoryForecaster()
 
131
  for _, item in inventory_data.iterrows():
132
  product_sales = sales_data[sales_data['product_id'] == item['product_id']]
133
 
134
+ # If no sales data for this product, use average daily sales of 1
135
+ if product_sales.empty:
136
+ avg_daily_sales = 1
137
+ max_daily_sales = 1
138
+ else:
139
+ avg_daily_sales = product_sales['quantity'].mean()
140
+ max_daily_sales = product_sales['quantity'].max()
141
+
142
+ if method == "simple":
143
+ days_left = forecaster.simple_division_method(
144
+ item['current_stock'],
145
+ avg_daily_sales
146
+ )
147
+ elif method == "safety_stock":
148
+ days_left = forecaster.safety_stock_method(
149
+ item['current_stock'],
150
+ avg_daily_sales,
151
+ max_daily_sales,
152
+ avg_lead_time=7,
153
+ max_lead_time=14
154
+ )
155
+ elif method == "weighted":
156
+ if not product_sales.empty:
157
  days_left = forecaster.weighted_average_method(
158
  item['current_stock'],
159
  product_sales
160
  )
161
  else:
162
+ days_left = forecaster.simple_division_method(
163
  item['current_stock'],
164
+ avg_daily_sales
 
165
  )
166
+ else:
167
+ days_left = forecaster.seasonal_adjustment_method(
168
+ item['current_stock'],
169
+ avg_daily_sales,
170
+ seasonal_factor=1.0
171
+ )
172
+
173
+ # Risk categorization
174
+ if days_left < 7:
175
+ risk_level = "🔴 Critical"
176
+ elif days_left < 14:
177
+ risk_level = "🟡 Warning"
178
+ else:
179
+ risk_level = "🟢 Safe"
180
+
181
+ forecasts.append({
182
+ 'Product': item['product_name'],
183
+ 'Current Stock': item['current_stock'],
184
+ 'Days Until Stockout': round(days_left, 1),
185
+ 'Risk Level': risk_level
186
+ })
187
 
188
  # Create results DataFrame
189
  results_df = pd.DataFrame(forecasts)
190
 
191
  if results_df.empty:
192
+ # Return all 3 required values for Gradio
193
+ return "No inventory data available.", pd.DataFrame(), None
194
 
195
  # Sort by days until stockout
196
  results_df = results_df.sort_values('Days Until Stockout')
 
199
  critical_items = len(results_df[results_df['Days Until Stockout'] < 7])
200
  warning_items = len(results_df[results_df['Days Until Stockout'].between(7, 14)])
201
 
202
+ mode_indicator = "Demo Mode" if use_demo_mode else "Live Data"
203
  summary = f"""
204
+ ## Inventory Forecast - {method.replace('_', ' ').title()} Method ({mode_indicator})
205
 
206
  - **Critical Items** (< 7 days): {critical_items}
207
  - **Warning Items** (7-14 days): {warning_items}
 
216
  except Exception as e:
217
  error_msg = f"Error calculating forecast: {str(e)}"
218
  gr.Error(error_msg)
219
+ # Return all 3 required values for Gradio
220
+ return error_msg, pd.DataFrame(), None
221
 
222
  def update_status(api_token):
223
  """Update API status based on token"""
config.py CHANGED
@@ -17,12 +17,12 @@ class Config:
17
  self.wildberries_api_token = os.getenv("WILDBERRIES_API_TOKEN")
18
  self.wildberries_base_url = "https://statistics-api.wildberries.ru"
19
  self.wildberries_content_url = "https://content-api.wildberries.ru"
20
- self.wildberries_analytics_url = "https://analytics-api.wildberries.ru"
 
21
 
22
- # Rate limiting settings (based on Wildberries API documentation)
23
  self.rate_limit_requests = 300 # requests per minute
24
  self.rate_limit_window = 60 # seconds
25
- self.rate_limit_burst = 10 # burst allowance
26
 
27
  # Request timeout settings
28
  self.request_timeout = 30 # seconds
@@ -75,7 +75,13 @@ class Config:
75
  "incomes": f"{self.wildberries_base_url}/api/v1/supplier/incomes",
76
  "reportDetailByPeriod": f"{self.wildberries_base_url}/api/v1/supplier/reportDetailByPeriod",
77
  "analytics": f"{self.wildberries_analytics_url}/api/v2/nm-report/detail",
78
- "content": f"{self.wildberries_content_url}/content/v1/cards/cursor/list"
 
 
 
 
 
 
79
  }
80
 
81
  def validate_token(self, token: str) -> bool:
 
17
  self.wildberries_api_token = os.getenv("WILDBERRIES_API_TOKEN")
18
  self.wildberries_base_url = "https://statistics-api.wildberries.ru"
19
  self.wildberries_content_url = "https://content-api.wildberries.ru"
20
+ self.wildberries_analytics_url = "https://seller-analytics-api.wildberries.ru"
21
+ self.wildberries_common_url = "https://common-api.wildberries.ru"
22
 
23
+ # Rate limiting settings (matches official documentation)
24
  self.rate_limit_requests = 300 # requests per minute
25
  self.rate_limit_window = 60 # seconds
 
26
 
27
  # Request timeout settings
28
  self.request_timeout = 30 # seconds
 
75
  "incomes": f"{self.wildberries_base_url}/api/v1/supplier/incomes",
76
  "reportDetailByPeriod": f"{self.wildberries_base_url}/api/v1/supplier/reportDetailByPeriod",
77
  "analytics": f"{self.wildberries_analytics_url}/api/v2/nm-report/detail",
78
+ "content": f"{self.wildberries_content_url}/content/v1/cards/cursor/list",
79
+ "ping_statistics": f"{self.wildberries_base_url}/ping",
80
+ "ping_content": f"{self.wildberries_content_url}/ping",
81
+ "ping_analytics": f"{self.wildberries_analytics_url}/ping",
82
+ "ping_common": f"{self.wildberries_common_url}/ping",
83
+ "news": f"{self.wildberries_common_url}/api/communications/v2/news",
84
+ "seller_info": f"{self.wildberries_common_url}/api/v1/seller-info"
85
  }
86
 
87
  def validate_token(self, token: str) -> bool: