Pārlūkot izejas kodu

1、优化日志
2、优化代码结构
3、新增程序化创意2.0

liyuyi@c-top.com.cn 4 gadi atpakaļ
vecāks
revīzija
f63b3f1028
4 mainītis faili ar 542 papildinājumiem un 166 dzēšanām
  1. 3 1
      ai_callback_handler.py
  2. 311 122
      ai_strategy_request_func.py
  3. 182 19
      ai_time_task_creative_handler.py
  4. 46 24
      utils/CommonFunction.py

+ 3 - 1
ai_callback_handler.py

@@ -8,7 +8,7 @@ from utils.BaseClass import *
 from utils.DataBaseConfig import *
 from utils.LogConfig import *
 
-log_handler = TimedRotatingFileHandler("logs/ai_callback/ai_callback.log",
+log_handler = TimedRotatingFileHandler("logs/ai_callback.log",
                                        when="midnight", backupCount=100)
 log_handler.setFormatter(log_formatter)
 logger = logging.getLogger('ai_callback_logger')
@@ -38,6 +38,7 @@ class AiCallBackAddCreative(tornado.web.RequestHandler):
                                      charset=charset)
             cursor = db_con.cursor()
             for item in data['callbackData']:
+                # TODO 1、更新message 和 status 到表中  2、需要判断是否为 程序化,来更新不同的表
                 if item['code'] == 0:
                     creative_uuid = item['creative_uuid']
                     creative_id = item['creative_id']
@@ -81,6 +82,7 @@ class AiCallBackAddGroup(tornado.web.RequestHandler):
                                      charset=charset)
             cursor = db_con.cursor()
             for item in data['callbackData']:
+                # TODO 更新message 和 status 到表中
                 if item['code'] == 0:
                     group_uuid = item['group_uuid']
                     unit_id = item['unit_id']

+ 311 - 122
ai_strategy_request_func.py

@@ -11,7 +11,7 @@ from utils.DataBaseConfig import *
 from utils.LogConfig import *
 from utils.UrlConfig import *
 
-log_handler = TimedRotatingFileHandler("logs/ai_strategy_request/ai_strategy_request.log",
+log_handler = TimedRotatingFileHandler("logs/ai_strategy_request.log",
                                        when="midnight", backupCount=100)
 log_handler.setFormatter(log_formatter)
 logger = logging.getLogger('ai_strategy_request_logger')
@@ -22,29 +22,43 @@ print('id of ai_strategy_request_logger %s' % id(logger))
 
 def ai_strategy_request_parse(data):
     """
-
-
+    发送创建组和创意的请求
     :param data:
     :return:
     """
     if data["operation_type"] == 1:
         inst = ParseAddCampaignOrAddGroupRequest(data)
+        # 1 获取客户策略信息
         inst.get_advertiser_strategy_info()
+
+        # 2 写入智能策略信息
         inst.write_intelligence_strategy_table()
+
+        # 3 判断 campaign_id 是否为空,为空则发送创建计划的请求
         if inst.campaign_id == "" or inst.campaign_id is None:
             if inst.add_campaign() == -1:
                 return {'code': -2, 'message': '广告计划创建失败!'}
-        inst.assemble_group_and_creative_params()
-        create_group_and_creative_res = inst.res_data
-        inst.update_intelligence_strategy_table()
-        logger.info("create group and creative request info: %s" % create_group_and_creative_res)
 
+        # 4 unit_type "4-自定义" or  "7-程序化创意2.0" ,调用对应的拼装参数函数
+        if data['group_info'].get('unit_type', 4) == 4:
+            inst.assemble_group_and_creative_params()
+        if data['group_info'].get('unit_type', 4) == 7:
+            inst.assemble_group_and_programme_creative_params()
+
+        # 5 发送的请求信息更新到数据库表
+        inst.update_intelligence_strategy_table(request_content=inst.res_data, message=None)
+
+        # 6 发送创建组和创意的请求
         request = requests.post(create_group_and_creative_url,
                                 headers=headers,
-                                data=json.JSONEncoder().encode(create_group_and_creative_res)
-                                )
+                                data=json.JSONEncoder().encode(inst.res_data))
         response_data = json.loads(request.text)
-        logger.info("请求返回:%s" % response_data)
+
+        # 7 接受的返回信息更新到数据库表
+        inst.update_intelligence_strategy_table(message=response_data)
+
+        # 8 写入日志信息
+        logger.info("策略uuid = %s 的请求返回信息:%s" % (inst.ai_strategy_uuid, response_data))
         return {'code': 0, 'message': response_data}
     else:
         return {'code': -1, 'message': '暂时不支持非新增的操作请求!'}
@@ -99,13 +113,12 @@ class ParseAddCampaignOrAddGroupRequest(object):
                 'ai_strategy_remark': self.request_data['ai_strategy_remark'],
                 'create_time': datetime.datetime.now()
             }
-
         df = pd.DataFrame.from_dict(intelligence_strategy_dict, orient='index').T
         df.to_sql(name="ctop_ai_kuaishou_intelligence_strategy", con=engine, if_exists='append', index=False)
 
-    def update_intelligence_strategy_table(self):
+    def update_intelligence_strategy_table(self, request_content=None, message=None):
         """
-        拼装之后的json, 更新到 ai_strategy_request_content
+        更新 intelligence_strategy_table
         :return:
         """
         db_con = pymysql.connect(host=host,
@@ -115,13 +128,17 @@ class ParseAddCampaignOrAddGroupRequest(object):
                                  database=db,
                                  charset=charset)
         cursor = db_con.cursor()
