Browse Source

Merge branch 'test' of https://git.yishihui.com/algorithm/rov-offline into test

caida 1 year ago
parent
commit
6f3c53ea61
8 changed files with 407 additions and 8 deletions
  1. 1 1
      cal_24h_score.py
  2. 4 1
      cal_hour_score.py
  3. 2 1
      compose_score.py
  4. 34 1
      config.py
  5. 63 4
      region_rule_rank_h.py
  6. 2 0
      region_rule_rank_h_task.sh
  7. 300 0
      rule_rank_h_new.py
  8. 1 0
      videos_filter.py

+ 1 - 1
cal_24h_score.py

@@ -74,7 +74,7 @@ def cal_score(data_df):
     df['share_rate_view'] = (df['share人数'] + 1) / (df['view人数'] + 1000)
 
     # back_rate = (return+1)/(share+10)
-    df['back_rate'] = (df['回流人数'] + 1) / (df['view人数'] + 10)
+    df['back_rate'] = (df['回流人数'] + 1) / (df['share人数'] + 10)
 
     df['log_back'] = (df['回流人数'] + 1).apply(math.log)
 

+ 4 - 1
cal_hour_score.py

@@ -116,7 +116,10 @@ def cal_score(data_df):
     # score5 = share/view * (back_rate + back_rate_2h + back_rate_3h) * logback
     df['hour_score5'] = df['share_rate_view'] * (df['back_rate'] + df['back_rate_2h'] + df['back_rate_3h']) * df['log_back']
 
-    score_df = df[['videoid', 'hour_score1', 'hour_score2', 'hour_score3', 'hour_score4', 'hour_score5']]
+    # score6 = 回流/(view+5)*back_rate
+    df['hour_score6'] = df['lastonehour_return'] / (df['lastonehour_view'] + 5) * df['back_rate']
+
+    score_df = df[['videoid', 'hour_score1', 'hour_score2', 'hour_score3', 'hour_score4', 'hour_score5', 'hour_score6']]
     # print(score_df)
     return score_df
 

+ 2 - 1
compose_score.py

@@ -33,11 +33,12 @@ def cal_compose_score(score_hour_path, score_24h_path, merge_score_path):
     # score_merge_df['score5'] = score_merge_df['24h_score1'] + score_merge_df['hour_score5']
     score_merge_df['score6'] = score_merge_df['24h_score1'] * 0.2 + score_merge_df['hour_score4'] * 0.8
     score_merge_df['score7'] = score_merge_df['24h_score2'] + score_merge_df['hour_score4']
+    score_merge_df['score8'] = score_merge_df['24h_score1'] + score_merge_df['hour_score6']
 
     # print(score_merge_df)
     log_.info(f"score_merge_df shape: {score_merge_df.shape}")
     score_merge_df.to_csv(merge_score_path, index=False)
-    score_df = score_merge_df[['videoid', 'score1', 'score4', 'score6', 'score7']]
+    score_df = score_merge_df[['videoid', 'score1', 'score4', 'score6', 'score7', 'score8']]
     log_.info(f"score_df shape: {score_merge_df.shape}")
     return score_df
 

+ 34 - 1
config.py

@@ -334,6 +334,24 @@ class BaseConfig(object):
         ]
     }
 
+    # 小时级更新过去1h数据 loghubods.video_each_hour_update_no_province_apptype(不区分地域)
+    PROJECT_H_APP_TYPE = 'loghubods'
+    TABLE_H_APP_TYPE = 'video_each_hour_update_no_province_apptype'
+    # 小时级规则参数
+    RULE_PARAMS_H_APP_TYPE = {
+        'rule_params': {
+            # score = sharerate * backrate * LOG(lastonehour_return + 1) * K2
+            # sharerate = lastonehour_share / (lastonehour_play + 1000)
+            # backrate = lastonehour_return / (lastonehour_share + 10)
+            # ctr = lastonehour_play / (lastonehour_show + 1000), 对ctr限最大值:K2 = 0.6 if ctr > 0.6 else ctr
+            'rule1': {'view_type': 'video-show', 'platform_return_rate': 0.001, 'merge_func': 2},
+        },
+        'data_params': DATA_PARAMS,
+        'params_list': [
+            {'data': 'data10', 'rule': 'rule1'},
+        ],
+    }
+
     # 地域分组小时级规则更新使用数据
     PROJECT_REGION_APP_TYPE = 'loghubods'
     TABLE_REGION_APP_TYPE = 'video_each_hour_update_province_apptype'
