bakyt92 commited on
Commit
7e3a96a
·
1 Parent(s): a3a4e3f

update config and wildberries_client

Browse files
Files changed (2) hide show
  1. config.py +7 -7
  2. wildberries_client.py +150 -19
config.py CHANGED
@@ -71,14 +71,14 @@ class Config:
71
  return self.wildberries_api_token is not None and len(self.wildberries_api_token) > 0
72
 
73
  def get_endpoints(self) -> Dict[str, str]:
74
- """Get API endpoint configurations based on official documentation"""
75
  return {
76
- # Statistics API endpoints
77
- "sales": f"{self.wildberries_base_url}/api/v1/supplier/sales",
78
- "orders": f"{self.wildberries_base_url}/api/v1/supplier/orders",
79
- "stocks": f"{self.wildberries_base_url}/api/v1/supplier/stocks",
80
- "incomes": f"{self.wildberries_base_url}/api/v1/supplier/incomes",
81
- "reportDetailByPeriod": f"{self.wildberries_base_url}/api/v1/supplier/reportDetailByPeriod",
82
 
83
  # Analytics API endpoints
84
  "analytics": f"{self.wildberries_analytics_url}/api/v2/nm-report/detail",
 
71
  return self.wildberries_api_token is not None and len(self.wildberries_api_token) > 0
72
 
73
  def get_endpoints(self) -> Dict[str, str]:
74
+ """Get API endpoint configurations based on working API calls"""
75
  return {
76
+ # Statistics API endpoints - Updated to working v5 version
77
+ "sales": f"{self.wildberries_base_url}/api/v5/supplier/reportDetailByPeriod",
78
+ "orders": f"{self.wildberries_base_url}/api/v5/supplier/reportDetailByPeriod",
79
+ "stocks": f"{self.wildberries_base_url}/api/v5/supplier/reportDetailByPeriod",
80
+ "incomes": f"{self.wildberries_base_url}/api/v5/supplier/reportDetailByPeriod",
81
+ "reportDetailByPeriod": f"{self.wildberries_base_url}/api/v5/supplier/reportDetailByPeriod",
82
 
83
  # Analytics API endpoints
84
  "analytics": f"{self.wildberries_analytics_url}/api/v2/nm-report/detail",
wildberries_client.py CHANGED
@@ -153,18 +153,24 @@ class WildberriesAPI:
153
  try:
154
  response = self._make_request("GET", endpoint, params=params)
155
 
156
- if not response or "data" not in response:
157
  logger.warning("No sales data returned from API")
158
  return pd.DataFrame()
159
 
160
- # Convert to DataFrame
161
- sales_data = pd.DataFrame(response["data"])
 
 
 
 
 
 
162
 
163
  if sales_data.empty:
164
  return sales_data
165
 
166
  # Process and clean the data
167
- sales_data = self._process_sales_data(sales_data)
168
 
169
  return sales_data
170
 
@@ -187,23 +193,29 @@ class WildberriesAPI:
187
  if not date_from:
188
  date_from = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
189
 
190
- params = {"dateFrom": date_from}
191
 
192
  try:
193
  response = self._make_request("GET", endpoint, params=params)
194
 
195
- if not response or "data" not in response:
196
  logger.warning("No stock data returned from API")
197
  return pd.DataFrame()
198
 
199
- # Convert to DataFrame
200
- stock_data = pd.DataFrame(response["data"])
 
 
 
 
 
 
201
 
202
  if stock_data.empty:
203
  return stock_data
204
 
205
  # Process and clean the data
206
- stock_data = self._process_stock_data(stock_data)
207
 
208
  return stock_data
209
 
@@ -224,25 +236,31 @@ class WildberriesAPI:
224
  """
225
  endpoint = self.config.get_endpoints()["orders"]
226
 
227
- params = {"dateFrom": date_from}
228
  if date_to:
229
  params["dateTo"] = date_to
230
 
231
  try:
232
  response = self._make_request("GET", endpoint, params=params)
233
 
234
- if not response or "data" not in response:
235
  logger.warning("No orders data returned from API")
236
  return pd.DataFrame()
237
 
238
- # Convert to DataFrame
239
- orders_data = pd.DataFrame(response["data"])
 
 
 
 
 
 
240
 
241
  if orders_data.empty:
242
  return orders_data
243
 
244
  # Process and clean the data
245
- orders_data = self._process_orders_data(orders_data)
246
 
247
  return orders_data
248
 
@@ -362,13 +380,126 @@ class WildberriesAPI:
362
 
363
  return df
364
 
365
- def _process_orders_data(self, df: pd.DataFrame) -> pd.DataFrame:
366
- """Process and clean orders data from API response"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
- # Similar processing to sales data
369
- # This would be implemented based on the specific orders API response format
370
  return df
371
 
 
 
 
 
 
372
  def test_connection(self) -> Dict[str, Any]:
373
  """Test API connection and return status"""
374
  try:
@@ -464,7 +595,7 @@ class WildberriesAPI:
464
  "message": f"Failed to fetch seller info: {str(e)}"
465
  }
466
 
467
- def get_news(self, from_date: str = None, from_id: int = None, limit: int = 100) -> Dict[str, Any]:
468
  """
469
  Get seller portal news
470
 
 
153
  try:
154
  response = self._make_request("GET", endpoint, params=params)
155
 
156
+ if not response:
157
  logger.warning("No sales data returned from API")
158
  return pd.DataFrame()
159
 
160
+ # Handle direct array response (v5 API format)
161
+ if isinstance(response, list):
162
+ sales_data = pd.DataFrame(response)
163
+ elif isinstance(response, dict) and "data" in response:
164
+ sales_data = pd.DataFrame(response["data"])
165
+ else:
166
+ logger.warning("Unexpected API response format")
167
+ return pd.DataFrame()
168
 
169
  if sales_data.empty:
170
  return sales_data
171
 
172
  # Process and clean the data
173
+ sales_data = self._process_reportdetail_data(sales_data)
174
 
175
  return sales_data
176
 
 
193
  if not date_from:
194
  date_from = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
195
 
196
+ params = {"dateFrom": date_from, "limit": 100}
197
 
198
  try:
199
  response = self._make_request("GET", endpoint, params=params)
200
 
201
+ if not response:
202
  logger.warning("No stock data returned from API")
203
  return pd.DataFrame()
204
 
205
+ # Handle direct array response (v5 API format)
206
+ if isinstance(response, list):
207
+ stock_data = pd.DataFrame(response)
208
+ elif isinstance(response, dict) and "data" in response:
209
+ stock_data = pd.DataFrame(response["data"])
210
+ else:
211
+ logger.warning("Unexpected API response format")
212
+ return pd.DataFrame()
213
 
214
  if stock_data.empty:
215
  return stock_data
216
 
217
  # Process and clean the data
218
+ stock_data = self._process_reportdetail_data(stock_data)
219
 
220
  return stock_data
221
 
 
236
  """
237
  endpoint = self.config.get_endpoints()["orders"]
238
 
239
+ params = {"dateFrom": date_from, "limit": 100}
240
  if date_to:
241
  params["dateTo"] = date_to
242
 
243
  try:
244
  response = self._make_request("GET", endpoint, params=params)
245
 
246
+ if not response:
247
  logger.warning("No orders data returned from API")
248
  return pd.DataFrame()
249
 
250
+ # Handle direct array response (v5 API format)
251
+ if isinstance(response, list):
252
+ orders_data = pd.DataFrame(response)
253
+ elif isinstance(response, dict) and "data" in response:
254
+ orders_data = pd.DataFrame(response["data"])
255
+ else:
256
+ logger.warning("Unexpected API response format")
257
+ return pd.DataFrame()
258
 
259
  if orders_data.empty:
260
  return orders_data
261
 
262
  # Process and clean the data
263
+ orders_data = self._process_reportdetail_data(orders_data)
264
 
265
  return orders_data
266
 
 
380
 
381
  return df
382
 
383
+ def _process_reportdetail_data(self, df: pd.DataFrame) -> pd.DataFrame:
384
+ """Process and clean reportDetailByPeriod data from API response (v5)"""
385
+
386
+ # Column mapping based on actual API response structure
387
+ column_mapping = {
388
+ 'realizationreport_id': 'report_id',
389
+ 'date_from': 'date_from',
390
+ 'date_to': 'date_to',
391
+ 'create_dt': 'create_date',
392
+ 'currency_name': 'currency',
393
+ 'suppliercontract_code': 'contract_code',
394
+ 'rrd_id': 'record_id',
395
+ 'gi_id': 'goods_id',
396
+ 'subject_name': 'category',
397
+ 'nm_id': 'product_id',
398
+ 'brand_name': 'brand',
399
+ 'sa_name': 'supplier_article',
400
+ 'ts_name': 'tech_size',
401
+ 'barcode': 'barcode',
402
+ 'doc_type_name': 'document_type',
403
+ 'quantity': 'quantity',
404
+ 'retail_price': 'retail_price',
405
+ 'retail_amount': 'retail_amount',
406
+ 'sale_percent': 'sale_percent',
407
+ 'commission_percent': 'commission_percent',
408
+ 'office_name': 'office_name',
409
+ 'supplier_oper_name': 'operation_name',
410
+ 'order_dt': 'order_date',
411
+ 'sale_dt': 'sale_date',
412
+ 'rr_dt': 'report_date',
413
+ 'shk_id': 'warehouse_code',
414
+ 'retail_price_withdisc_rub': 'discounted_price',
415
+ 'delivery_amount': 'delivery_amount',
416
+ 'return_amount': 'return_amount',
417
+ 'delivery_rub': 'delivery_cost',
418
+ 'gi_box_type_name': 'box_type',
419
+ 'product_discount_for_report': 'product_discount',
420
+ 'supplier_promo': 'supplier_promo',
421
+ 'rid': 'rid',
422
+ 'ppvz_spp_prc': 'spp_percent',
423
+ 'ppvz_kvw_prc_base': 'kvw_percent_base',
424
+ 'ppvz_kvw_prc': 'kvw_percent',
425
+ 'sup_rating_prc_up': 'rating_bonus',
426
+ 'is_kgvp_v2': 'is_kgvp',
427
+ 'ppvz_sales_commission': 'sales_commission',
428
+ 'ppvz_for_pay': 'amount_for_pay',
429
+ 'ppvz_reward': 'reward',
430
+ 'acquiring_fee': 'acquiring_fee',
431
+ 'acquiring_percent': 'acquiring_percent',
432
+ 'payment_processing': 'payment_processing',
433
+ 'acquiring_bank': 'acquiring_bank',
434
+ 'ppvz_vw': 'warehouse_cost',
435
+ 'ppvz_vw_nds': 'warehouse_cost_vat',
436
+ 'ppvz_office_name': 'pickup_office',
437
+ 'ppvz_office_id': 'pickup_office_id',
438
+ 'ppvz_supplier_id': 'supplier_id',
439
+ 'ppvz_supplier_name': 'supplier_name',
440
+ 'ppvz_inn': 'supplier_inn',
441
+ 'declaration_number': 'declaration_number',
442
+ 'sticker_id': 'sticker_id',
443
+ 'site_country': 'site_country',
444
+ 'penalty': 'penalty',
445
+ 'additional_payment': 'additional_payment',
446
+ 'rebill_logistic_cost': 'logistics_cost',
447
+ 'rebill_logistic_org': 'logistics_company',
448
+ 'storage_fee': 'storage_fee',
449
+ 'deduction': 'deduction',
450
+ 'acceptance': 'acceptance',
451
+ 'assembly_id': 'assembly_id',
452
+ 'srid': 'unique_id',
453
+ 'report_type': 'report_type'
454
+ }
455
+
456
+ # Rename columns that exist
457
+ for old_name, new_name in column_mapping.items():
458
+ if old_name in df.columns:
459
+ df = df.rename(columns={old_name: new_name})
460
+
461
+ # Convert date columns
462
+ date_columns = ['create_date', 'order_date', 'sale_date', 'report_date']
463
+ for col in date_columns:
464
+ if col in df.columns:
465
+ df[col] = pd.to_datetime(df[col], errors='coerce')
466
+
467
+ # Convert numeric columns
468
+ numeric_columns = [
469
+ 'quantity', 'retail_price', 'retail_amount', 'sale_percent', 'commission_percent',
470
+ 'discounted_price', 'delivery_amount', 'return_amount', 'delivery_cost',
471
+ 'product_discount', 'supplier_promo', 'spp_percent', 'kvw_percent_base',
472
+ 'kvw_percent', 'rating_bonus', 'sales_commission', 'amount_for_pay',
473
+ 'reward', 'acquiring_fee', 'acquiring_percent', 'warehouse_cost',
474
+ 'warehouse_cost_vat', 'penalty', 'additional_payment', 'logistics_cost',
475
+ 'storage_fee', 'deduction', 'acceptance'
476
+ ]
477
+
478
+ for col in numeric_columns:
479
+ if col in df.columns:
480
+ df[col] = pd.to_numeric(df[col], errors='coerce')
481
+
482
+ # Add product name (use supplier_article if available)
483
+ if 'product_name' not in df.columns:
484
+ if 'supplier_article' in df.columns:
485
+ df['product_name'] = df['supplier_article']
486
+ elif 'category' in df.columns:
487
+ df['product_name'] = df['category']
488
+ else:
489
+ df['product_name'] = 'Unknown Product'
490
+
491
+ # Filter out non-sales records (keep only actual sales)
492
+ if 'operation_name' in df.columns:
493
+ # Keep records that are actual sales or returns
494
+ df = df[~df['operation_name'].str.contains('Возмещение', na=False)]
495
 
 
 
496
  return df
497
 
498
+ def _process_orders_data(self, df: pd.DataFrame) -> pd.DataFrame:
499
+ """Process and clean orders data from API response (legacy method)"""
500
+ # This is now handled by _process_reportdetail_data
501
+ return self._process_reportdetail_data(df)
502
+
503
  def test_connection(self) -> Dict[str, Any]:
504
  """Test API connection and return status"""
505
  try:
 
595
  "message": f"Failed to fetch seller info: {str(e)}"
596
  }
597
 
598
+ def get_news(self, from_date: str = None, from_id: int = None, limit: int = 500) -> Dict[str, Any]:
599
  """
600
  Get seller portal news
601