Quellcode durchsuchen

update ad threshold auto update

liqian vor 2 Jahren
Ursprung
Commit
c6494e3ebe
4 geänderte Dateien mit 246 neuen und 79 gelöschten Zeilen
  1. 89 12
      ad_threshold_auto_update.py
  2. 6 6
      ad_user_video_predict.py
  3. 127 61
      config.py
  4. 24 0
      utils.py

+ 89 - 12
ad_threshold_auto_update.py

@@ -5,7 +5,7 @@ from threading import Timer
 
 import pandas as pd
 
-from utils import RedisHelper, data_check, get_feature_data, send_msg_to_feishu
+from utils import RedisHelper, data_check, get_feature_data, send_msg_to_feishu, request_get
 from config import set_config
 from log import Log
 config_, _ = set_config()
@@ -14,10 +14,8 @@ redis_helper = RedisHelper()
 
 features = [
     'apptype',
-    'adcode',
-    'visit_uv_today',
-    'visit_uv_yesterday',
-    'b'
+    '分组',
+    '广告uv'
 ]
 
 
@@ -62,6 +60,53 @@ def get_threshold_record_new(ad_abtest_abcode_config, feature_df, threshold_reco
     return threshold_record_new, robot_msg_record
 
 
+def get_threshold_record_new_by_uv(ad_abtest_abcode_config, feature_df, threshold_record, ad_target_uv):
+    """根据广告uv计算新的阈值参数"""
+    robot_msg_record = []
+    threshold_record_new = threshold_record.copy()
+    for app_type, target_uv_mapping in ad_target_uv.items():
+        update_threshold_params = ad_abtest_abcode_config.get(int(app_type))
+        ab_test_id = update_threshold_params.get('ab_test_id')
+        temp_df = feature_df[feature_df['apptype'] == int(app_type)]
+        for ab_test_group, target_uv in target_uv_mapping.items():
+            threshold_update = update_threshold_params.get('threshold_update')
+
+    for app_type, config_params in ad_abtest_abcode_config.items():
+        # 获取对应端的数据, 更新阈值参数
+        # log_.info(f"app_type = {app_type}")
+        temp_df = feature_df[feature_df['apptype'] == app_type]
+        ab_test_id = config_params.get('ab_test_id')
+        threshold_update = config_params.get('threshold_update')
+
+        for config_name, ab_code_list in ab_test_config.items():
+            ad_abtest_tag = f"{ab_test_id}-{config_name}"
+            # log_.info(f"ad_abtest_tag = {ad_abtest_tag}")
+            if len(ab_code_list) > 0:
+                b_mean = temp_df[temp_df['adcode'].isin(ab_code_list)]['b'].mean()
+                if b_mean < 0:
+                    # 阈值按梯度调高
+                    gradient = up_threshold_update[config_name].get('gradient')
+                    update_range = up_threshold_update[config_name].get('update_range')
+                    b_i = (b_mean * -1) // gradient + 1
+                    threshold_param_new = float(threshold_record.get(ad_abtest_tag)) + update_range * b_i
+                elif b_mean > 0.1:
+                    # 阈值按梯度调低
+                    gradient = down_threshold_update[config_name].get('gradient')
+                    update_range = down_threshold_update[config_name].get('update_range')
+                    b_i = (b_mean - 0.1) // gradient + 1
+                    threshold_param_new = float(threshold_record.get(ad_abtest_tag)) - update_range * b_i
+                else:
+                    continue
+                if threshold_param_new > 0:
+                    threshold_record_new[ad_abtest_tag] = threshold_param_new
+                    robot_msg_record.append({'appType': app_type, 'abtestTag': ad_abtest_tag,
+                                             'gradient': round(gradient, 4), 'range': round(update_range, 4),
+                                             'i': int(b_i),
+                                             'paramOld': round(float(threshold_record.get(ad_abtest_tag)), 4),
+                                             'paramNew': round(threshold_param_new, 4)})
+    return threshold_record_new, robot_msg_record
+
+
 def update_threshold(threshold_record_old, threshold_record_new):
     """更新阈值"""
     ad_mid_group_list = [group for class_key, group_list in config_.AD_MID_GROUP.items()
@@ -87,7 +132,7 @@ def update_threshold(threshold_record_old, threshold_record_new):
             redis_helper.set_data_to_redis(key_name=key_name, value=threshold_new)
 
 
-def update_ad_abtest_threshold(project, table, dt, ad_abtest_abcode_config):
+def update_ad_abtest_threshold(project, table, dt, ad_abtest_abcode_config, ad_target_uv):
     # 获取当前阈值参数值
     threshold_record = redis_helper.get_data_from_redis(key_name=config_.KEY_NAME_PREFIX_AD_THRESHOLD_RECORD)
     threshold_record = eval(threshold_record)
@@ -95,10 +140,11 @@ def update_ad_abtest_threshold(project, table, dt, ad_abtest_abcode_config):
     # 获取uv数据
     feature_df = get_feature_data(project=project, table=table, features=features, dt=dt)
     feature_df['apptype'] = feature_df['apptype'].astype(int)
-    feature_df['b'] = feature_df['b'].astype(float)
-    # 根据活跃人数变化计算新的阈值参数
-    threshold_record_new, robot_msg_record = get_threshold_record_new(
-        ad_abtest_abcode_config=ad_abtest_abcode_config, feature_df=feature_df, threshold_record=threshold_record)
+    feature_df['广告uv'] = feature_df['广告uv'].astype(float)
+    # 根据广告uv变化计算新的阈值参数
+    threshold_record_new, robot_msg_record = get_threshold_record_new_by_uv(
+        ad_abtest_abcode_config=ad_abtest_abcode_config, feature_df=feature_df,
+        threshold_record=threshold_record, ad_target_uv=ad_target_uv)
     log_.info(f"threshold_record_new = {threshold_record_new}")
     # 更新阈值
     update_threshold(threshold_record_old=threshold_record, threshold_record_new=threshold_record_new)
@@ -108,23 +154,54 @@ def update_ad_abtest_threshold(project, table, dt, ad_abtest_abcode_config):
     return robot_msg_record
 
 
+def get_ad_target_uv():
+    """获取管理后台开启自动调整阈值开关的目标uv值"""
+    ad_target_uv = {}
+    result = request_get(request_url=config_.GET_AD_TARGET_UV_URL)
+    if result is None:
+        log_.info('获取管理后台广告目标uv值失败!')
+        return ad_target_uv
+    if result['code'] != 0:
+        log_.info('获取管理后台广告目标uv值失败!')
+        return ad_target_uv
+    if not result['content']:
+        return ad_target_uv
+    for item in result['content']:
+        app_type = item['productId']
+        target_uv_mapping = {}
+        for uv_item in item['uvTargetDetails']:
+            ab_group = uv_item['abParam']
+            target_uv = uv_item['uvTarget']
+            target_uv_mapping[ab_group] = target_uv
+        ad_target_uv[app_type] = target_uv_mapping
+    return ad_target_uv
+
+
 def timer_check():
     try:
+        # 获取自动调整阈值参数
         ad_abtest_abcode_config = config_.AD_ABTEST_ABCODE_CONFIG
         project = config_.AD_THRESHOLD_AUTO_UPDATE_DATA.get('project')
         table = config_.AD_THRESHOLD_AUTO_UPDATE_DATA.get('table')
         now_date = datetime.datetime.today()
         now_min = datetime.datetime.now().minute
         log_.info(f"now_date: {datetime.datetime.strftime(now_date, '%Y%m%d%H')}")
-        dt = datetime.datetime.strftime(now_date - datetime.timedelta(hours=1), '%Y%m%d%H')
+
+        # 管理后台获取开启自动调整阈值开关的目标uv值
+        ad_target_uv = get_ad_target_uv()
+        log_.info(f"ad_target_uv: {ad_target_uv}")
+        if len(ad_target_uv) == 0:
+            return
 
         # 查看当前更新的数据是否已准备好
+        dt = datetime.datetime.strftime(now_date - datetime.timedelta(hours=1), '%Y%m%d%H')
         data_count = data_check(project=project, table=table, dt=dt)
         if data_count > 0:
             log_.info(f"data count = {data_count}")
             # 数据准备好,进行更新
             robot_msg_record = update_ad_abtest_threshold(
-                project=project, table=table, dt=dt, ad_abtest_abcode_config=ad_abtest_abcode_config)
+                project=project, table=table, dt=dt,
+                ad_abtest_abcode_config=ad_abtest_abcode_config, ad_target_uv=ad_target_uv)
             if len(robot_msg_record) > 0:
                 robot_msg_record_text = "\n".join([str(item) for item in robot_msg_record])
                 msg = f"threshold_param_update: \n{robot_msg_record_text.replace(', ', ',   ')}\n"

+ 6 - 6
ad_user_video_predict.py

@@ -133,15 +133,15 @@ def predict():
     dt = datetime.datetime.strftime(now_date, '%Y%m%d')
     log_.info(f"dt = {dt}")
     params = config_.AD_ABTEST_CONFIG
-    threshold_record = {}
     for config_key, config_param in params.items():
         predict_ad_group_video(dt=dt, config_key=config_key, config_param=config_param)
-        # 阈值参数记录
-        ad_threshold_mappings = config_.AD_ABTEST_THRESHOLD_CONFIG.get(config_key.split('-')[0])
-        for abtest_group, ad_threshold_mapping in ad_threshold_mappings.items():
-            threshold_record[f"{config_key}-{abtest_group}"] = ad_threshold_mapping['group']
+    # 阈值参数记录
+    # threshold_record = {}
+    # for abtest_id, ad_threshold_mapping in config_.AD_ABTEST_THRESHOLD_CONFIG.items():
+    #     for abtest_group, threshold_param in ad_threshold_mapping.items():
+    #         threshold_record[f"{abtest_id}-{abtest_group}"] = threshold_param['group']
     redis_helper.set_data_to_redis(key_name=config_.KEY_NAME_PREFIX_AD_THRESHOLD_RECORD,
-                                   value=str(threshold_record),
+                                   value=str(config_.AD_ABTEST_THRESHOLD_CONFIG),
                                    expire_time=24*3600)
 
 

+ 127 - 61
config.py

@@ -719,7 +719,7 @@ class BaseConfig(object):
     # 自动调整广告模型阈值数据
     AD_THRESHOLD_AUTO_UPDATE_DATA = {
         'project': 'loghubods',
-        'table': 'visit_uv_hh'
+        'table': 'ad_view_monitor_hh_report_final'
     }
 
     # 调用广告模型appType列表
@@ -1014,62 +1014,120 @@ class BaseConfig(object):
         },
     }
 