@@ -438,13 +456,19 @@ class BaseConfig(object):
             'rule27': {'view_type': 'video-show-region', 'platform_return_rate': 0.001,
                        'region_24h_rule_key': 'rule4', '24h_rule_key': 'rule4', 'merge_func': 2,
                        'score_func': 'back_rate_exponential_weighting1'},
-            # score = sharerate ^ 0.5 * backrate ^ 0.8 * LOG(lastonehour_return + 1) * K2 ^ 0.5
+            # score = sharerate ^ 0.5 * backrate ^ 2 * LOG(lastonehour_return + 1) * K2 ^ 0.5
             # sharerate = lastonehour_share / (lastonehour_play + 1000)
             # backrate = lastonehour_return / (lastonehour_share + 10)
             # ctr = lastonehour_play / (lastonehour_show + 1000), 对ctr限最大值:K2 = 0.6 if ctr > 0.6 else ctr
             'rule28': {'view_type': 'video-show-region', 'platform_return_rate': 0.001,
                        'region_24h_rule_key': 'rule4', '24h_rule_key': 'rule4', 'merge_func': 2,
                        'score_func': 'back_rate_exponential_weighting2'},
+            'rule29': {'view_type': 'video-show-region', 'platform_return_rate': 0.001,
+                       'region_24h_rule_key': 'rule4', '24h_rule_key': 'rule4', 'merge_func': 2,
+                       'score_func': 'back_rate_rank_weighting'},
+            # 增加不区分地域小时级列表
+            'rule30': {'view_type': 'video-show-region', 'platform_return_rate': 0.001,
+                       'region_24h_rule_key': 'rule4', '24h_rule_key': 'rule4', 'h_rule_key': 'rule1', 'merge_func': 2},
 
         },
         'data_params': DATA_PARAMS,
@@ -485,6 +509,8 @@ class BaseConfig(object):
             # {'data': 'data10', 'rule': 'rule26'},  # 501
             {'data': 'data10', 'rule': 'rule27'},  # 502
             {'data': 'data10', 'rule': 'rule28'},  # 503
