qrpa 1.1.32__py3-none-any.whl → 1.1.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of qrpa might be problematic. Click here for more details.

@@ -0,0 +1,429 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 新品分析数据模型
5
+ 使用SQLAlchemy定义shein_new_product_analysis表结构
6
+ """
7
+
8
+ from sqlalchemy import create_engine, Column, Integer, String, DECIMAL, Date, BigInteger, DateTime, Text, Index
9
+ from sqlalchemy.ext.declarative import declarative_base
10
+ from sqlalchemy.orm import sessionmaker
11
+ from datetime import datetime
12
+ import json
13
+
14
+ # 创建基类
15
+ Base = declarative_base()
16
+
17
+ class SheinNewProductAnalysis(Base):
18
+ """
19
+ 希音新品分析表
20
+ 存储SKC新品分析相关数据
21
+ """
22
+ __tablename__ = 'shein_new_product_analysis'
23
+
24
+ # 主键ID
25
+ id = Column(BigInteger, primary_key=True, autoincrement=True, comment='主键ID')
26
+
27
+ # 店铺信息
28
+ store_username = Column(String(100), nullable=False, comment='店铺账号')
29
+ store_name = Column(String(200), nullable=True, comment='店铺名称')
30
+
31
+ # 日期信息
32
+ stat_date = Column(Date, nullable=False, comment='统计日期')
33
+ shelf_date = Column(Date, nullable=True, comment='上架日期')
34
+
35
+ # SKC信息
36
+ skc = Column(String(100), nullable=False, comment='平台SKC')
37
+ sku_supplier_no = Column(String(100), nullable=True, comment='商家SKC')
38
+ layer_nm = Column(String(100), nullable=True, comment='商品层级')
39
+ goods_name = Column(String(500), nullable=True, comment='商品标题')
40
+ img_url = Column(String(500), nullable=True, comment='SKC图片URL')
41
+
42
+ # 状态标识
43
+ onsale_flag = Column(Integer, nullable=True, default=0, comment='在售标识(0-否,1-是)')
44
+ sale_flag = Column(Integer, nullable=True, default=0, comment='上架标识(0-否,1-是)')
45
+
46
+ # 销售数据
47
+ sale_cnt = Column(Integer, nullable=True, default=0, comment='销量')
48
+ pay_order_cnt = Column(Integer, nullable=True, default=0, comment='支付订单数')
49
+ goods_uv = Column(Integer, nullable=True, default=0, comment='商品访客数')
50
+ cart_uv_idx = Column(Integer, nullable=True, default=0, comment='加车访客')
51
+ eps_uv_idx = Column(Integer, nullable=True, default=0, comment='曝光人数')
52
+
53
+ # 转化率数据
54
+ eps_gds_ctr_idx = Column(DECIMAL(10, 4), nullable=True, comment='点击率')
55
+ gds_pay_ctr_idx = Column(DECIMAL(10, 4), nullable=True, comment='转化率')
56
+ bad_comment_rate = Column(DECIMAL(10, 4), nullable=True, comment='差评率')
57
+
58
+ # 促销活动信息(JSON格式)
59
+ prom_inf_ing = Column(Text, nullable=True, comment='活动中的促销活动(JSON)')
60
+ prom_inf_ready = Column(Text, nullable=True, comment='即将开始的促销活动(JSON)')
61
+
62
+ # AB测试信息(JSON格式)
63
+ ab_test = Column(Text, nullable=True, comment='AB测试数据(JSON)')
64
+
65
+ # 分析字段(不参与更新)
66
+ reason_analysis = Column(Text, nullable=True, comment='原因分析')
67
+ optimization_action = Column(Text, nullable=True, comment='优化动作')
68
+
69
+ # 备注
70
+ remark = Column(String(500), nullable=True, comment='备注')
71
+
72
+ # 时间戳
73
+ created_at = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间')
74
+ updated_at = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now, comment='更新时间')
75
+
76
+ # 创建联合唯一索引
77
+ __table_args__ = (
78
+ Index('idx_date_skc', 'stat_date', 'skc', unique=True),
79
+ Index('idx_stat_date', 'stat_date'),
80
+ Index('idx_skc', 'skc'),
81
+ Index('idx_store_username', 'store_username'),
82
+ )
83
+
84
+ def __repr__(self):
85
+ return f"<SheinNewProductAnalysis(id={self.id}, store_username='{self.store_username}', skc='{self.skc}', stat_date={self.stat_date})>"
86
+
87
+ class NewProductAnalysisManager:
88
+ """
89
+ 新品分析数据管理器
90
+ 提供数据库操作相关方法
91
+ """
92
+
93
+ # database_url = f"mysql+pymysql://{config.mysql_username}:{config.mysql_password}@{config.mysql_host}:{config.mysql_port}/{config.mysql_database}"
94
+
95
+ @classmethod
96
+ def __init__(self, database_url=None):
97
+ """
98
+ 初始化数据库连接
99
+
100
+ Args:
101
+ database_url (str): 数据库连接URL
102
+ """
103
+ # if database_url is None:
104
+ # database_url = self.database_url
105
+ print(f"{self.__name__} 连接数据库: {database_url}")
106
+ self.engine = create_engine(database_url, echo=False)
107
+ self.Session = sessionmaker(bind=self.engine)
108
+
109
+ def create_table(self):
110
+ """
111
+ 创建数据表
112
+ """
113
+ Base.metadata.create_all(self.engine)
114
+ print("数据表创建成功")
115
+
116
+ def insert_data(self, data_list):
117
+ """
118
+ 批量插入数据
119
+
120
+ Args:
121
+ data_list (list): 数据字典列表
122
+
123
+ Returns:
124
+ int: 成功插入的记录数
125
+ """
126
+ session = self.Session()
127
+ try:
128
+ insert_count = 0
129
+ for data in data_list:
130
+ # 检查是否存在(根据唯一索引:stat_date + skc)
131
+ existing = session.query(SheinNewProductAnalysis).filter(
132
+ SheinNewProductAnalysis.stat_date == data.get('stat_date'),
133
+ SheinNewProductAnalysis.skc == data.get('skc')
134
+ ).first()
135
+
136
+ # 先处理JSON字段转换(无论是新增还是更新都需要)
137
+ # 使用 'in' 判断而不是 'and',确保空列表[]和空字典{}也能被转换
138
+ if 'prom_inf_ing' in data and isinstance(data['prom_inf_ing'], (dict, list)):
139
+ data['prom_inf_ing'] = json.dumps(data['prom_inf_ing'], ensure_ascii=False)
140
+ if 'prom_inf_ready' in data and isinstance(data['prom_inf_ready'], (dict, list)):
141
+ data['prom_inf_ready'] = json.dumps(data['prom_inf_ready'], ensure_ascii=False)
142
+ if 'ab_test' in data and isinstance(data['ab_test'], (dict, list)):
143
+ data['ab_test'] = json.dumps(data['ab_test'], ensure_ascii=False)
144
+
145
+ if existing:
146
+ # 更新现有记录(排除不更新的分析字段)
147
+ exclude_fields = {'reason_analysis', 'optimization_action'}
148
+ for key, value in data.items():
149
+ if key not in exclude_fields:
150
+ setattr(existing, key, value)
151
+ setattr(existing, 'updated_at', datetime.now())
152
+ else:
153
+ # 插入新记录
154
+ new_record = SheinNewProductAnalysis(**data)
155
+ session.add(new_record)
156
+ insert_count += 1
157
+
158
+ session.commit()
159
+ print(f"成功插入/更新 {insert_count} 条记录")
160
+ return insert_count
161
+ except Exception as e:
162
+ session.rollback()
163
+ print(f"插入数据失败: {e}")
164
+ raise
165
+ finally:
166
+ session.close()
167
+
168
+ def get_records_by_date_range(self, store_username, start_date, end_date):
169
+ """
170
+ 根据日期范围查询记录
171
+
172
+ Args:
173
+ store_username (str): 店铺账号
174
+ start_date (str): 开始日期
175
+ end_date (str): 结束日期
176
+
177
+ Returns:
178
+ list: 记录列表
179
+ """
180
+ session = self.Session()
181
+ try:
182
+ records = session.query(SheinNewProductAnalysis).filter(
183
+ SheinNewProductAnalysis.store_username == store_username,
184
+ SheinNewProductAnalysis.stat_date >= start_date,
185
+ SheinNewProductAnalysis.stat_date <= end_date
186
+ ).all()
187
+ return records
188
+ finally:
189
+ session.close()
190
+
191
+ def get_records_by_skc(self, store_username, skc):
192
+ """
193
+ 根据SKC查询记录
194
+
195
+ Args:
196
+ store_username (str): 店铺账号
197
+ skc (str): 平台SKC
198
+
199
+ Returns:
200
+ list: 记录列表
201
+ """
202
+ session = self.Session()
203
+ try:
204
+ records = session.query(SheinNewProductAnalysis).filter(
205
+ SheinNewProductAnalysis.store_username == store_username,
206
+ SheinNewProductAnalysis.skc == skc
207
+ ).order_by(SheinNewProductAnalysis.stat_date.desc()).all()
208
+ return records
209
+ finally:
210
+ session.close()
211
+
212
+ def _extract_prom_info(self, prom_list):
213
+ """
214
+ 提取促销活动信息
215
+
216
+ Args:
217
+ prom_list (list): 促销活动列表
218
+
219
+ Returns:
220
+ list: 提取后的促销活动信息列表
221
+ """
222
+ if not prom_list:
223
+ return []
224
+
225
+ result = []
226
+ for prom in prom_list:
227
+ prom_info = {
228
+ 'promNm' : prom.get('promNm'),
229
+ 'promId' : prom.get('promId'),
230
+ 'startDate': prom.get('startDate'),
231
+ 'endDate' : prom.get('endDate')
232
+ }
233
+
234
+ prom_id = prom.get('promId', '')
235
+ prom_detail = prom.get('promDetail', [])
236
+
237
+ # 根据promId长度判断取哪些字段
238
+ if len(str(prom_id)) > 6 and prom_detail:
239
+ # 营销工具:取第一个detail的数据
240
+ detail = prom_detail[0] if prom_detail else {}
241
+ prom_info['attend_num_sum'] = detail.get('attend_num_sum')
242
+ prom_info['supply_price'] = detail.get('supply_price')
243
+ prom_info['product_act_price'] = detail.get('product_act_price')
244
+ elif prom_detail:
245
+ # 营销活动:取第一个detail的数据
246
+ detail = prom_detail[0] if prom_detail else {}
247
+ prom_info['attend_num'] = detail.get('attend_num')
248
+ prom_info['goods_cost'] = detail.get('goods_cost')
249
+ prom_info['attend_cost'] = detail.get('attend_cost')
250
+
251
+ result.append(prom_info)
252
+
253
+ return result
254
+
255
+ def import_from_json(self, json_data_list):
256
+ """
257
+ 从JSON数据导入到数据库
258
+
259
+ Args:
260
+ json_data_list (list): JSON数据列表
261
+
262
+ Returns:
263
+ int: 成功导入的记录数
264
+ """
265
+ print(f"开始处理 {len(json_data_list)} 条JSON数据")
266
+ data_list = []
267
+ skipped_count = 0
268
+
269
+ for idx, item in enumerate(json_data_list, 1):
270
+ # 验证必填字段
271
+ stat_date = item.get('stat_date')
272
+ skc = item.get('skc')
273
+
274
+ if not stat_date:
275
+ print(f"⚠️ 警告: 第 {idx} 条记录缺少必填字段 stat_date (统计日期),跳过该记录")
276
+ skipped_count += 1
277
+ continue
278
+
279
+ if not skc:
280
+ print(f"⚠️ 警告: 第 {idx} 条记录缺少必填字段 skc,跳过该记录")
281
+ skipped_count += 1
282
+ continue
283
+
284
+ # 提取促销活动信息
285
+ prom_campaign = item.get('promCampaign', {})
286
+ prom_inf_ing = self._extract_prom_info(prom_campaign.get('promInfIng'))
287
+ prom_inf_ready = self._extract_prom_info(prom_campaign.get('promInfReady'))
288
+
289
+ # 提取AB测试信息(去掉product_name字段)
290
+ ab_test = item.get('ab_test', {})
291
+ if ab_test and isinstance(ab_test, dict):
292
+ ab_test_copy = ab_test.copy()
293
+ ab_test_copy.pop('product_name', None)
294
+ ab_test = ab_test_copy
295
+
296
+ # 构建数据库记录
297
+ record = {
298
+ 'store_username' : item.get('store_username'),
299
+ 'store_name' : item.get('store_name'),
300
+ 'stat_date' : stat_date,
301
+ 'shelf_date' : item.get('shelf_date'),
302
+ 'skc' : skc,
303
+ 'sku_supplier_no' : item.get('skuSupplierNo'),
304
+ 'layer_nm' : item.get('layerNm'),
305
+ 'goods_name' : item.get('goodsName'),
306
+ 'img_url' : item.get('imgUrl'),
307
+ 'onsale_flag' : int(item.get('onsaleFlag', 0)),
308
+ 'sale_flag' : int(item.get('saleFlag', 0)),
309
+ 'sale_cnt' : item.get('saleCnt', 0),
310
+ 'pay_order_cnt' : item.get('payOrderCnt', 0),
311
+ 'goods_uv' : item.get('goodsUv', 0),
312
+ 'cart_uv_idx' : item.get('cartUvIdx', 0),
313
+ 'eps_uv_idx' : item.get('epsUvIdx', 0),
314
+ 'eps_gds_ctr_idx' : item.get('epsGdsCtrIdx', 0),
315
+ 'gds_pay_ctr_idx' : item.get('gdsPayCtrIdx', 0),
316
+ 'bad_comment_rate': item.get('badCommentRate', 0),
317
+ 'prom_inf_ing' : prom_inf_ing,
318
+ 'prom_inf_ready' : prom_inf_ready,
319
+ 'ab_test' : ab_test
320
+ }
321
+
322
+ print(f"✓ 处理第 {idx} 条: SKC={record['skc']}, stat_date={stat_date}")
323
+ data_list.append(record)
324
+
325
+ if skipped_count > 0:
326
+ print(f"⚠️ 共跳过 {skipped_count} 条记录(缺少必填字段)")
327
+
328
+ # 调用insert_data方法插入数据
329
+ return self.insert_data(data_list)
330
+
331
+ def delete_records_by_date(self, store_username, stat_date):
332
+ """
333
+ 删除指定日期的记录
334
+
335
+ Args:
336
+ store_username (str): 店铺账号
337
+ stat_date (str): 统计日期
338
+
339
+ Returns:
340
+ int: 删除的记录数
341
+ """
342
+ session = self.Session()
343
+ try:
344
+ delete_count = session.query(SheinNewProductAnalysis).filter(
345
+ SheinNewProductAnalysis.store_username == store_username,
346
+ SheinNewProductAnalysis.stat_date == stat_date
347
+ ).delete()
348
+ session.commit()
349
+ print(f"成功删除 {delete_count} 条记录")
350
+ return delete_count
351
+ except Exception as e:
352
+ session.rollback()
353
+ print(f"删除数据失败: {e}")
354
+ raise
355
+ finally:
356
+ session.close()
357
+
358
+ # 创建全局实例,用于快速访问
359
+ _new_product_analysis_manager = None
360
+
361
+ def get_new_product_analysis_manager():
362
+ """
363
+ 获取新品分析管理器实例
364
+
365
+ Returns:
366
+ NewProductAnalysisManager: 新品分析管理器实例
367
+ """
368
+ global _new_product_analysis_manager
369
+ if _new_product_analysis_manager is None:
370
+ _new_product_analysis_manager = NewProductAnalysisManager()
371
+ return _new_product_analysis_manager
372
+
373
+ def insert_analysis_data(data_list):
374
+ """
375
+ 插入分析数据的便捷函数
376
+
377
+ Args:
378
+ data_list (list): 数据字典列表
379
+
380
+ Returns:
381
+ int: 成功插入的记录数
382
+ """
383
+ manager = get_new_product_analysis_manager()
384
+ return manager.insert_data(data_list)
385
+
386
+ def import_from_json(json_data_list):
387
+ """
388
+ 从JSON数据导入的便捷函数
389
+
390
+ Args:
391
+ json_data_list (list): JSON数据列表
392
+
393
+ Returns:
394
+ int: 成功导入的记录数
395
+ """
396
+ manager = get_new_product_analysis_manager()
397
+ return manager.import_from_json(json_data_list)
398
+
399
+ def get_records_by_skc(store_username, skc):
400
+ """
401
+ 根据SKC查询记录的便捷函数
402
+
403
+ Args:
404
+ store_username (str): 店铺账号
405
+ skc (str): 平台SKC
406
+
407
+ Returns:
408
+ list: 记录列表
409
+ """
410
+ manager = get_new_product_analysis_manager()
411
+ return manager.get_records_by_skc(store_username, skc)
412
+
413
+ if __name__ == '__main__':
414
+ # 测试代码
415
+ manager = NewProductAnalysisManager()
416
+
417
+ # 创建表
418
+ manager.create_table()
419
+
420
+ # 读取JSON文件
421
+ with open('../../docs/skc_model_GS9740414_2025-10-15.json', 'r', encoding='utf-8') as f:
422
+ json_data = json.load(f)
423
+ count = manager.import_from_json(json_data)
424
+ print(f"成功导入 {count} 条记录")
425
+
426
+ with open('../../docs/skc_model_S19118100_2025-10-15.json', 'r', encoding='utf-8') as f:
427
+ json_data = json.load(f)
428
+ count = manager.import_from_json(json_data)
429
+ print(f"成功导入 {count} 条记录")