-    # 广告模型abtest分组配置
+    # 广告模型自动调整阈值配置
     AD_ABTEST_ABCODE_CONFIG = {
         # 票圈vlog
-        # APP_TYPE['VLOG']: {
-        #     'ab_test_id': 173,
-        #     'ab_test_config': {"a": ["ab1"], "b": ["ab3", "ab4", "ab6", "ab7", "ab8"], "c": [], "d": ["ab2"]},
-        #     'threshold_update': 1/24,
-        # },
+        APP_TYPE['VLOG']: {
+            'ab_test_id': 173,
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
+            },
+        },
         # 票圈视频+
-        # APP_TYPE['PIAO_QUAN_VIDEO_PLUS']: {
-        #     'ab_test_id': 190,
-        #     'ab_test_config': {"a": ["ab1"], "b": ["ab6", "ab7", "ab2", "ab3"]},
-        #     'threshold_update': 1 / 24,
-        # },
+        APP_TYPE['PIAO_QUAN_VIDEO_PLUS']: {
+            'ab_test_id': 190,
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
+            },
+        },
         # 票圈视频
         APP_TYPE['LOVE_LIVE']: {
             'ab_test_id': 194,
-            'ab_test_config': {"a": ["ab6", "ab7", "ab8"], "b": ["ab3", "ab4"], "c": ["ab2"], "d": ["ab1"]},
-            'up_threshold_update': {
-                'a': {'gradient': 0.05, 'update_range': 1 / 24},
-                'b': {'gradient': 0.05, 'update_range': 1 / 24},
-                'c': {'gradient': 0.1, 'update_range': 1 / 192},
-                'd': {'gradient': 0.1, 'update_range': 1 / 192},
-            },
-            'down_threshold_update': {
-                'a': {'gradient': 0.1, 'update_range': 1 / 12},
-                'b': {'gradient': 0.1, 'update_range': 1 / 12},
-                'c': {'gradient': 0.1, 'update_range': 1 / 12},
-                'd': {'gradient': 0.1, 'update_range': 1 / 12},
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
             },
         },
         # 内容精选