+            {'data': 'data10', 'rule': 'rule29'},  # 509
+            {'data': 'data10', 'rule': 'rule30'},  # 510
         ],
         'params_list_new': [
             # {'data': 'data10', 'rule': 'rule19'},  # 316 票圈视频 + 召回在线去重
@@ -622,6 +648,13 @@ class BaseConfig(object):
     # 完整格式:recall:item:score:30day:{data_key}:{rule_key}:{date}
     RECALL_KEY_NAME_PREFIX_30DAY = 'recall:item:score:30day:'
 
+    # 小程序小时级更新上一小时数据结果存放 redis key前缀,
+    # 完整格式:recall:item:score:h:{data_key}:{rule_key}:{date}:{h}
+    RECALL_KEY_NAME_PREFIX_BY_H_H = 'recall:item:score:h:'
+    # 小程序小时级更新结果与小程序地域分组小时级更新结果去重后 存放 redis key前缀,
+    # 完整格式:recall:item:score:region:dup:h:{region}:{data_key}:{rule_key}:{date}:{h}
+    RECALL_KEY_NAME_PREFIX_DUP_H_H = 'recall:item:score:region:dup:h:'
+
     # 小程序地域分组小时级更新结果存放 redis key前缀,
     # 完整格式:recall:item:score:region:h:{region}:{data_key}:{rule_key}:{date}:{h}
     RECALL_KEY_NAME_PREFIX_REGION_BY_H = 'recall:item:score:region:h:'

+ 63 - 4
region_rule_rank_h.py

@@ -418,7 +418,7 @@ def cal_score_with_back_rate_exponential_weighting2(df, param):
     :param param: 规则参数
     :return:
     """
-    # score计算公式: score = sharerate ^ 0.5 * backrate ^ 0.8 * LOG(lastonehour_return + 1) * K2 ^ 0.5
+    # score计算公式: score = sharerate ^ 0.5 * backrate ^ 2 * LOG(lastonehour_return + 1) * K2 ^ 0.5
     # sharerate = lastonehour_share / (lastonehour_play + 1000)
     # backrate = lastonehour_return / (lastonehour_share + 10)
     # ctr = lastonehour_play / (lastonehour_show + 1000), 对ctr限最大值:K2 = 0.6 if ctr > 0.6 else ctr
@@ -442,6 +442,49 @@ def cal_score_with_back_rate_exponential_weighting2(df, param):
     df = df.sort_values(by=['score'], ascending=False)
     return df
 
+def cal_score_with_back_rate_by_rank_weighting(df, param):
+    """
+    add by sunmingze 20231123
+    计算score
+    :param df: 特征数据
+    :param param: 规则参数
+    :return:
+    """
+    # score计算公式: score =  1 / sharerate(rank)^0.5 + 5 / backrate(rank)^0.5 + 10 / LOG(lastonehour_return +1)(rank) ^0.5
+    #   +  1 / K2(rank)^0.5
+    # sharerate = lastonehour_share / (lastonehour_play + 1000)
+    # backrate = lastonehour_return / (lastonehour_share + 10)
+    # ctr = lastonehour_play / (lastonehour_show + 1000), 对ctr限最大值:K2 = 0.6 if ctr > 0.6 else ctr
+
+    df = df.fillna(0)
+    df['share_rate'] = df['lastonehour_share'] / (df['lastonehour_play'] + 1000)
+    df['back_rate'] = df['lastonehour_return'] / (df['lastonehour_share'] + 10)
+    df['log_back'] = (df['lastonehour_return'] + 1).apply(math.log)
+    if param.get('view_type', None) == 'video-show':
+        df['ctr'] = df['lastonehour_play'] / (df['lastonehour_show'] + 1000)
+    elif param.get('view_type', None) == 'video-show-region':
+        df['ctr'] = df['lastonehour_play'] / (df['lastonehour_show_region'] + 1000)
+    else:
+        df['ctr'] = df['lastonehour_play'] / (df['lastonehour_preview'] + 1000)
+    df['K2'] = df['ctr'].apply(lambda x: 0.6 if x > 0.6 else x)
+
+    df['platform_return_rate'] = df['platform_return'] / df['lastonehour_return']
+
+    # 分别的得到sharerate、backrate、K值、return人数的序关系
+    df['rank_by_sharerate'] = df['share_rate'].rank(ascending=0, method='dense')
+    df['rank_by_backrate'] = df['back_rate'].rank(ascending=0, method='dense')
+    df['rank_by_K2'] = df['K2'].rank(ascending=0, method='dense')
+    df['rank_by_logback'] = df['log_back'].rank(ascending=0, method='dense')
+
+    # 计算基于序的加法关系函数
+    df['score'] = 1/(df['rank_by_sharerate'] + 10) + 5/(df['rank_by_backrate'] + 10)
+    df['score'] = df['score'] + 5/(df['rank_by_logback'] + 10) + 1/(df['rank_by_K2'] + 10)
+
+    df = df.sort_values(by=['score'], ascending=False)
+    return df
+
+
+
 
 def cal_score(df, param):
     if param.get('return_data', None) == 'share_region_return':
@@ -464,6 +507,8 @@ def cal_score(df, param):
             df = cal_score_with_back_rate_exponential_weighting1(df=df, param=param)
         elif param.get('score_func', None) == 'back_rate_exponential_weighting2':
             df = cal_score_with_back_rate_exponential_weighting2(df=df, param=param)
+        elif param.get('score_func', None) == 'back_rate_rank_weighting':
+            df = cal_score_with_back_rate_by_rank_weighting(df=df, param=param)
         else:
             df = cal_score_initial(df=df, param=param)
     return df
@@ -621,12 +666,13 @@ def video_rank(df, now_date, now_h, rule_key, param, region, data_key, rule_rank
         # 清空线上过滤应用列表
         # redis_helper.del_keys(key_name=f"{config_.REGION_H_VIDEO_FILER}{region}.{app_type}.{data_key}.{rule_key}")
 
+    h_rule_key = param.get('h_rule_key', None)
     region_24h_rule_key = param.get('region_24h_rule_key', 'rule1')
     by_24h_rule_key = param.get('24h_rule_key', None)
     by_48h_rule_key = param.get('48h_rule_key', None)
     dup_remove = param.get('dup_remove', True)
     # 与其他召回视频池去重,存入对应的redis
-    dup_to_redis(h_video_ids=h_video_ids, now_date=now_date, now_h=now_h, rule_key=rule_key,
+    dup_to_redis(h_video_ids=h_video_ids, now_date=now_date, now_h=now_h, rule_key=rule_key, h_rule_key=h_rule_key,
                  region_24h_rule_key=region_24h_rule_key, by_24h_rule_key=by_24h_rule_key,
                  by_48h_rule_key=by_48h_rule_key, region=region, data_key=data_key,
                  rule_rank_h_flag=rule_rank_h_flag, political_filter=political_filter,
@@ -666,9 +712,21 @@ def dup_data(h_video_ids, initial_key_name, dup_key_name, region, political_filt
     return h_video_ids
 
 
-def dup_to_redis(h_video_ids, now_date, now_h, rule_key, region_24h_rule_key, by_24h_rule_key, by_48h_rule_key,
+def dup_to_redis(h_video_ids, now_date, now_h, rule_key, h_rule_key, region_24h_rule_key, by_24h_rule_key, by_48h_rule_key,
                  region, data_key, rule_rank_h_flag, political_filter, shield_config, dup_remove):
     """将地域分组小时级数据与其他召回视频池去重,存入对应的redis"""
+    # ##### 去重更新不区分地域小时级列表,并另存为redis中
+    if h_rule_key is not None:
+        h_key_name = \
+            f"{config_.RECALL_KEY_NAME_PREFIX_BY_H_H}{data_key}:{h_rule_key}:" \
+            f"{datetime.datetime.strftime(now_date, '%Y%m%d')}:{now_h}"
+        h_dup_key_name = \
+            f"{config_.RECALL_KEY_NAME_PREFIX_DUP_H_H}{region}:{data_key}:{rule_key}:" \
+            f"{datetime.datetime.strftime(now_date, '%Y%m%d')}:{now_h}"
+        h_video_ids = dup_data(h_video_ids=h_video_ids, initial_key_name=h_key_name,
+                               dup_key_name=h_dup_key_name, region=region, political_filter=political_filter,
+                               shield_config=shield_config, dup_remove=dup_remove)
+
     # ##### 去重更新地域分组小时级24h列表,并另存为redis中
     region_24h_key_name = \
         f"{config_.RECALL_KEY_NAME_PREFIX_REGION_BY_24H}{region}:{data_key}:{region_24h_rule_key}:" \
@@ -1034,6 +1092,7 @@ def h_bottom_process(param, rule_params_item, region_code_list, key_prefix, redi
     rule_key = param.get('rule')
     rule_param = rule_params_item.get(rule_key)
     log_.info(f"data_key = {data_key}, rule_key = {rule_key}, rule_param = {rule_param}")
+    h_rule_key = rule_param.get('h_rule_key', None)
     region_24h_rule_key = rule_param.get('region_24h_rule_key', 'rule1')
     by_24h_rule_key = rule_param.get('24h_rule_key', None)
     by_48h_rule_key = rule_param.get('48h_rule_key', None)
@@ -1059,7 +1118,7 @@ def h_bottom_process(param, rule_params_item, region_code_list, key_prefix, redi
         if len(final_data) > 0:
             redis_helper.add_data_with_zset(key_name=final_key_name, data=final_data, expire_time=2 * 24 * 3600)
         # 与其他召回视频池去重,存入对应的redis
-        dup_to_redis(h_video_ids=h_video_ids, now_date=now_date, now_h=now_h, rule_key=rule_key,
+        dup_to_redis(h_video_ids=h_video_ids, now_date=now_date, now_h=now_h, rule_key=rule_key, h_rule_key=h_rule_key,
                      region_24h_rule_key=region_24h_rule_key, region=region,
                      data_key=data_key, by_24h_rule_key=by_24h_rule_key,
                      by_48h_rule_key=by_48h_rule_key, rule_rank_h_flag=rule_rank_h_flag,

+ 2 - 0
region_rule_rank_h_task.sh

@@ -3,12 +3,14 @@ echo $ROV_OFFLINE_ENV
 if [[ $ROV_OFFLINE_ENV == 'test' ]]; then
     cd /data2/rov-offline && /root/anaconda3/bin/python /data2/rov-offline/rule_rank_h_by_24h.py &&
      /root/anaconda3/bin/python /data2/rov-offline/region_rule_rank_h_by24h.py &&
+     /root/anaconda3/bin/python /data2/rov-offline/rule_rank_h_new.py &&
       /root/anaconda3/bin/python /data2/rov-offline/region_rule_rank_h.py '24h'
 #      /root/anaconda3/bin/python /data2/rov-offline/region_rule_rank_h_new.py
 #      /root/anaconda3/bin/python /data2/rov-offline/laohaokan_recommend_update.py
 elif [[ $ROV_OFFLINE_ENV == 'pro' ]]; then
     cd /data/rov-offline && /root/anaconda3/bin/python /data/rov-offline/rule_rank_h_by_24h.py &&
      /root/anaconda3/bin/python /data/rov-offline/region_rule_rank_h_by24h.py &&
+     /root/anaconda3/bin/python /data/rov-offline/rule_rank_h_new.py &&
       /root/anaconda3/bin/python /data/rov-offline/region_rule_rank_h.py '24h'
 #      /root/anaconda3/bin/python /data/rov-offline/region_rule_rank_h_new.py
 #      /root/anaconda3/bin/python /data/rov-offline/laohaokan_recommend_update.py

+ 300 - 0
rule_rank_h_new.py

@@ -0,0 +1,300 @@
+import pandas as pd
+import math
+import traceback
+from functools import reduce
+from odps import ODPS
+from threading import Timer
+from datetime import datetime, timedelta
+from get_data import get_data_from_odps
+from db_helper import RedisHelper
+from utils import filter_video_status, check_table_partition_exits, filter_video_status_app, send_msg_to_feishu
+from config import set_config
+from log import Log
+
+config_, _ = set_config()
+log_ = Log()
+
+
+features = [
+    'apptype',
+    'videoid',
+    'lastonehour_preview',  # 过去1小时预曝光人数 - 区分地域
+    'lastonehour_view',  # 过去1小时曝光人数 - 区分地域
+    'lastonehour_play',  # 过去1小时播放人数 - 区分地域
+    'lastonehour_share',  # 过去1小时分享人数 - 区分地域
+    'lastonehour_return',  # 过去1小时分享,过去1小时回流人数 - 区分地域
+    'lastonehour_preview_total',  # 过去1小时预曝光次数 - 区分地域
+    'lastonehour_view_total',  # 过去1小时曝光次数 - 区分地域
+    'lastonehour_play_total',  # 过去1小时播放次数 - 区分地域
+    'lastonehour_share_total',  # 过去1小时分享次数 - 区分地域
+    'platform_return',
+    'lastonehour_show',  # 不区分地域
+    'lasttwohour_share',  # h-2小时分享人数
+    'lasttwohour_return_now',  # h-2分享,过去1小时回流人数
+    'lasttwohour_return',  # h-2分享,h-2回流人数
+    'lastthreehour_share',  # h-3小时分享人数
+    'lastthreehour_return_now',  # h-3分享,过去1小时回流人数
+    'lastthreehour_return',  # h-3分享,h-3回流人数
+
+    'lastonehour_return_new',  # 过去1小时分享,过去1小时回流人数(回流统计为对应地域分享带回的回流,分享限制地域,回流不限制地域)
+    'lasttwohour_return_now_new',  # h-2分享,过去1小时回流人数(回流统计为对应地域分享带回的回流,分享限制地域,回流不限制地域)
+    'lasttwohour_return_new',  # h-2分享,h-2回流人数(回流统计为对应地域分享带回的回流,分享限制地域,回流不限制地域)
+    'lastthreehour_return_now_new',  # h-3分享,过去1小时回流人数(回流统计为对应地域分享带回的回流,分享限制地域,回流不限制地域)
+    'lastthreehour_return_new',  # h-3分享,h-3回流人数(回流统计为对应地域分享带回的回流,分享限制地域,回流不限制地域)
+    'platform_return_new',  # 平台分发回流(回流统计为对应地域分享带回的回流,分享限制地域,回流不限制地域)
+]
+
+
+def h_data_check(project, table, now_date):
+    """检查数据是否准备好"""
+    odps = ODPS(
+        access_id=config_.ODPS_CONFIG['ACCESSID'],
+        secret_access_key=config_.ODPS_CONFIG['ACCESSKEY'],
+        project=project,
+        endpoint=config_.ODPS_CONFIG['ENDPOINT'],
+        connect_timeout=3000,
+        read_timeout=500000,
+        pool_maxsize=1000,
+        pool_connections=1000
+    )
+
+    try:
+        dt = datetime.strftime(now_date, '%Y%m%d%H')
+        check_res = check_table_partition_exits(date=dt, project=project, table=table)
+        if check_res:
+            sql = f'select * from {project}.{table} where dt = {dt}'
+            with odps.execute_sql(sql=sql).open_reader() as reader:
+                data_count = reader.count
+        else:
+            data_count = 0
+    except Exception as e:
+        data_count = 0
+    return data_count
+
+
+def h_rank_bottom(now_date, now_h, rule_params):
+    """未按时更新数据,用上一小时结果作为当前小时的数据"""
+    redis_helper = RedisHelper()
+    if now_h == 0:
+        redis_dt = datetime.strftime(now_date - timedelta(days=1), '%Y%m%d')
+        redis_h = 23
+    else:
+        redis_dt = datetime.strftime(now_date, '%Y%m%d')
+        redis_h = now_h - 1
+    key_prefix = config_.RECALL_KEY_NAME_PREFIX_BY_H_H
+
+    for param in rule_params.get('params_list'):
+        data_key = param.get('data')
+        rule_key = param.get('rule')
+        log_.info(f"data_key = {data_key}, rule_key = {rule_key}")
+        key_name = f"{key_prefix}{data_key}:{rule_key}:{redis_dt}:{redis_h}"
+        initial_data = redis_helper.get_all_data_from_zset(key_name=key_name, with_scores=True)
+        if initial_data is None:
+            initial_data = []
+        final_data = dict()
+        for video_id, score in initial_data:
+            final_data[video_id] = score
+        # 存入对应的redis
+        final_key_name = \
+            f"{key_prefix}{data_key}:{rule_key}:{datetime.strftime(now_date, '%Y%m%d')}:{now_h}"
+        if len(final_data) > 0:
+            redis_helper.add_data_with_zset(key_name=final_key_name, data=final_data, expire_time=2 * 3600)
+
+
+def get_feature_data(project, table, now_date):
+    """获取特征数据"""
+    dt = datetime.strftime(now_date, '%Y%m%d%H')
+    records = get_data_from_odps(date=dt, project=project, table=table)
+    feature_data = []
+    for record in records:
+        item = {}
+        for feature_name in features:
+            item[feature_name] = record[feature_name]
+        feature_data.append(item)
+    feature_df = pd.DataFrame(feature_data)
+    return feature_df
+
+
+def cal_score(df, param):
+    # score = sharerate * backrate * LOG(lastonehour_return + 1) * K2
+    # sharerate = lastonehour_share / (lastonehour_play + 1000)
+    # backrate = lastonehour_return / (lastonehour_share + 10)
+    # ctr = lastonehour_play / (lastonehour_show + 1000), 对ctr限最大值:K2 = 0.6 if ctr > 0.6 else ctr
+
+    df = df.fillna(0)
+    df['share_rate'] = df['lastonehour_share'] / (df['lastonehour_play'] + 1000)
+    df['back_rate'] = df['lastonehour_return'] / (df['lastonehour_share'] + 10)
+    df['log_back'] = (df['lastonehour_return'] + 1).apply(math.log)
+    if param.get('view_type', None) == 'video-show':
+        df['ctr'] = df['lastonehour_play'] / (df['lastonehour_show'] + 1000)
+    else:
+        df['ctr'] = df['lastonehour_play'] / (df['lastonehour_preview'] + 1000)
+    df['K2'] = df['ctr'].apply(lambda x: 0.6 if x > 0.6 else x)
+
+    df['platform_return_rate'] = df['platform_return'] / df['lastonehour_return']
+
+    df['score'] = df['share_rate'] * df['back_rate'] * df['log_back'] * df['K2']
+
+    df = df.sort_values(by=['score'], ascending=False)
+    return df
+
+
+def merge_df(df_left, df_right):
+    """
+    df按照videoid 合并,对应特征求和
+    :param df_left:
+    :param df_right:
+    :return:
+    """
+    df_merged = pd.merge(df_left, df_right, on=['videoid'], how='outer', suffixes=['_x', '_y'])
+    df_merged.fillna(0, inplace=True)
+    feature_list = ['videoid']
+    for feature in features:
+        if feature in ['apptype', 'videoid']:
+            continue
+        df_merged[feature] = df_merged[f'{feature}_x'] + df_merged[f'{feature}_y']
+        feature_list.append(feature)
+    return df_merged[feature_list]
+
+
+def merge_df_with_score(df_left, df_right):
+    """
+    df 按照videoid合并,平台回流人数、回流人数、分数 分别求和
+    :param df_left:
+    :param df_right:
+    :return:
+    """
+    df_merged = pd.merge(df_left, df_right, on=['videoid'], how='outer', suffixes=['_x', '_y'])
+    df_merged.fillna(0, inplace=True)
+    feature_list = ['videoid', 'lastonehour_return', 'platform_return', 'score']
+    for feature in feature_list[1:]:
+        df_merged[feature] = df_merged[f'{feature}_x'] + df_merged[f'{feature}_y']
+    return df_merged[feature_list]
+
+
+def video_rank_h(df, now_date, now_h, rule_key, param, data_key):
+    """
+    获取符合进入召回源条件的视频
+    """
+    redis_helper = RedisHelper()
+    log_.info(f"videos_count = {len(df)}")
+
+    # videoid重复时,保留分值高
+    df = df.sort_values(by=['score'], ascending=False)
+    df = df.drop_duplicates(subset=['videoid'], keep='first')
+    df['videoid'] = df['videoid'].astype(int)
+
+    # 获取符合进入召回源条件的视频
+    platform_return_rate = param.get('platform_return_rate', 0)
+    h_recall_df = df[df['platform_return_rate'] > platform_return_rate]
+    h_recall_videos = h_recall_df['videoid'].to_list()
+    log_.info(f'h_recall videos count = {len(h_recall_videos)}')
+
+    # 视频状态过滤
+    if data_key in ['data7', ]:
+        filtered_videos = filter_video_status_app(h_recall_videos)
+    else:
+        filtered_videos = filter_video_status(h_recall_videos)
+    log_.info('filtered_videos count = {}'.format(len(filtered_videos)))
+
+    # 写入对应的redis
+    now_dt = datetime.strftime(now_date, '%Y%m%d')
+    h_video_ids = []
+    h_recall_result = {}
+    for video_id in filtered_videos:
+        score = h_recall_df[h_recall_df['videoid'] == video_id]['score']
+        h_recall_result[int(video_id)] = float(score)
+        h_video_ids.append(int(video_id))
+
+    h_recall_key_name = \
+        f"{config_.RECALL_KEY_NAME_PREFIX_BY_H_H}{data_key}:{rule_key}:{now_dt}:{now_h}"
+
+    if len(h_recall_result) > 0:
+        log_.info(f"count = {len(h_recall_result)}, key = {h_recall_key_name}")
+        redis_helper.add_data_with_zset(key_name=h_recall_key_name, data=h_recall_result, expire_time=2 * 3600)
+
+
+def rank_by_h(now_date, now_h, rule_params, project, table):
+    # 获取特征数据
+    feature_df = get_feature_data(now_date=now_date, project=project, table=table)
+    feature_df['apptype'] = feature_df['apptype'].astype(int)
+    # rank
+    data_params_item = rule_params.get('data_params')
+    rule_params_item = rule_params.get('rule_params')
+
+    for param in rule_params.get('params_list'):
+        score_df_list = []
+        data_key = param.get('data')
+        data_param = data_params_item.get(data_key)
+        log_.info(f"data_key = {data_key}, data_param = {data_param}")
+        rule_key = param.get('rule')
+        rule_param = rule_params_item.get(rule_key)
+        log_.info(f"rule_key = {rule_key}, rule_param = {rule_param}")
+        merge_func = rule_param.get('merge_func', 1)
+
+        if merge_func == 2:
+            for apptype, weight in data_param.items():
+                df = feature_df[feature_df['apptype'] == apptype]
+                # 计算score
+                score_df = cal_score(df=df, param=rule_param)
+                score_df['score'] = score_df['score'] * weight
+                score_df_list.append(score_df)
+            # 分数合并
+            df_merged = reduce(merge_df_with_score, score_df_list)
+            # 更新平台回流比
+            df_merged['platform_return_rate'] = df_merged['platform_return'] / df_merged['lastonehour_return']
+            video_rank_h(df=df_merged, now_date=now_date, now_h=now_h,
+                         rule_key=rule_key, param=rule_param, data_key=data_key)
+
+        else:
+            df_list = [feature_df[feature_df['apptype'] == apptype] for apptype, _ in data_param.items()]
+            df_merged = reduce(merge_df, df_list)
+            score_df = cal_score(df=df_merged, param=rule_param)
+            video_rank_h(df=score_df, now_date=now_date, now_h=now_h,
+                         rule_key=rule_key, param=rule_param, data_key=data_key)
+
+
+def h_timer_check():
+    try:
+        project = config_.PROJECT_H_APP_TYPE
+        table = config_.TABLE_H_APP_TYPE
+        rule_params = config_.RULE_PARAMS_H_APP_TYPE
+        now_date = datetime.today()
+        log_.info(f"now_date: {datetime.strftime(now_date, '%Y%m%d%H')}")
+        now_min = datetime.now().minute
+        now_h = datetime.now().hour
+
+        if now_h == 0:
+            log_.info(f'now_h = {now_h} use bottom data!')
+            h_rank_bottom(now_date=now_date, now_h=now_h, rule_params=rule_params)
+            log_.info(f"h_data end!")
+            return
+        # 查看当前小时级更新的数据是否已准备好
+        h_data_count = h_data_check(project=project, table=table, now_date=now_date)
+        if h_data_count > 0:
+            log_.info(f'h_data_count = {h_data_count}')
+            # 数据准备好,进行更新
+            rank_by_h(now_date=now_date, now_h=now_h, rule_params=rule_params, project=project, table=table)
+            log_.info(f"h_data end!")
+        elif now_min > 40:
+            log_.info('h_recall data is None, use bottom data!')
+            h_rank_bottom(now_date=now_date, now_h=now_h, rule_params=rule_params)
+            log_.info(f"h_data end!")
+        else:
+            # 数据没准备好,1分钟后重新检查
+            Timer(60, h_timer_check).start()
+
+    except Exception as e:
+        log_.error(f"不区分地域小时级数据更新失败, exception: {e}, traceback: {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=f"rov-offline{config_.ENV_TEXT} - 不区分地域小时级数据更新失败\n"
+                     f"exception: {e}\n"
+                     f"traceback: {traceback.format_exc()}"
+        )
+
+
+if __name__ == '__main__':
+    log_.info(f"h_data start...")
+    h_timer_check()

+ 1 - 0
videos_filter.py

@@ -791,6 +791,7 @@ def filter_process_with_region(data_key, rule_key, region, now_date, now_h):
     # 需过滤视频列表
     key_prefix_list = [
         config_.RECALL_KEY_NAME_PREFIX_REGION_BY_H,
+        config_.RECALL_KEY_NAME_PREFIX_DUP_H_H,
         config_.RECALL_KEY_NAME_PREFIX_DUP1_REGION_24H_H,
         # config_.RECALL_KEY_NAME_PREFIX_DUP1_REGION_DAY_H,
         # config_.RECALL_KEY_NAME_PREFIX_DUP2_REGION_DAY_H,