|
@@ -1,97 +1,60 @@
|
|
-import uuid
|
|
|
|
import datetime
|
|
import datetime
|
|
-import tornado
|
|
|
|
import json
|
|
import json
|
|
-import traceback
|
|
|
|
-import pandas as pd
|
|
|
|
|
|
+import random
|
|
|
|
+import uuid
|
|
|
|
+
|
|
import numpy as np
|
|
import numpy as np
|
|
|
|
+import pandas as pd
|
|
import pymysql
|
|
import pymysql
|
|
-import os
|
|
|
|
-import yaml
|
|
|
|
-import random
|
|
|
|
import requests
|
|
import requests
|
|
-from utils.commonFunc import get_db_engine, is_contains_gender, is_contains_age, is_contains_region
|
|
|
|
|
|
+import yaml
|
|
|
|
+from loguru import logger
|
|
|
|
+
|
|
|
|
+from config.url_and_db import create_campaign_url, create_group_and_creative_url, headers, \
|
|
|
|
+ update_campaign_status_url, jeecg_db, jeecg_product_db
|
|
from utils.BaseClass import NpEncoder
|
|
from utils.BaseClass import NpEncoder
|
|
-from config.url import create_campaign_url, create_group_and_creative_url, headers, update_campaign_status_url
|
|
|
|
-import logging
|
|
|
|
-from concurrent_log import ConcurrentTimedRotatingFileHandler
|
|
|
|
-
|
|
|
|
-log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%Y/%m/%d %I:%M:%S %p')
|
|
|
|
-log_handler = ConcurrentTimedRotatingFileHandler("/data/pythonProject/ai_target/logs/ai_target_combine.log", when="midnight", backupCount=100)
|
|
|
|
-log_handler.setFormatter(log_formatter)
|
|
|
|
-logger = logging.getLogger('ai_strategy_request_logger')
|
|
|
|
-logger.addHandler(log_handler)
|
|
|
|
-logger.setLevel(logging.DEBUG)
|
|
|
|
-logger.info("ai_target_combine_logger started!")
|
|
|
|
-print('id of ai_target_combine_logger %s' % id(logger))
|
|
|
|
|
|
+from utils.commonFunc import is_contains_gender, is_contains_age, is_contains_region
|
|
|
|
|
|
with open('/data/pythonProject/ai_target/config/config.yaml', mode='r', encoding='utf-8') as f:
|
|
with open('/data/pythonProject/ai_target/config/config.yaml', mode='r', encoding='utf-8') as f:
|
|
config = yaml.load(f.read(), Loader=yaml.FullLoader)
|
|
config = yaml.load(f.read(), Loader=yaml.FullLoader)
|
|
|
|
|
|
|
|
|
|
-class AiTargetCombine(tornado.web.RequestHandler):
|
|
|
|
- def post(self):
|
|
|
|
- # 解析接收到的参数
|
|
|
|
- data = self.request.body
|
|
|
|
- data = str(data, 'utf8')
|
|
|
|
- data = json.loads(data, encoding='utf8')
|
|
|
|
- account_id = data['account_id']
|
|
|
|
- project_id = data['project_id']
|
|
|
|
- logger.info("账户信息=%s, 当前进程=%s, 当前进程的主进程=%s" % (account_id, os.getpid(), os.getppid()))
|
|
|
|
- try:
|
|
|
|
- # 1、创建类实例
|
|
|
|
- ins = GetTargetAndAssemblyParameters(account_id, project_id)
|
|
|
|
-
|
|
|
|
- # 2、判断target_combine_to_create是否为空,为空则不执行后续的操作
|
|
|
|
- if not ins.target_combine_to_create:
|
|
|
|
- logger.info("account_id = %s, message = '没有定向组合: 定向冲突或者素材关联的创意个数达到上限' " % ins.account_id)
|
|
|
|
- self.write(json.dumps({"account_id": account_id, "message": "没有定向组合: 定向冲突或者素材关联的创意个数达到上限", "code": 1}))
|
|
|
|
- self.flush()
|
|
|
|
- else:
|
|
|
|
- # 2、判断当天是否已经存在包含【优质定向】的广告计划,如果有则获取该计划id,否则需要新建计划
|
|
|
|
- sql = """select campaign_id, campaign_name from ctop_kuaishou_campaign
|
|
|
|
- where account_id = %s
|
|
|
|
- and put_create_time >= curdate()
|
|
|
|
- and campaign_name like '%s'""" % (ins.account_id, '%%优质定向%%')
|
|
|
|
- df = pd.read_sql(sql, ins.engine)
|
|
|
|
- if df.empty:
|
|
|
|
- ins.add_campaign()
|
|
|
|
- else:
|
|
|
|
- ins.campaign_id = df['campaign_id'].values[0]
|
|
|
|
-
|
|
|
|
- # 3、拼装广告组和创意的参数
|
|
|
|
- ins.assembly_group_and_creative_params()
|
|
|
|
-
|
|
|
|
- # 4、信息写如到智能策略表中
|
|
|
|
- ins.write_intelligence_strategy_table()
|
|
|
|
-
|
|
|
|
- # 5、
|
|
|
|
-
|
|
|
|
- # 5、发送创建组和创意的请求
|
|
|
|
- request = requests.post(create_group_and_creative_url,
|
|
|
|
- headers=headers,
|
|
|
|
- data=json.dumps(ins.request_data, cls=NpEncoder))
|
|
|
|
- response_data = json.loads(request.text)
|
|
|
|
-
|
|
|
|
- # 6、接口的返回信息更新到数据库表
|
|
|
|
- ins.update_intelligence_strategy_table(message=response_data)
|
|
|
|
-
|
|
|
|
- logger.info("account_id = %s, 策略uuid = %s 的请求返回信息:%s" % (ins.account_id, ins.ai_strategy_uuid, response_data))
|
|
|
|
- self.write(json.dumps({"account_id": account_id, "message": str(response_data), "code": 1}))
|
|
|
|
- self.flush()
|
|
|
|
- except:
|
|
|
|
- logger.error("account_id = %s, traceback is %s" % (account_id, traceback.format_exc()))
|
|
|
|
- self.write(json.dumps({"account_id": account_id, "message": str(traceback.format_exc()), "code": -1}))
|
|
|
|
- self.flush()
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-class GetTargetAndAssemblyParameters(object):
|
|
|
|
|
|
+@logger.catch()
|
|
|
|
+def ai_target_combine_create(account_id: int, project_id: int):
|
|
|
|
+ logger.info(f"account_id = {account_id}, project_id = {project_id} 开始创建优质定向...")
|
|
|
|
+ ins = GetTargetAndCreateAds(account_id, project_id)
|
|
|
|
+
|
|
|
|
+ # 2、判断 target_combine_to_create 是否为空,为空则不执行后续的操作
|
|
|
|
+ if not ins.target_combine_to_create:
|
|
|
|
+ logger.info(f"account_id = {account_id}, project_id = {project_id}, "
|
|
|
|
+ f"message = 没有定向组合: 定向冲突或者素材关联的创意个数达到上限, 创建优质定向已结束 ")
|
|
|
|
+ else:
|
|
|
|
+ # 创建或获取计划信息
|
|
|
|
+ ins.add_campaign()
|
|
|
|
+
|
|
|
|
+ # 3、拼装广告组和创意的参数
|
|
|
|
+ ins.assembly_group_and_creative_params()
|
|
|
|
+
|
|
|
|
+ # 4、信息写如到智能策略表中
|
|
|
|
+ ins.write_intelligence_strategy_table()
|
|
|
|
+
|
|
|
|
+ # 5、发送创建组和创意的请求
|
|
|
|
+ request = requests.post(create_group_and_creative_url,
|
|
|
|
+ headers=headers,
|
|
|
|
+ data=json.dumps(ins.request_data, cls=NpEncoder))
|
|
|
|
+ response_data = json.loads(request.text)
|
|
|
|
+
|
|
|
|
+ # 6、接口的返回信息更新到数据库表
|
|
|
|
+ ins.update_intelligence_strategy_table(message=response_data)
|
|
|
|
+
|
|
|
|
+ logger.info(f"account_id = {account_id}, 策略uuid = {ins.ai_strategy_uuid} 的请求返回信息:{response_data}")
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class GetTargetAndCreateAds(object):
|
|
def __init__(self, account_id, project_id):
|
|
def __init__(self, account_id, project_id):
|
|
self.account_id = account_id
|
|
self.account_id = account_id
|
|
self.project_id = project_id
|
|
self.project_id = project_id
|
|
- self.engine = None
|
|
|
|
- self.product_engine = None
|
|
|
|
- self.db_config = None
|
|
|
|
self.advertiser_strategy_id = None
|
|
self.advertiser_strategy_id = None
|
|
self.advertiser_strategy = {}
|
|
self.advertiser_strategy = {}
|
|
self.ai_strategy_uuid = str(uuid.uuid4())
|
|
self.ai_strategy_uuid = str(uuid.uuid4())
|
|
@@ -101,42 +64,23 @@ class GetTargetAndAssemblyParameters(object):
|
|
self.target_combine_from_table = []
|
|
self.target_combine_from_table = []
|
|
self.target_combine_filter_by_subset = []
|
|
self.target_combine_filter_by_subset = []
|
|
self.target_combine_to_create = []
|
|
self.target_combine_to_create = []
|
|
- self.get_database_engine() # 获取数据库信息
|
|
|
|
self.get_advertiser_strategy_info() # 获取账户配置信息
|
|
self.get_advertiser_strategy_info() # 获取账户配置信息
|
|
self.get_signature_and_target() # 从素材定向组合表中获取最新的定向组合
|
|
self.get_signature_and_target() # 从素材定向组合表中获取最新的定向组合
|
|
self.filter_target_combine_by_whether_is_subset() # 筛选出为账户配置子集的定向组合
|
|
self.filter_target_combine_by_whether_is_subset() # 筛选出为账户配置子集的定向组合
|
|
self.filter_target_combine_by_creative_cnt() # 依据素材在指定账户下关联的创意个数,进行筛选创建个数,防止超限导致的创建失败
|
|
self.filter_target_combine_by_creative_cnt() # 依据素材在指定账户下关联的创意个数,进行筛选创建个数,防止超限导致的创建失败
|
|
|
|
|
|
- def get_database_engine(self):
|
|
|
|
- # 数据库连接引擎,依据开发环境/生产环境 进行切换
|
|
|
|
- if os.getenv('LYY_DEV', 'unknown') == 'dev':
|
|
|
|
- self.engine = get_db_engine(config['devDB'])
|
|
|
|
- self.db_config = config['devDB']
|
|
|
|
- else:
|
|
|
|
- self.engine = get_db_engine(config['productDB'])
|
|
|
|
- self.db_config = config['productDB']
|
|
|
|
-
|
|
|
|
- # 有些表需要连接生产数据库,不管当前是开发环境还是测试环境
|
|
|
|
- self.product_engine = get_db_engine(config['productDB'])
|
|
|
|
-
|
|
|
|
def get_advertiser_strategy_info(self):
|
|
def get_advertiser_strategy_info(self):
|
|
"""
|
|
"""
|
|
- 获取指定账户下客户的策略信息(不管是开发环境还是生产环境,该表都读取生产数据库)
|
|
|
|
- 为后续的写入库表,校验一致性做准备
|
|
|
|
|
|
+ 获取指定账户下客户的策略信息(不管是开发环境还是生产环境,该表都读取生产数据库) 为后续的写入库表,校验一致性做准备
|
|
"""
|
|
"""
|
|
- sql = """
|
|
|
|
- select *
|
|
|
|
- from ctop_ai_kuaishou_advertiser_strategy
|
|
|
|
- where account_id = %d
|
|
|
|
- limit 1
|
|
|
|
- """ % self.account_id
|
|
|
|
- advertiser_strategy_df = pd.read_sql(sql, self.product_engine)
|
|
|
|
|
|
+ sql = f"select * from ctop_ai_kuaishou_advertiser_strategy where account_id = {self.account_id} and status = 1 limit 1"
|
|
|
|
+ advertiser_strategy_df = pd.read_sql(sql, jeecg_product_db)
|
|
self.advertiser_strategy_id = int(advertiser_strategy_df['id'].values[0])
|
|
self.advertiser_strategy_id = int(advertiser_strategy_df['id'].values[0])
|
|
self.advertiser_strategy = advertiser_strategy_df.T.to_dict()[0]
|
|
self.advertiser_strategy = advertiser_strategy_df.T.to_dict()[0]
|
|
|
|
|
|
def add_campaign(self):
|
|
def add_campaign(self):
|
|
"""
|
|
"""
|
|
- 新增广告计划,如果存在则跳过。
|
|
|
|
|
|
+ 广告计划,如果存在则获取campaign_id, 否则新建广告计划。
|
|
response_data = {
|
|
response_data = {
|
|
"code": 0,
|
|
"code": 0,
|
|
"data": {
|
|
"data": {
|
|
@@ -146,106 +90,90 @@ class GetTargetAndAssemblyParameters(object):
|
|
},
|
|
},
|
|
"message": "SUCCESS"}
|
|
"message": "SUCCESS"}
|
|
"""
|
|
"""
|
|
- # 1、请求创建广告计划的接口
|
|
|
|
- campaign_name = self.advertiser_strategy['campaign_name']
|
|
|
|
- campaign_name = campaign_name.replace('{{自定义}}', '')
|
|
|
|
- campaign_name = campaign_name.replace('{{日期}}', '')
|
|
|
|
- campaign_name += '_优质定向_'
|
|
|
|
- campaign_name += str(datetime.datetime.now().date())
|
|
|
|
- create_campaign_request_data = {'account_id': self.account_id,
|
|
|
|
- 'campaign_name': campaign_name,
|
|
|
|
- 'type': self.advertiser_strategy['campaign_type'],
|
|
|
|
- 'day_budget': self.advertiser_strategy['campaign_day_budget'],
|
|
|
|
- 'day_budget_schedule': eval(self.advertiser_strategy['campaign_day_budget_schedule'])
|
|
|
|
- if self.advertiser_strategy['campaign_day_budget_schedule'] else None}
|
|
|
|
- request = requests.post(url=create_campaign_url,
|
|
|
|
- headers=headers,
|
|
|
|
- data=json.dumps(create_campaign_request_data, cls=NpEncoder))
|
|
|
|
- response_data = json.loads(request.text)
|
|
|
|
-
|
|
|
|
- # 2、对接口返回的结果处理
|
|
|
|
- campaign_info_to_db = {
|
|
|
|
- 'campaign_uuid': str(uuid.uuid4()),
|
|
|
|
- 'account_id': self.account_id,
|
|
|
|
- 'ai_strategy_uuid': self.ai_strategy_uuid,
|
|
|
|
- 'campaign_name': campaign_name,
|
|
|
|
- 'campaign_type': self.advertiser_strategy['campaign_type'],
|
|
|
|
- 'day_budget': self.advertiser_strategy['campaign_day_budget'],
|
|
|
|
- 'day_budget_schedule': self.advertiser_strategy['campaign_day_budget_schedule'],
|
|
|
|
- 'create_time': datetime.datetime.now()
|
|
|
|
- }
|
|
|
|
- res = 0
|
|
|
|
- if response_data['code'] == 0:
|
|
|
|
- self.campaign_id = response_data['data'].get('campaign_id', None)
|
|
|
|
- campaign_info_to_db['campaign_id'] = self.campaign_id
|
|
|
|
- campaign_info_to_db['campaign_create_time'] = response_data['data'].get('campaign_create_time', None)
|
|
|
|
- campaign_info_to_db['status'] = response_data.get('code', None)
|
|
|
|
- campaign_info_to_db['message'] = response_data.get('message', None)
|
|
|
|
-
|
|
|
|
- # self.advertiser_strategy['campaign_status'] 如果为暂停,需要创建成功后,调用修改广告计划状态的接口
|
|
|
|
- # `campaign_status` int(11) DEFAULT '1' COMMENT '计划状态: 1:投放 2:暂停 '
|
|
|
|
- if self.advertiser_strategy['campaign_status'] == 2:
|
|
|
|
- update_campaign_status_request = requests.post(url=update_campaign_status_url,
|
|
|
|
- headers=headers,
|
|
|
|
- data=json.dumps({'accountId': self.account_id,
|
|
|
|
- 'putStatus': 2,
|
|
|
|
- 'userId': '113dee46c7df464da78c07a985e92cd1',
|
|
|
|
- 'campaignIds': [self.campaign_id]},
|
|
|
|
- cls=NpEncoder))
|
|
|
|
- update_campaign_status_response_data = json.loads(update_campaign_status_request.text)
|
|
|
|
- if update_campaign_status_response_data.get('result') and \
|
|
|
|
- update_campaign_status_response_data.get('result').get('failCount') == 0:
|
|
|
|
- logger.info("账户 %s 下的广告计划 %s 暂停成功!" % (self.account_id, self.campaign_id))
|
|
|
|
- else:
|
|
|
|
- logger.error("账户 %s 下的广告计划 %s 暂停失败,异常信息: %s!" %
|
|
|
|
- (self.account_id,
|
|
|
|
- self.campaign_id,
|
|
|
|
- str(update_campaign_status_response_data.get('result'))))
|
|
|
|
- raise Exception("账户 %s 下的广告计划 %s 暂停失败!" % (self.account_id, self.campaign_id))
|
|
|
|
|
|
+ # 判断当天是否已经存在包含【优质定向】的广告计划,如果有则获取该计划id,否则需要新建计划
|
|
|
|
+ sql = f"select campaign_id from ctop_kuaishou_campaign where account_id = {self.account_id} " \
|
|
|
|
+ f"and put_create_time >= curdate() and campaign_name like '%%优质定向V0.2%%'"
|
|
|
|
+ campaign_df = pd.read_sql(sql, jeecg_db)
|
|
|
|
+ if not campaign_df.empty:
|
|
|
|
+ self.campaign_id = campaign_df['campaign_id'].values[0]
|
|
else:
|
|
else:
|
|
- campaign_info_to_db['message'] = response_data['message']
|
|
|
|
- campaign_info_to_db['status'] = response_data['code']
|
|
|
|
- res = -1
|
|
|
|
- raise Exception("账户 %s,新建广告计划失败,详细:%s!" % (self.account_id, str(response_data)))
|
|
|
|
-
|
|
|
|
- # 3、写入计划层级的操作表
|
|
|
|
- df = pd.DataFrame.from_dict(campaign_info_to_db, orient='index').T
|
|
|
|
- df.to_sql(name="ctop_ai_kuaishou_campaign_level_operation_record",
|
|
|
|
- con=self.engine,
|
|
|
|
- if_exists='append',
|
|
|
|
- index=False)
|
|
|
|
- return res
|
|
|
|
|
|
+ # 请求创建广告计划的接口
|
|
|
|
+ campaign_name = self.advertiser_strategy['campaign_name']
|
|
|
|
+ campaign_name = campaign_name.replace('{{自定义}}', '')
|
|
|
|
+ campaign_name = campaign_name.replace('{{日期}}', '')
|
|
|
|
+ campaign_name += '_优质定向V0.2_'
|
|
|
|
+ campaign_name += str(datetime.datetime.now().date())
|
|
|
|
+ create_campaign_request_data = {'account_id': self.account_id,
|
|
|
|
+ 'campaign_name': campaign_name,
|
|
|
|
+ 'type': self.advertiser_strategy['campaign_type'],
|
|
|
|
+ 'day_budget': self.advertiser_strategy['campaign_day_budget'],
|
|
|
|
+ 'day_budget_schedule': eval(self.advertiser_strategy['campaign_day_budget_schedule'])
|
|
|
|
+ if self.advertiser_strategy['campaign_day_budget_schedule'] else None}
|
|
|
|
+ request = requests.post(url=create_campaign_url,
|
|
|
|
+ headers=headers,
|
|
|
|
+ data=json.dumps(create_campaign_request_data, cls=NpEncoder))
|
|
|
|
+ response_data = json.loads(request.text)
|
|
|
|
+
|
|
|
|
+ # 2、对接口返回的结果处理
|
|
|
|
+ campaign_info_to_db = {
|
|
|
|
+ 'campaign_uuid': str(uuid.uuid4()),
|
|
|
|
+ 'account_id': self.account_id,
|
|
|
|
+ 'ai_strategy_uuid': self.ai_strategy_uuid,
|
|
|
|
+ 'campaign_name': campaign_name,
|
|
|
|
+ 'campaign_type': self.advertiser_strategy['campaign_type'],
|
|
|
|
+ 'day_budget': self.advertiser_strategy['campaign_day_budget'],
|
|
|
|
+ 'day_budget_schedule': self.advertiser_strategy['campaign_day_budget_schedule'],
|
|
|
|
+ 'create_time': datetime.datetime.now()
|
|
|
|
+ }
|
|
|
|
+ if response_data['code'] == 0:
|
|
|
|
+ self.campaign_id = response_data['data'].get('campaign_id', None)
|
|
|
|
+ campaign_info_to_db['campaign_id'] = self.campaign_id
|
|
|
|
+ campaign_info_to_db['campaign_create_time'] = response_data['data'].get('campaign_create_time', None)
|
|
|
|
+ campaign_info_to_db['status'] = response_data.get('code', None)
|
|
|
|
+ campaign_info_to_db['message'] = response_data.get('message', None)
|
|
|
|
+
|
|
|
|
+ # self.advertiser_strategy['campaign_status'] 如果为暂停,需要创建成功后,调用修改广告计划状态的接口
|
|
|
|
+ # `campaign_status` int(11) DEFAULT '1' COMMENT '计划状态: 1:投放 2:暂停 '
|
|
|
|
+ if self.advertiser_strategy['campaign_status'] == 2:
|
|
|
|
+ update_campaign_status_request = requests.post(url=update_campaign_status_url,
|
|
|
|
+ headers=headers,
|
|
|
|
+ data=json.dumps({'accountId': self.account_id,
|
|
|
|
+ 'putStatus': 2,
|
|
|
|
+ 'userId': '113dee46c7df464da78c07a985e92cd1',
|
|
|
|
+ 'campaignIds': [self.campaign_id]},
|
|
|
|
+ cls=NpEncoder))
|
|
|
|
+ update_campaign_status_response_data = json.loads(update_campaign_status_request.text)
|
|
|
|
+ if update_campaign_status_response_data.get('result') and \
|
|
|
|
+ update_campaign_status_response_data.get('result').get('failCount') == 0:
|
|
|
|
+ logger.info(f"账户:{self.account_id} 下的广告计划 {self.campaign_id} 暂停成功!")
|
|
|
|
+ else:
|
|
|
|
+ logger.error(f"账户:{self.account_id} 下的广告计划 {self.campaign_id} 暂停失败,"
|
|
|
|
+ f"异常信息: {str(update_campaign_status_response_data.get('result'))}")
|
|
|
|
+ raise Exception(f"账户:{self.account_id} 下的广告计划 {self.campaign_id} 暂停失败!")
|
|
|
|
+ else:
|
|
|
|
+ campaign_info_to_db['message'] = response_data['message']
|
|
|
|
+ campaign_info_to_db['status'] = response_data['code']
|
|
|
|
+ raise Exception(f"账户:{self.account_id}, 新建广告计划失败,详细:{str(response_data)}!")
|
|
|
|
+
|
|
|
|
+ # 3、写入计划层级的操作表
|
|
|
|
+ df = pd.DataFrame.from_dict(campaign_info_to_db, orient='index').T
|
|
|
|
+ df.to_sql(name="ctop_ai_kuaishou_campaign_level_operation_record",
|
|
|
|
+ con=jeecg_db,
|
|
|
|
+ if_exists='append',
|
|
|
|
+ index=False)
|
|
|
|
|
|
def get_signature_and_target(self):
|
|
def get_signature_and_target(self):
|
|
"""
|
|
"""
|
|
从 ctop_ai_kuaishou_signature_recommended_target_combine 表中读取素材和对应的定向
|
|
从 ctop_ai_kuaishou_signature_recommended_target_combine 表中读取素材和对应的定向
|
|
读取配置文件的条件,筛选出符合条件的定向组合
|
|
读取配置文件的条件,筛选出符合条件的定向组合
|
|
"""
|
|
"""
|
|
- sql = """
|
|
|
|
- select * from ctop_ai_kuaishou_signature_recommended_target_combine where project_id = %s
|
|
|
|
- and stat_date = (select max(stat_date) from ctop_ai_kuaishou_signature_recommended_target_combine)
|
|
|
|
- """ % self.project_id
|
|
|
|
- df = pd.read_sql(sql, self.engine)
|
|
|
|
-
|
|
|
|
- # 计算组合的概率,相对于实际投放概率高出了百分之多少,样本量是否达标(读取配置文件),且p值小于等于0.05
|
|
|
|
- df['improve_ratio'] = (df['combine_estimate_prob'] - df['actual_prob']) / df['actual_prob']
|
|
|
|
-
|
|
|
|
- # 两种类型的过滤标准不一样,分开进行判断,然后对结果进行合并
|
|
|
|
- df_1 = df[(df['target_type'] == 'action_ratio') &
|
|
|
|
- (df['improve_ratio'] >= config['filterTargetCombine']['actionRatio']['improveRatio']) &
|
|
|
|
- (df['sample_size'] >= config['filterTargetCombine']['actionRatio']['sampleSize']) &
|
|
|
|
- (df['p_value'] <= 0.05)]
|
|
|
|
-
|
|
|
|
- df_2 = df[(df['target_type'] == 'convert_ratio') &
|
|
|
|
- (df['improve_ratio'] >= config['filterTargetCombine']['convertRatio']['improveRatio']) &
|
|
|
|
- (df['sample_size'] >= config['filterTargetCombine']['convertRatio']['sampleSize']) &
|
|
|
|
- (df['p_value'] <= 0.05)]
|
|
|
|
-
|
|
|
|
- merge_df = pd.concat([df_1, df_2], axis=0)
|
|
|
|
|
|
+ sql = f"select * from ctop_ai_kuaishou_signature_recommended_target_combine_v2 where project_id = {self.project_id} and " \
|
|
|
|
+ f"stat_date = (select max(stat_date) from ctop_ai_kuaishou_signature_recommended_target_combine_v2)"
|
|
|
|
+ target_df = pd.read_sql(sql, jeecg_db)
|
|
|
|
|
|
- # 'age', 'gender', 'city', 'business', 'province', 'client'
|
|
|
|
|
|
+ # 'age', 'gender', 'city'
|
|
features = [key for key, value in config['bayesDim'].items() if value['isOn']]
|
|
features = [key for key, value in config['bayesDim'].items() if value['isOn']]
|
|
- self.target_combine_from_table = merge_df[['id', 'signature'] + features].to_dict(orient='records')
|
|
|
|
|
|
+ self.target_combine_from_table = target_df[['id', 'signature'] + features].to_dict(orient='records')
|
|
|
|
|
|
def write_intelligence_strategy_table(self):
|
|
def write_intelligence_strategy_table(self):
|
|
"""
|
|
"""
|
|
@@ -257,12 +185,12 @@ class GetTargetAndAssemblyParameters(object):
|
|
'advertiser_strategy_id': self.advertiser_strategy_id,
|
|
'advertiser_strategy_id': self.advertiser_strategy_id,
|
|
'account_id': self.account_id,
|
|
'account_id': self.account_id,
|
|
'ai_strategy_request_content': str(self.request_data),
|
|
'ai_strategy_request_content': str(self.request_data),
|
|
- 'ai_strategy_remark': '优质定向组合',
|
|
|
|
|
|
+ 'ai_strategy_remark': '优质定向组合V0.2',
|
|
'create_time': datetime.datetime.now()
|
|
'create_time': datetime.datetime.now()
|
|
}
|
|
}
|
|
df = pd.DataFrame.from_dict(intelligence_strategy_dict, orient='index').T
|
|
df = pd.DataFrame.from_dict(intelligence_strategy_dict, orient='index').T
|
|
df.to_sql(name="ctop_ai_kuaishou_intelligence_strategy",
|
|
df.to_sql(name="ctop_ai_kuaishou_intelligence_strategy",
|
|
- con=self.engine,
|
|
|
|
|
|
+ con=jeecg_db,
|
|
if_exists='append',
|
|
if_exists='append',
|
|
index=False)
|
|
index=False)
|
|
|
|
|
|
@@ -277,7 +205,6 @@ class GetTargetAndAssemblyParameters(object):
|
|
gender_bool = True
|
|
gender_bool = True
|
|
age_bool = True
|
|
age_bool = True
|
|
region_bool = True
|
|
region_bool = True
|
|
-
|
|
|
|
for item in self.target_combine_from_table:
|
|
for item in self.target_combine_from_table:
|
|
if 'gender' in item.keys():
|
|
if 'gender' in item.keys():
|
|
gender_bool, gender_dict = is_contains_gender(item['gender'], self.advertiser_strategy['gender'])
|
|
gender_bool, gender_dict = is_contains_gender(item['gender'], self.advertiser_strategy['gender'])
|
|
@@ -296,21 +223,24 @@ class GetTargetAndAssemblyParameters(object):
|
|
target_combine.update(region_dict) if 'city' in item.keys() else None
|
|
target_combine.update(region_dict) if 'city' in item.keys() else None
|
|
self.target_combine_filter_by_subset.append(target_combine)
|
|
self.target_combine_filter_by_subset.append(target_combine)
|
|
else:
|
|
else:
|
|
- logger.info("推荐定向:%s 与 账户 %s 配置信息里的定向(gender:%s, age_min: %s,age_max: %s, ages_ranges:%s,region:%s )存在冲突" %
|
|
|
|
- (item,
|
|
|
|
- self.account_id,
|
|
|
|
- self.advertiser_strategy['gender'],
|
|
|
|
- self.advertiser_strategy['age_min'],
|
|
|
|
- self.advertiser_strategy['age_max'],
|
|
|
|
- self.advertiser_strategy['ages_range'],
|
|
|
|
- self.advertiser_strategy['region']))
|
|
|
|
|
|
+ if not gender_bool:
|
|
|
|
+ logger.info(f"优质定向中的gender:{item.get('gender')}, "
|
|
|
|
+ f"与账户:{self.account_id} 配置信息里的gender: {self.advertiser_strategy['gender']} 存在冲突")
|
|
|
|
+ if not age_bool:
|
|
|
|
+ logger.info(f"优质定向中的age:{item.get('age')}, "
|
|
|
|
+ f"与账户:{self.account_id} 配置信息里的age: "
|
|
|
|
+ f"{self.advertiser_strategy['age_min'], self.advertiser_strategy['age_max'], self.advertiser_strategy['ages_range']}"
|
|
|
|
+ f"存在冲突")
|
|
|
|
+ if not region_bool:
|
|
|
|
+ logger.info(f"优质定向中的region:{item.get('city')}, "
|
|
|
|
+ f"与账户:{self.account_id} 配置信息里的region: {self.advertiser_strategy['region']} 存在冲突")
|
|
|
|
|
|
def filter_target_combine_by_creative_cnt(self):
|
|
def filter_target_combine_by_creative_cnt(self):
|
|
"""
|
|
"""
|
|
依据素材已经关联的创意个数,来定素材可以创建的定向组合个数, 并从中随机随着N个
|
|
依据素材已经关联的创意个数,来定素材可以创建的定向组合个数, 并从中随机随着N个
|
|
"""
|
|
"""
|
|
if not self.target_combine_filter_by_subset:
|
|
if not self.target_combine_filter_by_subset:
|
|
- return None
|
|
|
|
|
|
+ return
|
|
|
|
|
|
# 1-1 获取素材在该账户下关联的创意个数,过滤掉素材关联创意个数超过200的情况
|
|
# 1-1 获取素材在该账户下关联的创意个数,过滤掉素材关联创意个数超过200的情况
|
|
df = pd.DataFrame(self.target_combine_filter_by_subset)
|
|
df = pd.DataFrame(self.target_combine_filter_by_subset)
|
|
@@ -318,23 +248,13 @@ class GetTargetAndAssemblyParameters(object):
|
|
# 会导致后续拼装的参数发送给java服务报错(jackson.core.JsonParseException: Non-standard token \'NaN\':)
|
|
# 会导致后续拼装的参数发送给java服务报错(jackson.core.JsonParseException: Non-standard token \'NaN\':)
|
|
# 需要把 np.nan 替换为 None
|
|
# 需要把 np.nan 替换为 None
|
|
df.replace({np.nan: None}, inplace=True)
|
|
df.replace({np.nan: None}, inplace=True)
|
|
- # signature in ('03b93728f0d82ea7865c3c7cf632bc1b',),避免这样的sql报错
|
|
|
|
- if df.signature.nunique() == 1:
|
|
|
|
- sig_lst = tuple(list(df.signature.unique()) * 2)
|
|
|
|
- else:
|
|
|
|
- sig_lst = tuple(list(df.signature.unique()))
|
|
|
|
-
|
|
|
|
- sql = """
|
|
|
|
- select t1.signature, t2.creative_count
|
|
|
|
- from
|
|
|
|
- (select account_id, signature, photo_id from ctop_kuaishou_video_get
|
|
|
|
- where account_id = %s and signature in %s
|
|
|
|
- group by account_id, signature) t1
|
|
|
|
- left join
|
|
|
|
- ctop_kuaishou_video_relate_creatives t2
|
|
|
|
- on t1.account_id = t2.account_id and t1.photo_id = t2.photo_id
|
|
|
|
- """ % (self.account_id, sig_lst)
|
|
|
|
- creative_cnt_df = pd.read_sql(sql, self.product_engine)
|
|
|
|
|
|
+ # signature in ('03b93728f0d82ea7865c3c7cf632bc1b',), 避免这样的sql报错
|
|
|
|
+ sig_lst = tuple(list(df.signature.unique()) * 2) if df.signature.nunique() == 1 else tuple(list(df.signature.unique()))
|
|
|
|
+ sql = f"select t1.signature, t2.creative_count " \
|
|
|
|
+ f"from (select account_id, signature, photo_id from ctop_kuaishou_video_get where account_id = {self.account_id} " \
|
|
|
|
+ f"and signature in {sig_lst} ) t1 left join ctop_kuaishou_video_relate_creatives t2 " \
|
|
|
|
+ f"on t1.account_id = t2.account_id and t1.photo_id = t2.photo_id "
|
|
|
|
+ creative_cnt_df = pd.read_sql(sql, jeecg_product_db)
|
|
creative_cnt_df = creative_cnt_df[creative_cnt_df.creative_count < 200]
|
|
creative_cnt_df = creative_cnt_df[creative_cnt_df.creative_count < 200]
|
|
|
|
|
|
# 1-2 计算每个素材还能创建的广告组(定向)个数: (200 - 已关联创意个数) / 15
|
|
# 1-2 计算每个素材还能创建的广告组(定向)个数: (200 - 已关联创意个数) / 15
|
|
@@ -387,15 +307,14 @@ class GetTargetAndAssemblyParameters(object):
|
|
group_name = group_params['unit_name']
|
|
group_name = group_params['unit_name']
|
|
|
|
|
|
# 获取素材名称, 在广告组命名中进行替换 (同时获取photo_id, 用于拼装创意层级的参数)
|
|
# 获取素材名称, 在广告组命名中进行替换 (同时获取photo_id, 用于拼装创意层级的参数)
|
|
- sql = """select photo_id, photo_name, material_type
|
|
|
|
- from ctop_kuaishou_video_get where account_id = %s and signature = '%s' limit 1""" % \
|
|
|
|
- (self.account_id, item['signature'])
|
|
|
|
- signature_df = pd.read_sql(sql, self.product_engine)
|
|
|
|
|
|
+ sql = f"select photo_id, photo_name, material_type from ctop_kuaishou_video_get where account_id = {self.account_id} and " \
|
|
|
|
+ f"signature = '{item['signature']}' limit 1"
|
|
|
|
+ signature_df = pd.read_sql(sql, jeecg_product_db)
|
|
photo_name = signature_df['photo_name'].values[0] if not signature_df.empty else 'unknown'
|
|
photo_name = signature_df['photo_name'].values[0] if not signature_df.empty else 'unknown'
|
|
photo_id = signature_df['photo_id'].values[0] if not signature_df.empty else None
|
|
photo_id = signature_df['photo_id'].values[0] if not signature_df.empty else None
|
|
material_type = signature_df['material_type'].values[0] if not signature_df.empty else None
|
|
material_type = signature_df['material_type'].values[0] if not signature_df.empty else None
|
|
group_name.replace('{{素材名称}}', photo_name)
|
|
group_name.replace('{{素材名称}}', photo_name)
|
|
- group_name = group_name + '_优质定向' + '_' + str(group_cnt)
|
|
|
|
|
|
+ group_name = group_name + '_优质定向V0.2' + '_' + str(group_cnt)
|
|
|
|
|
|
# 写入数据表中组信息
|
|
# 写入数据表中组信息
|
|
single_group_params_to_db = group_params_to_db.copy()
|
|
single_group_params_to_db = group_params_to_db.copy()
|
|
@@ -441,14 +360,14 @@ class GetTargetAndAssemblyParameters(object):
|
|
# 创意层级信息批量写入数据库
|
|
# 创意层级信息批量写入数据库
|
|
creative_df = pd.DataFrame(batch_creative_params_to_db)
|
|
creative_df = pd.DataFrame(batch_creative_params_to_db)
|
|
creative_df.to_sql(name="ctop_ai_kuaishou_creative_level_operation_record",
|
|
creative_df.to_sql(name="ctop_ai_kuaishou_creative_level_operation_record",
|
|
- con=self.engine,
|
|
|
|
|
|
+ con=jeecg_product_db,
|
|
if_exists='append',
|
|
if_exists='append',
|
|
index=False)
|
|
index=False)
|
|
|
|
|
|
# 组层级信息批量写入数据库
|
|
# 组层级信息批量写入数据库
|
|
group_df = pd.DataFrame(batch_group_params_to_db)
|
|
group_df = pd.DataFrame(batch_group_params_to_db)
|
|
group_df.to_sql(name="ctop_ai_kuaishou_unit_level_operation_record",
|
|
group_df.to_sql(name="ctop_ai_kuaishou_unit_level_operation_record",
|
|
- con=self.engine,
|
|
|
|
|
|
+ con=jeecg_product_db,
|
|
if_exists='append',
|
|
if_exists='append',
|
|
index=False)
|
|
index=False)
|
|
|
|
|
|
@@ -538,8 +457,6 @@ class GetTargetAndAssemblyParameters(object):
|
|
'behavior_interest': self.advertiser_strategy.get('behavior_interest')
|
|
'behavior_interest': self.advertiser_strategy.get('behavior_interest')
|
|
}
|
|
}
|
|
|
|
|
|
- group_params['unit_name'] = group_params['unit_name'].replace('{{自定义}}', '')
|
|
|
|
-
|
|
|
|
# 2、smart_cover 和 asset_mining 需要将0/1 转化为 False/True
|
|
# 2、smart_cover 和 asset_mining 需要将0/1 转化为 False/True
|
|
group_params['smart_cover'] = True if group_params.get('smart_cover', 0) == 1 else False
|
|
group_params['smart_cover'] = True if group_params.get('smart_cover', 0) == 1 else False
|
|
group_params['asset_mining'] = True if group_params.get('asset_mining', 0) == 1 else False
|
|
group_params['asset_mining'] = True if group_params.get('asset_mining', 0) == 1 else False
|
|
@@ -549,7 +466,7 @@ class GetTargetAndAssemblyParameters(object):
|
|
# 3-1 获取应用标记和应用名称
|
|
# 3-1 获取应用标记和应用名称
|
|
sql = """select app_version, app_name from ctop_kuaishou_app_list where app_id = %s limit 1""" % \
|
|
sql = """select app_version, app_name from ctop_kuaishou_app_list where app_id = %s limit 1""" % \
|
|
group_params.get('app_id', -1)
|
|
group_params.get('app_id', -1)
|
|
- app_info_df = pd.read_sql(sql, self.engine)
|
|
|
|
|
|
+ app_info_df = pd.read_sql(sql, jeecg_product_db)
|
|
app_version = app_info_df['app_version'].values[0] if not app_info_df.empty else 'unknown'
|
|
app_version = app_info_df['app_version'].values[0] if not app_info_df.empty else 'unknown'
|
|
app_name = app_info_df['app_name'].values[0] if not app_info_df.empty else 'unknown'
|
|
app_name = app_info_df['app_name'].values[0] if not app_info_df.empty else 'unknown'
|
|
# 3-2 素材名称, 在拼装广告组和创意层级函数中获取
|
|
# 3-2 素材名称, 在拼装广告组和创意层级函数中获取
|
|
@@ -578,6 +495,7 @@ class GetTargetAndAssemblyParameters(object):
|
|
else:
|
|
else:
|
|
unit_name = unit_name + '_' + now_date
|
|
unit_name = unit_name + '_' + now_date
|
|
group_params['unit_name'] = unit_name
|
|
group_params['unit_name'] = unit_name
|
|
|
|
+ group_params['unit_name'] = group_params['unit_name'].replace('{{自定义}}', '')
|
|
|
|
|
|
# 4、处理 cpa_bid
|
|
# 4、处理 cpa_bid
|
|
# 因为存在 【单出价请输入单数字,阶梯出价请用/隔开】 如:1900/2000/2100 cpa_bid varchar(255) DEFAULT NULL COMMENT '出价'
|
|
# 因为存在 【单出价请输入单数字,阶梯出价请用/隔开】 如:1900/2000/2100 cpa_bid varchar(255) DEFAULT NULL COMMENT '出价'
|
|
@@ -638,7 +556,7 @@ class GetTargetAndAssemblyParameters(object):
|
|
random.shuffle(description_lst)
|
|
random.shuffle(description_lst)
|
|
creative_params['description'] = description_lst[0]
|
|
creative_params['description'] = description_lst[0]
|
|
|
|
|
|
- # 2 写入数据库的创意参数
|
|
|
|
|
|
+ # 2 写入数据库的创意参数(数据库表结构没有下面的字段,因此入库时去掉这些字段)
|
|
creative_params_to_db = creative_params.copy()
|
|
creative_params_to_db = creative_params.copy()
|
|
drop_cols = ['put_status', 'live_creative_type']
|
|
drop_cols = ['put_status', 'live_creative_type']
|
|
for col in drop_cols:
|
|
for col in drop_cols:
|
|
@@ -665,11 +583,11 @@ class GetTargetAndAssemblyParameters(object):
|
|
更新 intelligence_strategy_table
|
|
更新 intelligence_strategy_table
|
|
:return:
|
|
:return:
|
|
"""
|
|
"""
|
|
- db_con = pymysql.connect(host=self.db_config['host'],
|
|
|
|
- port=self.db_config['port'],
|
|
|
|
- user=self.db_config['username'],
|
|
|
|
- password=self.db_config['password'],
|
|
|
|
- database=self.db_config['database'],
|
|
|
|
|
|
+ db_con = pymysql.connect(host=jeecg_product_db['host'],
|
|
|
|
+ port=jeecg_product_db['port'],
|
|
|
|
+ user=jeecg_product_db['username'],
|
|
|
|
+ password=jeecg_product_db['password'],
|
|
|
|
+ database=jeecg_product_db['database'],
|
|
charset='utf8')
|
|
charset='utf8')
|
|
cursor = db_con.cursor()
|
|
cursor = db_con.cursor()
|
|
if message:
|
|
if message:
|