-        # APP_TYPE['LONG_VIDEO']: {
-        #     'ab_test_id': 195,
-        #     'ab_test_config': {"b": [], "c": ["ab1", "ab2"]},
-        #     'threshold_update': 1 / 24,
-        # },
+        APP_TYPE['LONG_VIDEO']: {
+            'ab_test_id': 195,
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
+            },
+        },
         # 票圈短视频
-        # APP_TYPE['SHORT_VIDEO']: {
-        #     'ab_test_id': 196,
-        #     'ab_test_config': {"a": [], "b": [], "c": ["ab2", "ab3", "ab1", "ab9"]},
-        #     'threshold_update': 1 / 24,
-        # },
+        APP_TYPE['SHORT_VIDEO']: {
+            'ab_test_id': 196,
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
+            },
+        },
         # 老好看视频
-        # APP_TYPE['LAO_HAO_KAN_VIDEO']: {
-        #     'ab_test_id': 197,
-        #     'ab_test_config': {"a": ["ab0", "ab100", "ab6", "ab7", "ab8", "ab9"],
-        #                        "b": ["ab1", "ab2", "ab3",  "ab4", "ab5"]},
-        #     'threshold_update': 1 / 24,
-        # },
+        APP_TYPE['LAO_HAO_KAN_VIDEO']: {
+            'ab_test_id': 197,
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
+            },
+        },
         # 票圈最惊奇