-        cursor.execute("""UPDATE ctop_ai_kuaishou_intelligence_strategy
-                        SET ai_strategy_request_content= %s
-                        WHERE ai_strategy_uuid = %s""", (str(self.res_data), self.ai_strategy_uuid))
+        if request_content:
+            cursor.execute("""UPDATE ctop_ai_kuaishou_intelligence_strategy
+                                SET ai_strategy_request_content= %s
+                                WHERE ai_strategy_uuid = %s""", (str(request_content), self.ai_strategy_uuid))
+        if message:
+            cursor.execute("""UPDATE ctop_ai_kuaishou_intelligence_strategy
+                                SET message = %s
+                                WHERE ai_strategy_uuid = %s""", (str(message), self.ai_strategy_uuid))
         db_con.commit()
         db_con.close()
 
-    # 如果手动请求的数据结构中,"campaign" 中的 campaign_id 为空则调用该新增计划方法
     def add_campaign(self):
         """
         新增广告计划, 创建成功之后写入计划层级操作表中
@@ -146,161 +163,333 @@ class ParseAddCampaignOrAddGroupRequest(object):
         #     },
         #     "message": "SUCCESS"}
 
+        campaign_info_to_db = {
+            'campaign_uuid': str(uuid.uuid4()),
+            'account_id': self.account_id,
+            'ai_strategy_uuid': self.ai_strategy_uuid,
+            'campaign_name': self.campaign_info['campaign_name'],
+            'campaign_type': self.advertiser_strategy['campaign_type'],
+            'operation_type': self.operation_type,
+            'status': 1,
+            'message': None,
+            'create_time': datetime.datetime.now()
+        }
+        res = 0
         if res_data['code'] == 0:
             self.campaign_id = res_data['data']['campaign_id']
-            campaign_info_to_db = {
-                                   'campaign_uuid': str(uuid.uuid4()),
-                                   'account_id': self.account_id,
-                                   'ai_strategy_uuid': self.ai_strategy_uuid,
-                                   'campaign_name': self.campaign_info['campaign_name'],
-                                   'campaign_id': self.campaign_id,
-                                   'campaign_type': self.advertiser_strategy['campaign_type'],
-                                   'operation_type': self.operation_type,
-                                   'campaign_create_time': res_data['data']['campaign_create_time'],
-                                   'create_time': datetime.datetime.now()
-                                   }
-            df = pd.DataFrame.from_dict(campaign_info_to_db, orient='index').T
-            df.to_sql(name="ctop_ai_kuaishou_campaign_level_operation_record",
-                      con=engine,
-                      if_exists='append',
-                      index=False)
-        else:
-            logger.info("广告计划创建失败:%s,  ai_strategy_uuid is %s" %
-                             (res_data['message'], self.ai_strategy_uuid))
-            return -1
+            campaign_info_to_db['campaign_id'] = self.campaign_id
+            campaign_info_to_db['campaign_create_time'] = res_data['data']['campaign_create_time']
+            campaign_info_to_db['message'] = res_data['message']
+            logger.info("广告计划创建成功,campaign_id = %s,  ai_strategy_uuid = %s" %
+                        (self.campaign_id, self.ai_strategy_uuid))
 
-    def assemble_group_and_creative_params(self):
-        """
-        拼接组和创意层级参数
-        写入数据库
-        :return:
-        """
-        # 1、组层级基本信息
+        else:
+            campaign_info_to_db['message'] = res_data['message']
+            campaign_info_to_db['status'] = -1
+            logger.error("广告计划创建失败:%s,  ai_strategy_uuid = %s" % (res_data['message'], self.ai_strategy_uuid))
+            res = -1
+
+        # 写入计划层级的操作表
+        df = pd.DataFrame.from_dict(campaign_info_to_db, orient='index').T
+        df.to_sql(name="ctop_ai_kuaishou_campaign_level_operation_record",
+                  con=engine,
+                  if_exists='append',
+                  index=False)
+        return res
+
+    def get_group_params(self):
+        group_params = self.advertiser_strategy.copy()
+        # 1 从客户策略表中,剔除掉不是组层级的参数(客户基本信息、计划层级信息、创意层级信息)
         drop_cols = ['id', 'account_id', 'status', 'campaign_type', 'campaign_name',
                      'creative_name', 'action_bar_text', 'description', 'short_slogan',
                      'sticker_title', 'overlay_type', 'expose_tag', 'new_expose_tag', 'site_id',
                      'click_track_url', 'impression_url', 'ad_photo_played_t3s_url', 'actionbar_click_url',
                      'creative_category', 'creative_tag', 'image_cnt',
                      'create_time', 'effective_time', 'expiry_time']
-
-        group_cols_to_list = ['app_store', 'scene_id', 'region', 'district_ids', 'ages_range', 'device_brand',
-                              'device_price', 'business_interest', 'fans_star', 'interest_video', 'app_interest',
-                              'app_ids', 'population', 'exclude_population', 'paid_audience', 'behavior_interest']
-
-        basic_group_info = self.advertiser_strategy.copy()
         for col in drop_cols:
-            del basic_group_info[col]
+            del group_params[col]
 
-        # 3、从请求的信息中更新组层级信息, 如组的名称、测试定向、测试出价等, update客户策略信息
+        # 2 从请求的信息中更新组层级信息, 如组的名称、测试定向、测试出价等, update客户策略信息
         if (self.group_info is not None) and (len(self.group_info) > 0):
             for key, value in self.group_info.items():
-                basic_group_info.update({key: value})
-
-
-
-        # 发送的请求,这几个字段需要转为 [] 数组
-        group_info_to_res_data = basic_group_info.copy()
-        for col in group_cols_to_list:
-            group_info_to_res_data[col] = eval(group_info_to_res_data[col]) \
-                if group_info_to_res_data[col] is not None else None
-
-        if 'group_name' in group_info_to_res_data.keys():
-            group_info_to_res_data['unit_name'] = group_info_to_res_data['group_name']
-            del group_info_to_res_data['group_name']
-
-
-        # 写入组层级操作记录数据表基本信息
-        group_info_to_db = basic_group_info.copy()
-        group_info_to_db['ai_strategy_uuid'] = self.ai_strategy_uuid
-        group_info_to_db['account_id'] = self.account_id
-        group_info_to_db['campaign_id'] = self.campaign_id
-        group_info_to_db['operation_type'] = self.operation_type
-
-        # 2、创意层级基本信息
-        creative_cols = ['creative_name',  'action_bar_text', 'description', 'short_slogan',
+                group_params.update({key: value})
+
+        # 3 拼装json的组参数
+        group_params_to_request = group_params.copy()
+        # 3-1 拼接发送请求json时, 以下字段需要转为 list 类型
+        cols_to_list = ['app_store', 'scene_id', 'region', 'district_ids', 'ages_range', 'device_brand',
+                        'device_price', 'business_interest', 'fans_star', 'interest_video', 'app_interest',
+                        'app_ids', 'population', 'exclude_population', 'paid_audience', 'behavior_interest']
+        for col in cols_to_list:
+            group_params_to_request[col] = eval(group_params_to_request[col]) \
+                if group_params_to_request[col] is not None else None
+        # 3-2 发送请求,需要将 group_name 改为 unit_name
+        group_params_to_request['unit_name'] = group_params_to_request.pop('group_name')
+
+        # 4 写入数据库的组参数
+        group_params_to_db = group_params.copy()
+        group_params_to_db['ai_strategy_uuid'] = self.ai_strategy_uuid
+        group_params_to_db['account_id'] = self.account_id
+        group_params_to_db['campaign_id'] = self.campaign_id
+        group_params_to_db['operation_type'] = self.operation_type
+        group_params_to_db['status'] = 1
+        group_params_to_db['message'] = None
+
+        return group_params_to_request, group_params_to_db
+
+    def get_creative_params(self):
+        # 1 从客户策略表中获取 创意层级基本信息
+        creative_cols = ['creative_name', 'action_bar_text', 'description', 'short_slogan',
                          'sticker_title', 'overlay_type', 'expose_tag', 'new_expose_tag', 'site_id',
                          'click_track_url', 'impression_url', 'ad_photo_played_t3s_url', 'actionbar_click_url',
                          'creative_category', 'creative_tag']
-        basic_creative_info = {}
+        creative_params = {}
         for col in creative_cols:
-            basic_creative_info[col] = self.advertiser_strategy.get(col)
+            creative_params[col] = self.advertiser_strategy.get(col)
 
-        # 2.1从请求的信息中更新创意层级的信息,如广告语
+        # 2 从请求的信息中更新创意层级的信息,如广告语
         if (self.creative_info is not None) and (len(self.creative_info) > 0):
             for key, value in self.creative_info.items():
-                basic_creative_info.update({key: value})
-
-        # 2.2
-        creative_info_to_res_data = basic_creative_info.copy()
-        creative_cols_to_list = ['creative_tag']
-        for col in creative_cols_to_list:
-            creative_info_to_res_data[col] = eval(creative_info_to_res_data[col]) \
-                if creative_info_to_res_data[col] is not None else None
-
-        # 2.3 写入创意层级操作记录数据表基本信息
-        creative_info_to_db = basic_creative_info.copy()
-        creative_info_to_db['ai_strategy_uuid'] = self.ai_strategy_uuid
-        creative_info_to_db['account_id'] = self.account_id
-        creative_info_to_db['campaign_id'] = self.campaign_id
-        creative_info_to_db['operation_type'] = self.operation_type
-
-        # 拼接最终请求数据
+                creative_params.update({key: value})
+
+        # 3 拼装json的 创意参数
+        creative_params_to_request = creative_params.copy()
+        # 3-1 拼接发送请求json时, 以下字段需要转为 list 类型
+        cols_to_list = ['creative_tag']
+        for col in cols_to_list:
+            creative_params_to_request[col] = eval(creative_params_to_request[col]) \
+                if creative_params_to_request[col] is not None else None
+
+        # 4 写入数据库的创意参数
+        creative_params_to_db = creative_params.copy()
+        creative_params_to_db['ai_strategy_uuid'] = self.ai_strategy_uuid
+        creative_params_to_db['account_id'] = self.account_id
+        creative_params_to_db['campaign_id'] = self.campaign_id
+        creative_params_to_db['operation_type'] = self.operation_type
+        creative_params_to_db['status'] = 1
+        creative_params_to_db['message'] = None
+
+        return creative_params_to_request, creative_params_to_db
+
+    def get_programme_creative_params(self):
+        # 1 从客户策略表中获取 创意层级基本信息
+        creative_cols = ['creative_name', 'action_bar_text', 'description', 'site_id',
+                         'click_track_url', 'actionbar_click_url',
+                         'creative_category', 'creative_tag']
+        creative_params = {}
+        for col in creative_cols:
+            creative_params[col] = self.advertiser_strategy.get(col)
+
+        # 2 从请求的信息中更新创意层级的信息,如广告语,创意名称等
+        if (self.creative_info is not None) and (len(self.creative_info) > 0):
+            for key, value in self.creative_info.items():
+                creative_params.update({key: value})
+
+        # 3 程序化创意部分字段发生改变,需要重命名
+        #   creative_name  --> package_name(程序化创意名称)
+        #   action_bar_text --> action_bar(行动号召按钮)
+        #   description --> captions(作品广告语)
+        #   click_track_url --> click_url(点击监测链接)
+        creative_params['package_name'] = creative_params.pop('creative_name')
+        creative_params['action_bar'] = creative_params.pop('action_bar_text')
+        creative_params['captions'] = creative_params.pop('description')
+        creative_params['click_url'] = creative_params.pop('click_track_url')
+
+        # 4 拼装json的 创意参数
+        creative_params_to_request = creative_params.copy()
+        # 4-1 拼接发送请求json时, 以下字段需要转为 list 类型
+        cols_to_list = ['creative_tag']
+        for col in cols_to_list:
+            creative_params_to_request[col] = eval(creative_params_to_request[col]) \
+                if creative_params_to_request[col] is not None else None
+
+        # 5 写入数据库的创意参数
+        creative_params_to_db = creative_params.copy()
+        creative_params_to_db['ai_strategy_uuid'] = self.ai_strategy_uuid
+        creative_params_to_db['account_id'] = self.account_id
+        creative_params_to_db['campaign_id'] = self.campaign_id
+        creative_params_to_db['operation_type'] = self.operation_type
+        creative_params_to_db['status'] = 1
+        creative_params_to_db['message'] = None
+
+        return creative_params_to_request, creative_params_to_db
+
+    def assemble_group_and_creative_params(self):
+        """
+        拼接组和创意层级参数
+        写入数据库
+        :return:
+        """
+        # 获取组和创意的基本参数
+        group_params_to_request, group_params_to_db = self.get_group_params()
+        creative_params_to_request, creative_params_to_db = self.get_creative_params()
+
         self.res_data['group_list'] = []
         group_cnt = 1
         for video in self.video:
             group_uuid = str(uuid.uuid4())
-            group_name = basic_group_info['group_name'] + '-' + str(group_cnt)
+            group_name = group_params_to_db['group_name'] + '-' + str(group_cnt)
 
             # 写入数据表中组信息
-            single_group_info_to_db = group_info_to_db.copy()
-            single_group_info_to_db['group_uuid'] = group_uuid
-            single_group_info_to_db['group_name'] = group_name
-            single_group_info_to_db['create_time'] = datetime.datetime.now()
-            df = pd.DataFrame.from_dict(single_group_info_to_db, orient='index').T
+            single_group_params_to_db = group_params_to_db.copy()
+            single_group_params_to_db['group_uuid'] = group_uuid
+            single_group_params_to_db['group_name'] = group_name
+            single_group_params_to_db['create_time'] = datetime.datetime.now()
+            df = pd.DataFrame.from_dict(single_group_params_to_db, orient='index').T
             df.to_sql(name="ctop_ai_kuaishou_unit_level_operation_record",
                       con=engine,
                       if_exists='append',
                       index=False)
 
             # 拼装返回json的组信息
-            single_group_info_to_res_data = group_info_to_res_data.copy()
-            single_group_info_to_res_data['group_uuid'] = group_uuid
-            single_group_info_to_res_data['unit_name'] = group_name
-            single_group_info_to_res_data['creative_list'] = []
+            single_group_params_to_request = group_params_to_request.copy()
+            single_group_params_to_request['group_uuid'] = group_uuid
+            single_group_params_to_request['unit_name'] = group_name
+            single_group_params_to_request['creative_list'] = []
             group_cnt += 1
 
             # 拼装组下面创意的信息
             creative_cnt = 1
             for image_md5 in video['imageList'][: self.advertiser_strategy['image_cnt']]:
                 creative_uuid = str(uuid.uuid4())
-                creative_name = basic_creative_info['creative_name'] + '_' + str(creative_cnt)
-
-                single_creative_info_to_db = creative_info_to_db.copy()
-                single_creative_info_to_db['creative_uuid'] = creative_uuid
-                single_creative_info_to_db['creative_name'] = creative_name
-                single_creative_info_to_db['photo_id'] = video['photo_id']
-                single_creative_info_to_db['image_md5'] = image_md5
-                single_creative_info_to_db['create_time'] = datetime.datetime.now()
-                df = pd.DataFrame.from_dict(single_creative_info_to_db, orient='index').T
+                creative_name = creative_params_to_db['creative_name'] + '_' + str(creative_cnt)
+
+                single_creative_params_to_db = creative_params_to_db.copy()
+                single_creative_params_to_db['creative_uuid'] = creative_uuid
+                single_creative_params_to_db['creative_name'] = creative_name
+                single_creative_params_to_db['photo_id'] = video['photo_id']
+                single_creative_params_to_db['image_md5'] = image_md5
+                single_creative_params_to_db['create_time'] = datetime.datetime.now()
+                df = pd.DataFrame.from_dict(single_creative_params_to_db, orient='index').T
                 df.to_sql(name="ctop_ai_kuaishou_creative_level_operation_record",
                           con=engine,
                           if_exists='append',
                           index=False)
 
-                single_creative_info_to_res_data = creative_info_to_res_data.copy()
-                single_creative_info_to_res_data['creative_uuid'] = creative_uuid
-                single_creative_info_to_res_data['creative_name'] = creative_name
-                single_creative_info_to_res_data['photo_id'] = video['photo_id']
-                single_creative_info_to_res_data['image_md5'] = image_md5
-                single_group_info_to_res_data['creative_list'].append(single_creative_info_to_res_data)
+                single_creative_params_to_request = creative_params_to_request.copy()
+                single_creative_params_to_request['creative_uuid'] = creative_uuid
+                single_creative_params_to_request['creative_name'] = creative_name
+                single_creative_params_to_request['photo_id'] = video['photo_id']
+                single_creative_params_to_request['image_md5'] = image_md5
 
+                # 单个创意信息加入到组里面的 creative_list 中
+                single_group_params_to_request['creative_list'].append(single_creative_params_to_request)
                 creative_cnt += 1
             # 组和创意信息添加到最终返回数据 group_list 中
-            self.res_data['group_list'].append(single_group_info_to_res_data)
+            self.res_data['group_list'].append(single_group_params_to_request)
 
         self.res_data['account_id'] = self.account_id
         self.res_data['campaign_id'] = self.campaign_id
 
+    def assemble_group_and_programme_creative_params(self):
+        """
+        拼接组和程序化创意的参数
+        写入数据库
+        :return:
+        """
+        # 获取组和创意的基本参数
+        group_params_to_request, group_params_to_db = self.get_group_params()
+        creative_params_to_request, creative_params_to_db = self.get_programme_creative_params()
+
+        # 每5个视频一组,来拼接组层级 和 程序化创意 层级的参数
+        self.res_data['group_list'] = []
+        cnt = 1
+        for i in range(0, len(self.video), 5):
+            group_uuid = str(uuid.uuid4())
+            group_name = group_params_to_db['group_name'] + '-' + str(cnt)
+
+            # 写入数据表中组信息
+            single_group_params_to_db = group_params_to_db.copy()
+            single_group_params_to_db['group_uuid'] = group_uuid
+            single_group_params_to_db['group_name'] = group_name
+            single_group_params_to_db['create_time'] = datetime.datetime.now()
+            df = pd.DataFrame.from_dict(single_group_params_to_db, orient='index').T
+            df.to_sql(name="ctop_ai_kuaishou_unit_level_operation_record",
+                      con=engine,
+                      if_exists='append',
+                      index=False)
+
+            # 拼装返回json的组信息
+            single_group_params_to_request = group_params_to_request.copy()
+            single_group_params_to_request['group_uuid'] = group_uuid
+            single_group_params_to_request['unit_name'] = group_name
+            single_group_params_to_request['creative_list'] = []
+            single_group_params_to_request['programCreative'] = None
+
+            # 拼装程序化创意的信息
+            horizontal_photo_ids = []
+            vertical_photo_ids = []
+            cover_image_tokens = []
+            if i+5 < len(self.video):
+                for j in range(i, i+5, 1):
+                    if self.video[j]['material_type'] == 1:
+                        horizontal_photo_ids.append(self.video[j]['photo_id'])
+                        cover_image_tokens.append(self.video[j]['imageList'][0])
+                    if self.video[j]['material_type'] == 2:
+                        vertical_photo_ids.append(self.video[j]['photo_id'])
+                        cover_image_tokens.append(self.video[j]['imageList'][0])
+                if len(cover_image_tokens) >= 5:
+                    cover_image_tokens = cover_image_tokens[:4]
+
+                creative_uuid = str(uuid.uuid4())
+                package_name = creative_params_to_db['package_name'] + '_' + str(cnt)
+                single_creative_params_to_db = creative_params_to_db.copy()
+                single_creative_params_to_db['creative_uuid'] = creative_uuid
+                single_creative_params_to_db['package_name'] = package_name
+                single_creative_params_to_db['horizontal_photo_ids'] = horizontal_photo_ids
+                single_creative_params_to_db['vertical_photo_ids'] = vertical_photo_ids
+                single_creative_params_to_db['cover_image_tokens'] = cover_image_tokens
+                single_creative_params_to_db['create_time'] = datetime.datetime.now()
+                df = pd.DataFrame.from_dict(single_creative_params_to_db, orient='index').T
+                df.to_sql(name="ctop_ai_kuaishou_program_creative_level_operation_record",
+                          con=engine,
+                          if_exists='append',
+                          index=False)
+
+                single_creative_params_to_request = creative_params_to_request.copy()
+                single_creative_params_to_request['creative_uuid'] = creative_uuid
+                single_creative_params_to_request['package_name'] = package_name
+                single_creative_params_to_request['horizontal_photo_ids'] = horizontal_photo_ids
+                single_creative_params_to_request['vertical_photo_ids'] = vertical_photo_ids
+                single_creative_params_to_request['image_md5s'] = cover_image_tokens
+            else:
+                for j in range(i, len(self.video)):
+                    if self.video[j]['material_type'] == 1:
+                        horizontal_photo_ids.append(self.video[j]['photo_id'])
+                        cover_image_tokens.append(self.video[j]['imageList'][0])
+                    if self.video[j]['material_type'] == 2:
+                        vertical_photo_ids.append(self.video[j]['photo_id'])
+                        cover_image_tokens.append(self.video[j]['imageList'][0])
+                if len(cover_image_tokens) >= 5:
+                    cover_image_tokens = cover_image_tokens[:4]
+
+                creative_uuid = str(uuid.uuid4())
+                package_name = creative_params_to_db['package_name'] + '_' + str(cnt)
+                single_creative_params_to_db = creative_params_to_db.copy()
+                single_creative_params_to_db['creative_uuid'] = creative_uuid
+                single_creative_params_to_db['package_name'] = package_name
+                single_creative_params_to_db['horizontal_photo_ids'] = horizontal_photo_ids
+                single_creative_params_to_db['vertical_photo_ids'] = vertical_photo_ids
+                single_creative_params_to_db['cover_image_tokens'] = cover_image_tokens
+                single_creative_params_to_db['create_time'] = datetime.datetime.now()
+                df = pd.DataFrame.from_dict(single_creative_params_to_db, orient='index').T
+                df.to_sql(name="ctop_ai_kuaishou_program_creative_level_operation_record",
+                          con=engine,
+                          if_exists='append',
+                          index=False)
+
+                single_creative_params_to_request = creative_params_to_request.copy()
+                single_creative_params_to_request['creative_uuid'] = creative_uuid
+                single_creative_params_to_request['package_name'] = package_name
+                single_creative_params_to_request['horizontal_photo_ids'] = horizontal_photo_ids
+                single_creative_params_to_request['vertical_photo_ids'] = vertical_photo_ids
+                single_creative_params_to_request['image_md5s'] = cover_image_tokens
+
+            single_group_params_to_request['programCreative'] = single_creative_params_to_request
+            cnt += 1
+
+        self.res_data['group_list'].append(single_group_params_to_request)
+
 
 # TODO 修改操作
 class ParseModifyRequest(object):

+ 182 - 19
ai_time_task_creative_handler.py

@@ -12,7 +12,7 @@ from ai_strategy_request_func import ai_strategy_request_parse
 from utils.DataBaseConfig import *
 
 
-log_handler = TimedRotatingFileHandler("logs/ai_auto_create/ai_auto_create.log",
+log_handler = TimedRotatingFileHandler("logs/ai_auto_create.log",
                                        when="midnight", backupCount=100)
 log_handler.setFormatter(log_formatter)
 logger = logging.getLogger('ai_time_task_creative_logger')
@@ -41,7 +41,8 @@ class AiHistoricalMissingMaterial(tornado.web.RequestHandler):
                                                 get_missing_video_url,
                                                 missing_video_start_time,
                                                 missing_video_end_time)
-            if video_info is None:
+            if video_info['code'] != 0:
+                logger.info("the res of video is %s" % video_info['message'])
                 logger.info("没有获取到视频,不发送ai策略请求!")
                 self.write(json.dumps({"message": "没有获取到视频,不发送ai策略请求!"}))
                 self.flush()
@@ -52,8 +53,13 @@ class AiHistoricalMissingMaterial(tornado.web.RequestHandler):
                     self.write(json.dumps({"message": "没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!"}))
                     self.flush()
                 else:
-                    request_data = get_request_data(account_id, campaign_id, video_info, campaign_name, group_name,
-                                                    "补充遗漏素材", "补充遗漏素材")
+                    request_data = get_request_data(account_id=account_id,
+                                                    campaign_id=campaign_id,
+                                                    video_info=video_info,
+                                                    campaign_name=campaign_name,
+                                                    group_name=group_name,
+                                                    ai_strategy_remark="补充遗漏素材",
+                                                    name_replace="补充遗漏素材")
                     request = ai_strategy_request_parse(request_data)
                     logger.info("ai策略的返回信息:%s" % request)
                     self.write(json.dumps(request))
@@ -95,7 +101,8 @@ class AiAutoCreative(tornado.web.RequestHandler):
                     break
 
             video_info = get_new_video_info(account_id, get_new_video_url)
-            if video_info is None:
+            if video_info['code'] != 0:
+                logger.info("the res of video is %s" % video_info['message'])
                 logger.info("没有获取到视频,不发送ai策略请求!")
                 self.write(json.dumps({"message": "没有获取到视频,不发送ai策略请求!"}))
                 self.flush()
@@ -106,8 +113,13 @@ class AiAutoCreative(tornado.web.RequestHandler):
                     self.write(json.dumps({"message": "没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!"}))
                     self.flush()
                 else:
-                    request_data = get_request_data(account_id, campaign_id, video_info, campaign_name, group_name,
-                                                    "自动上新", "素材自动上新")
+                    request_data = get_request_data(account_id=account_id,
+                                                    campaign_id=campaign_id,
+                                                    video_info=video_info,
+                                                    campaign_name=campaign_name,
+                                                    group_name=group_name,
+                                                    ai_strategy_remark="自动上新",
+                                                    name_replace="素材自动上新")
                     request = ai_strategy_request_parse(request_data)
                     logger.info("ai策略的返回信息:%s" % request)
                     self.write(json.dumps(request))
@@ -135,7 +147,8 @@ class AiHighQualityMaterial(tornado.web.RequestHandler):
             video_info = get_top_video_info(account_id, high_quality_video_cnt,
                                             (datetime.datetime.now() + datetime.timedelta(days=-high_quality_video_days)).strftime("%Y-%m-%d"),
                                              str(datetime.date.today()), get_high_quality_video_url)
-            if video_info is None:
+            if video_info['code'] != 0:
+                logger.info("the res of video is %s" % video_info['message'])
                 logger.info("没有获取到视频,不发送ai策略请求!")
                 self.write(json.dumps({"message": "没有获取到视频,不发送ai策略请求!"}))
                 self.flush()
@@ -146,8 +159,13 @@ class AiHighQualityMaterial(tornado.web.RequestHandler):
                     self.write(json.dumps({"message": "没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!"}))
                     self.flush()
                 else:
-                    request_data = get_request_data(account_id, campaign_id, video_info, campaign_name, group_name,
-                                                    "高质量素材", "高质量素材")
+                    request_data = get_request_data(account_id=account_id,
+                                                    campaign_id=campaign_id,
+                                                    video_info=video_info,
+                                                    campaign_name=campaign_name,
+                                                    group_name=group_name,
+                                                    ai_strategy_remark="高质量素材复建",
+                                                    name_replace="高质量素材")
                     request = ai_strategy_request_parse(request_data)
                     logger.info("ai策略的返回信息:%s" % request)
                     self.write(json.dumps(request))
@@ -226,11 +244,12 @@ class AiCheckAndUpTOFullCreative(tornado.web.RequestHandler):
                 logger.info("需要补充的视频数量:%s" % video_cnt)
                 now = datetime.datetime.now()
                 video_info = get_top_video_info(account_id,
-                                            video_cnt,
-                                            (now + datetime.timedelta(days=-2*high_quality_video_days)).strftime("%Y-%m-%d"),
-                                            (now + datetime.timedelta(days=-high_quality_video_days)).strftime("%Y-%m-%d"),
-                                            get_high_quality_video_url)
-                if video_info is None:
+                                                video_cnt,
+                                                (now + datetime.timedelta(days=-2*high_quality_video_days)).strftime("%Y-%m-%d"),
+                                                (now + datetime.timedelta(days=-high_quality_video_days)).strftime("%Y-%m-%d"),
+                                                get_high_quality_video_url)
+                if video_info['code'] != 0:
+                    logger.info("the res of video is %s" % video_info['message'])
                     logger.info("没有获取到视频,不发送ai策略请求!")
                     self.write(json.dumps({"message": "没有获取到视频,不发送ai策略请求!"}))
                     self.flush()
@@ -241,8 +260,13 @@ class AiCheckAndUpTOFullCreative(tornado.web.RequestHandler):
                         self.write(json.dumps({"message": "没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!"}))
                         self.flush()
                     else:
-                        request_data = get_request_data(account_id, campaign_id, video_info, campaign_name, group_name,
-                                                        "高质量素材", "高质量素材")
+                        request_data = get_request_data(account_id=account_id,
+                                                        campaign_id=campaign_id,
+                                                        video_info=video_info,
+                                                        campaign_name=campaign_name,
+                                                        group_name=group_name,
+                                                        ai_strategy_remark="用高质量素材补满创意",
+                                                        name_replace="高质量素材")
                         request = ai_strategy_request_parse(request_data)
                         logger.info("ai策略的返回信息:%s" % request)
                         self.write(json.dumps(request))
@@ -253,7 +277,145 @@ class AiCheckAndUpTOFullCreative(tornado.web.RequestHandler):
             self.flush()
 
 
-def get_request_data(account_id, campaign_id, video_info, campaign_name, group_name,ai_strategy_remark, name_replace):
+class AiProgramCreativeHighQualityMaterial(tornado.web.RequestHandler):
+    """
+    使用历史跑量素材创建程序化创意
+    跑量素材150个(近14天的素材)
+    计划名称--跑量素材程序化
+    timeTask: 每天上午10点执行 --(0 0 10 * * ?)
+    ----------------------------------------
+    拼装参数和写入数据库时,需要修改名称(程序化与非程序化不一致):
+    creative_name  --> package_name(程序化创意名称)
+    action_bar_text --> action_bar(行动号召按钮)
+    description --> captions(作品广告语)
+    click_track_url --> click_url
+
+    写入数据库时:
+    image_md5s --> cover_image_tokens
+
+    程序化与非程序化的逻辑不一样:
+    需要5个视频4张封面一组,拼装程序化创意参数需要单独抽取一个方法出来
+
+    所有操作记录表中,添加status信息(已提交,成功,失败),和回调时的 message信息
+
+    """
+    def post(self):
+        data = self.request.body
+        data = str(data, 'utf8')
+        data = json.loads(data, encoding='utf8')
+        logger.info("***************************************  NEW REQUEST ***************************************")
+        logger.info("raw data from request is %s" % data)
+
+        account_id = data.get('account_id')
+        campaign_id = data.get('campaign_id')
+
+        try:
+            video_info = get_top_video_info(account_id, high_quality_video_cnt,
+                                            (datetime.datetime.now() + datetime.timedelta(
+                                             days=-high_quality_video_days)).strftime("%Y-%m-%d"),
+                                            str(datetime.date.today()), get_high_quality_video_url)
+            if video_info['code'] != 0:
+                logger.info("the res of video is %s" % video_info['message'])
+                logger.info("没有获取到视频,不发送ai策略请求!")
+                self.write(json.dumps({"message": "没有获取到视频,不发送ai策略请求!"}))
+                self.flush()
+            else:
+                campaign_name, group_name = get_campaign_and_group_name_rule(account_id)
+                if (campaign_name is None) or (group_name is None):
+                    logger.info("没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!")
+                    self.write(json.dumps({"message": "没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!"}))
+                    self.flush()
+                else:
+                    request_data = get_request_data(account_id=account_id,
+                                                    campaign_id=campaign_id,
+                                                    video_info=video_info,
+                                                    campaign_name=campaign_name,
+                                                    group_name=group_name,
+                                                    ai_strategy_remark="程序化高质量素材",
+                                                    name_replace="程序化高质量素材",
+                                                    unit_type=7)
+                    request = ai_strategy_request_parse(request_data)
+                    logger.info("ai策略的返回信息:%s" % request)
+                    self.write(json.dumps(request))
+                    self.flush()
+        except Exception:
+            logger.error(traceback.format_exc())
+            self.write(json.dumps(traceback.format_exc()))
+            self.flush()
+
+
+# TODO 获取最新的素材 更换素材请求接口
+class AiProgramCreativeNewMaterial(tornado.web.RequestHandler):
+    """
+    使用历史跑量素材创建程序化创意
+    跑量素材150个(近14天的素材)
+    计划名称--跑量素材程序化
+    timeTask: 每天上午10点执行 --(0 0 10 * * ?)
+    ----------------------------------------
+    拼装参数和写入数据库时,需要修改名称(程序化与非程序化不一致):
+    creative_name  --> package_name(程序化创意名称)
+    action_bar_text --> action_bar(行动号召按钮)
+    description --> captions(作品广告语)
+    click_track_url --> click_url
+
+    写入数据库时:
+    image_md5s --> cover_image_tokens
+
+    程序化与非程序化的逻辑不一样:
+    需要5个视频4张封面一组,拼装程序化创意参数需要单独抽取一个方法出来
+
+    所有操作记录表中,添加status信息(已提交,成功,失败),和回调时的 message信息
+
+    """
+    def post(self):
+        data = self.request.body
+        data = str(data, 'utf8')
+        data = json.loads(data, encoding='utf8')
+        logger.info("***************************************  NEW REQUEST ***************************************")
+        logger.info("raw data from request is %s" % data)
+
+        account_id = data.get('account_id')
+        campaign_id = data.get('campaign_id')
+
+        try:
+            # TODO 获取最新的素材 更换素材请求接口
+            video_info = get_top_video_info(account_id, high_quality_video_cnt,
+                                            (datetime.datetime.now() + datetime.timedelta(
+                                             days=-high_quality_video_days)).strftime("%Y-%m-%d"),
+                                            str(datetime.date.today()), get_high_quality_video_url)
+            if video_info['code'] != 0:
+                logger.info("the res of video is %s" % video_info['message'])
+                logger.info("没有获取到视频,不发送ai策略请求!")
+                self.write(json.dumps({"message": "没有获取到视频,不发送ai策略请求!"}))
+                self.flush()
+            else:
+                campaign_name, group_name = get_campaign_and_group_name_rule(account_id)
+                if (campaign_name is None) or (group_name is None):
+                    logger.info("没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!")
+                    self.write(json.dumps({"message": "没有获取到广告计划命名规则 或 广告组命名规则,不发送ai策略请求!"}))
+                    self.flush()
+                else:
+                    request_data = get_request_data(account_id=account_id,
+                                                    campaign_id=campaign_id,
+                                                    video_info=video_info,
+                                                    campaign_name=campaign_name,
+                                                    group_name=group_name,
+                                                    ai_strategy_remark="程序化上新素材",
+                                                    name_replace="程序化高上新素材",
+                                                    unit_type=7)
+                    request = ai_strategy_request_parse(request_data)
+                    logger.info("ai策略的返回信息:%s" % request)
+                    self.write(json.dumps(request))
+                    self.flush()
+        except Exception:
+            logger.error(traceback.format_exc())
+            self.write(json.dumps(traceback.format_exc()))
+            self.flush()
+
+
+def get_request_data(account_id, campaign_id, video_info,
+                     campaign_name, group_name, ai_strategy_remark,
+                     name_replace, unit_type=4):
     res = {
         "video": video_info,
         "operation_type": 1,
@@ -264,7 +426,8 @@ def get_request_data(account_id, campaign_id, video_info, campaign_name, group_n
              "campaign_name": campaign_name.replace('自定义', name_replace) + str(datetime.datetime.now())},
         'group_info':
             {'group_name': group_name.replace('自定义', name_replace) + str(datetime.datetime.now()),
-             'begin_time': str(datetime.date.today())},
+             'begin_time': str(datetime.date.today()),
+             'unit_type': unit_type},
         'creative_info': {'is_sticky': 0}
     }
     return res

+ 46 - 24
utils/CommonFunction.py

@@ -3,17 +3,6 @@ from utils.UrlConfig import headers
 import json
 import pandas as pd
 from utils.DataBaseConfig import *
-from logging.handlers import TimedRotatingFileHandler
-from utils.LogConfig import *
-
-log_handler = TimedRotatingFileHandler("logs/ai_auto_create/ai_auto_create.log",
-                                       when="midnight", backupCount=100)
-log_handler.setFormatter(log_formatter)
-logger = logging.getLogger('ai_common_func_logger')
-logger.addHandler(log_handler)
-logger.setLevel(logging.DEBUG)
-print('id of ai_common_func_logger %s' % id(logger))
-logger.info("ai_common_func started!")
 
 
 def get_lower_case_name(name):
@@ -26,33 +15,67 @@ def get_lower_case_name(name):
 
 
 def get_new_video_info(account_id, url):
+    """
+    url:http://192.168.1.8:8080/jeecg-boot/kuaishou/material/getNewVideoList
+    请求方式:POST
+    remark: 返回 upload_time 在上次调用时间到本次调用时间内的素材(第一次调用返回5分钟前到本次调用时间的素材)
+    入参:accountId
+    """
     request_data = {"accountId": account_id}
     request = requests.post(url, headers=headers, data=json.JSONEncoder().encode(request_data)).text
     request = json.loads(request)
-    if request['code'] != 0:
-        logger.info('the res of get_new_video_info is %s' % request)
-        return None
-    return request['data']
+    return request
 
 
 def get_missing_video_info(account_id, cnt, url, start_time, end_time):
+    """
+    url:"http://192.168.1.8:8080/jeecg-boot/kuaishou/material/getCreateZeroVideoList"
+    请求方式:POST
+    remark: 视频的upload_time 在startTime 和 endTime 之间,创意关联个数为0,order by upload_time asc limit videoCnt
+    入参:
+        accountId:  账户id
+        videoCnt:  查询数量
+        startTime: 开始时间
+        endTime:   结束时间
+    """
+
     request_data = {"accountId": account_id, "videoCnt": cnt, "startTime": start_time, "endTime": end_time}
     request = requests.post(url, headers=headers, data=json.JSONEncoder().encode(request_data)).text
     request = json.loads(request)
-    if request['code'] != 0:
-        logger.info('the res of get_missing_video_info is %s' % request)
-        return None
-    return request['data']
+    return request
 
 
 def get_top_video_info(account_id, cnt, start_time, end_time, url):
+    """
+    url:http://192.168.1.8:8080/jeecg-boot/kuaishou/material/getHistoryTopVideoList
+    请求方式:POST
+    remark: ctop_kuaishou_report_daily_material
+            整个项目维度 stat_date 在 startTime 和 endTime, order by sum(charge) desc limit num
+    入参:
+        accountId:  账户id
+        startTime: 开始时间
+        endTime:   结束时间
+        num: 查询数量
+    返回:
+    {
+    "code": 0,
+    "data": [
+        {
+            "charge": 40871.123,
+            "video_url": "&di=811c96e5&bp=13890",
+            "photo_id": "5254011957394409468",
+            "signature": "4af22090cddc32773c7707de3c652f77",
+            "channel_type": 0,
+            "material_type":1, --1:竖版 2:横版
+            "imageList": ["09f61ca7174160c12d9a68306049ca82","46996bea84287692b3884686a52903ed"]
+        }
+        ],
+    "message": "SUCCESS"
+    """
     request_data = {"accountId": account_id, "num": cnt, "startTime": start_time, "endTime": end_time}
     request = requests.post(url, headers=headers, data=json.JSONEncoder().encode(request_data)).text
     request = json.loads(request)
-    if request['code'] != 0:
-        logger.info('the res of get_top_video_info is %s' % request)
-        return None
-    return request['data']
+    return request
 
 
 def get_campaign_and_group_name_rule(account_id):
@@ -65,7 +88,6 @@ def get_campaign_and_group_name_rule(account_id):
     df = pd.read_sql(sql, engine)
     if df.empty:
         return None, None
-
     campaign_name = df['campaign_name'].values[0]
     group_name = df['group_name'].values[0]
     return campaign_name, group_name