-        # APP_TYPE['ZUI_JING_QI']: {
-        #     'ab_test_id': 198,
-        #     'ab_test_config': {"a": ["ab3", "ab6", "ab7"], "b": ["ab8", "ab9"]},
-        #     'threshold_update': 1 / 24,
-        # },
+        APP_TYPE['ZUI_JING_QI']: {
+            'ab_test_id': 198,
+            'threshold_update': {
+                'ab0': 1 / 48,
+                'ab1': 1 / 48,
+                'ab2': 1 / 48,
+                'ab3': 1 / 48,
+                'ab4': 1 / 48,
+                'ab5': 1 / 48,
+                'ab6': 1 / 48,
+                'ab7': 1 / 48,
+                'ab8': 1 / 48,
+                'ab9': 1 / 48,
+            },
+        },
     }
 
     # 用户组有广告时的分享率预测结果存放 redis key 前缀,完整格式:ad:users:group:predict:share:rate:{user_data_key}:{user_rule_key}:{date}
@@ -1159,11 +1217,13 @@ class DevelopmentConfig(BaseConfig):
     NOTIFY_BACKEND_updateFallBackVideoList_URL = 'http://videotest-internal.yishihui.com/longvideoapi/openapi/recommend/updateFallBackVideoList'
     # 获取限流视频接口地址
     GET_VIDEO_LIMIT_LIST_URL = 'http://videotest-internal.yishihui.com/longvideoapi/openapi/recommend/getVideoLimitList'
+    # 获取管理后台设置的广告目标uv值接口地址
+    GET_AD_TARGET_UV_URL = 'https://testadmin.piaoquantv.com/manager/ad/algo/threshold/productUvTargetList'
 
-    # logs 上传oss 目标Bucket指定目录
-    OSS_FOLDER_LOGS = 'rov-offline/dev/logs/'
-    # data 上传oss 目标Bucket指定目录
-    OSS_FOLDER_DATA = 'rov-offline/dev/data/'
+    # # logs 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_LOGS = 'rov-offline/dev/logs/'
+    # # data 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_DATA = 'rov-offline/dev/data/'
 
 
 class TestConfig(BaseConfig):
@@ -1239,11 +1299,13 @@ class TestConfig(BaseConfig):
     NOTIFY_BACKEND_updateFallBackVideoList_URL = 'http://videotest-internal.yishihui.com/longvideoapi/openapi/recommend/updateFallBackVideoList'
     # 获取限流视频接口地址
     GET_VIDEO_LIMIT_LIST_URL = 'http://videotest-internal.yishihui.com/longvideoapi/openapi/recommend/getVideoLimitList'
+    # 获取管理后台设置的广告目标uv值接口地址
+    GET_AD_TARGET_UV_URL = 'https://testadmin.piaoquantv.com/manager/ad/algo/threshold/productUvTargetList'
 
-    # logs 上传oss 目标Bucket指定目录
-    OSS_FOLDER_LOGS = 'rov-offline/test/logs/'
-    # data 上传oss 目标Bucket指定目录
-    OSS_FOLDER_DATA = 'rov-offline/test/data/'
+    # # logs 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_LOGS = 'rov-offline/test/logs/'
+    # # data 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_DATA = 'rov-offline/test/data/'
 
 
 class PreProductionConfig(BaseConfig):
@@ -1319,11 +1381,13 @@ class PreProductionConfig(BaseConfig):
     NOTIFY_BACKEND_updateFallBackVideoList_URL = 'http://videopre-internal.piaoquantv.com/longvideoapi/openapi/recommend/updateFallBackVideoList'
     # 获取限流视频接口地址
     GET_VIDEO_LIMIT_LIST_URL = 'http://prespeed-internal.piaoquantv.com/longvideoapi/openapi/recommend/getVideoLimitList'
+    # 获取管理后台设置的广告目标uv值接口地址
+    GET_AD_TARGET_UV_URL = 'https://preadmin.piaoquantv.com/manager/ad/algo/threshold/productUvTargetList'
 
-    # logs 上传oss 目标Bucket指定目录
-    OSS_FOLDER_LOGS = 'rov-offline/pre/logs/'
-    # data 上传oss 目标Bucket指定目录
-    OSS_FOLDER_DATA = 'rov-offline/pre/data/'
+    # # logs 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_LOGS = 'rov-offline/pre/logs/'
+    # # data 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_DATA = 'rov-offline/pre/data/'
 
 
 class ProductionConfig(BaseConfig):
@@ -1399,11 +1463,13 @@ class ProductionConfig(BaseConfig):
     NOTIFY_BACKEND_updateFallBackVideoList_URL = 'http://recommend-common-internal.piaoquantv.com/longvideoapi/openapi/recommend/updateFallBackVideoList'
     # 获取限流视频接口地址
     GET_VIDEO_LIMIT_LIST_URL = 'http://recommend-common-internal.piaoquantv.com/longvideoapi/openapi/recommend/getVideoLimitList'
+    # 获取管理后台设置的广告目标uv值接口地址
+    GET_AD_TARGET_UV_URL = 'https://admin.piaoquantv.com/manager/ad/algo/threshold/productUvTargetList'
 
-    # logs 上传oss 目标Bucket指定目录
-    OSS_FOLDER_LOGS = 'rov-offline/pro/logs/'
-    # data 上传oss 目标Bucket指定目录
-    OSS_FOLDER_DATA = 'rov-offline/pro/data/'
+    # # logs 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_LOGS = 'rov-offline/pro/logs/'
+    # # data 上传oss 目标Bucket指定目录
+    # OSS_FOLDER_DATA = 'rov-offline/pro/data/'
 
 
 def set_config():

+ 24 - 0
utils.py

@@ -153,6 +153,30 @@ def request_post(request_url, request_data=None, **kwargs):
         return None
 
 
+def request_get(request_url):
+    """
+    get 请求 HTTP接口
+    :param request_url: 接口URL
+    :return: res_data json格式
+    """
+    try:
+        response = requests.get(url=request_url)
+        if response.status_code == 200:
+            res_data = json.loads(response.text)
+            return res_data
+        else:
+            log_.info(f"response.status_code: {response.status_code}")
+            return None
+    except Exception as e:
+        log_.error('url: {}, exception: {}, traceback: {}'.format(request_url, e, traceback.format_exc()))
+        send_msg_to_feishu(
+            webhook=config_.FEISHU_ROBOT['server_robot'].get('webhook'),
+            key_word=config_.FEISHU_ROBOT['server_robot'].get('key_word'),
+            msg_text='rov-offline{} - 接口请求失败:{}, exception: {}'.format(config_.ENV_TEXT, request_url, e)
+        )
+        return None
+
+
 def data_normalization(data):
     """
     对结果做归一化处理(Min-Max Normalization),将分数控制在[0, 100]