import datetime import os import time import requests from api import API from back_ground_module import CommonModule import pandas as pd from log_config import configure_task_logger, configure_error_task_logger api_instance = API() common_module = CommonModule() # start_time = datetime.datetime.now() # 获取已经配置好的常规日志记录器 logger = configure_task_logger() # 获取已经配置好的错误任务日志记录器 error_task_logger = configure_error_task_logger() output_dir = "output" # 设置输出目录 os.makedirs(output_dir, exist_ok=True) class NewExceptionTask: """ SaaS异常回访 """ def __init__(self): self.exception_service_todo = None self.get_feature_usage = None self.saas_create_time = None self.index = None self.date_one = None self.data_yichang_S = None self.date_list = None self.Smart_detection = None self.service_remind = None self.NGV_data_list = None self.permissions_table = None self.staff_id_list = None self.json_list = [] self.policy_recognition = None self.widget_list = None self.private_domain = None self.public_domain = None self.public_domain_list = None self.different_industries = None self.different_industries_list = None self.groupnotification = None self.fields_mapping = { "门店名称": "_widget_1748241895830", "联系人": "_widget_1748241895831", "开户时间": "_widget_1748241895839", "门店编码": "_widget_1748241895842", "联系方式": "_widget_1748241895832", "系统版本": "_widget_1748241895850", "公司名称": "_widget_1748241895844", "运营顾问": "_widget_1748246808679", "区域经理": "_widget_1748246808682", "公司等级": "_widget_1748241895846", "运营专家": "_widget_1748246808681", "操作模式E.L/E.S": "_widget_1748241895853", "活跃健康状态变化": "_widget_1748241895829", "初始日": "_widget_1748241895833", "推进日": "_widget_1748241895834", "异常跟进情况描述": "_widget_1748512176640", "异常变化原因": "_widget_1748512176641", "正常使用": "_widget_1748512176643", "门店原因": "_widget_1748512176645", "服务原因": "_widget_1748512176647", "产品原因": "_widget_1748512176649", "未正式切换": "_widget_1748512176651", "跟进状态": "_widget_1748512176655", "是否可激活": "_widget_1758615839701", "是否有续约风险": "_widget_1758615839703", "当前跟进人": "_widget_1748246808678", "激活策略": "_widget_1758615839717", "跟进时间": "_widget_1748512176654", "是否跟进完成": "_widget_1751273412737", "区域客服": "_widget_1748246808680", "大区": "_widget_1748241895847", "省": "_widget_1748241895848", "城市": "_widget_1748241895855", "门店类型": "_widget_1748241895849", "saas客户类型": "_widget_1748241895851", "门店阶段": "_widget_1748241895852", "提交人": "creator", "提交时间": "createTime", "更新时间": "updateTime" } def calculate_date_one(self, start_offset=0): """ 计算从当前日期(或指定偏移量的日期)开始,往前遍历遇到date_list中日期的次数。 参数: - start_offset: 从当前日期起始的天数偏移量,默认为0(即今天)。负数表示过去,正数表示未来。 返回: - date_one: 遍历到date_list中日期的次数。 """ jdy_date = datetime.datetime.now().strftime("%Y-%m-%d") jdy_start_time = datetime.datetime.now().strftime("%Y-%m-%d ") # 设置起始日期 now_time = datetime.datetime.now() + datetime.timedelta(days=start_offset) # 初始化计数器 date_one = 1 print("当前日期:", now_time.strftime("%Y-%m-%d")) # 检查起始日期是否在date_list中 if now_time.strftime("%Y-%m-%d") in self.date_list: date_one = 0 print("开始次数:", date_one) else: # 遍历日期 for i in range(1, 10): new_date = now_time + datetime.timedelta(days=-i) new_date_str = new_date.strftime("%Y-%m-%d") print("遍历日期:", new_date_str) if new_date_str in self.date_list: date_one += 1 print("节假日期:", new_date_str) else: break print("遍历次数:", date_one) return date_one @staticmethod def download_url_content(url, save_path): """ 下载指定 URL 的内容并保存到本地文件。 :param url: 要下载内容的 URL :param save_path: 保存文件的路径 """ try: # 发送 GET 请求以获取内容 response = requests.get(url, stream=True) response.raise_for_status() # 如果响应状态码不是 200,抛出异常 # 确保保存目录存在 os.makedirs(os.path.dirname(save_path), exist_ok=True) # 将内容写入文件 with open(save_path, 'wb') as file: for chunk in response.iter_content(chunk_size=8192): # 分块写入,避免占用过多内存 if chunk: # 过滤掉空块 file.write(chunk) print(f"文件已成功保存到 {save_path}") except requests.exceptions.RequestException as e: print(f"下载失败: {e}") except Exception as e: print(f"发生错误: {e}") def load_all_data(self): """加载所有必要的数据表""" # 省市区人员关系表 payload = {"api_key": "675b900991ad2491c69389ca", "entry_id": "676512ac3e54dc3159460c0a"} json_dict = api_instance.entry_data_list(payload) self.json_list = json_dict.get("data") # 获取简道云员工id payload = {"api_key": "6694d3c4fcb69ca9a111a6c4", "entry_id": "6769204a1902c9341340a1bc", } staff_id = api_instance.entry_data_list(payload) self.staff_id_list = staff_id.get("data") # api请求格式,将数据封装在data字典里 # 获取NGV数据 payload = {"api_key": "675b900991ad2491c69389ca", "entry_id": "675bb02bd2d53c2034c665e4"} self.NGV_data_list = api_instance.entry_data_list(payload).get("data", []) # print("NGV获取后的类型:", type(self.NGV_data_list)) # 获取异常服务待办 payload = {"api_key": "675b900991ad2491c69389ca", "entry_id": "68340de79f116c0b66b6b0cc"} self.exception_service_todo = api_instance.entry_data_list(payload).get("data", []) print(self.exception_service_todo) @staticmethod def build_index(json_list): index = {} for json_item in json_list: try: key = (json_item['_widget_1734677164861'], json_item['_widget_1734677164862'], json_item['_widget_1734677164863']) # 省市区 if '_widget_1734677164870' not in json_item: # 异常回访客服 raise KeyError("缺少 '异常回访客服' 键") index[key] = json_item except KeyError as e: print(f"警告:{e},跳过该条记录: {json_item}") continue print('index', index) return index @staticmethod def find_customer_service(province_name, city_name, area_name, index): key = (province_name, city_name, area_name) # print(index) if key not in index: return "数据缺失: 未找到对应的异常回访客服" return index[key] @staticmethod def get_staff_id(row_item, name): """辅助函数,用于获取员工ID""" if str(row_item["_widget_1734942794144"]) == str(name): # 检查姓名是否匹配 return row_item["_widget_1734942794145"] # 返回员工ID return None def assign_customer_service(self, province_name, city_name, area_name, index): """根据省市区派发给异常回访客服""" # try: customer_service_info = self.find_customer_service(province_name, city_name, area_name, index) customer_service = customer_service_info.get('_widget_1734677164870', {}).get('username') # 异常回访客服 return customer_service # except Exception as e: # print(f"Error finding customer service: {e}") # return "分配失败,请检查", "分配失败,请检查", "分配失败,请检查" def main(self): task_start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") try: self.load_all_data() self.data_yichang_S = common_module.get_yichang_details(days_back=1).astype(str) # 获取data_NGV 并转为str self.index = self.build_index(self.json_list) logger.info("开始运行SaaS异常回访") data_yichang = self.data_yichang_S.copy() # data_yichang.to_csv(os.path.join(output_dir,"data_yichang.csv"), index=False) def replace_values(series): # 使用条件判断来进行替换 return series.apply(lambda x: '' if pd.isna(x) or x in ['NA', 'None', ''] else x) # 对整个DataFrame的所有列应用替换函数 data_yichang = data_yichang.apply(replace_values) for index_num, row in data_yichang.iterrows(): # 对过滤后的每一条进行派发 try: # 每次循环前清空省市区变量 province_name = None city_name = None area_name = None is_pass = False for exception_service in self.exception_service_todo : if exception_service['_widget_1748241895842'] == row['org_code'] and exception_service['_widget_1748512176655'] in ['未处理', '处理中']: is_pass = True break if is_pass: logger.info(f"已存在待办,跳过该条记录: {row}") continue payload_dict = {} distribution_date = datetime.datetime.now(datetime.timezone.utc) distribution_date = distribution_date.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' date_obj1 = datetime.datetime.strptime(row["init_day"], "%Y%m%d").strftime("%Y-%m-%d") date_obj2 = datetime.datetime.strptime(row["push_day"], "%Y%m%d").strftime("%Y-%m-%d") NGV_roles = { 'service_impl_principal': row['service_impl_principal'], # 运营负责人 'area_manager': row['area_manager'], # 区域经理 'technician': row['technician'], # 运营专家 } for role, name in NGV_roles.items(): # 寻找对应的员工ID for row_item in self.staff_id_list: staff_id = self.get_staff_id(row_item, name) if staff_id: NGV_roles[role] = staff_id break # 找到后退出循环 else: NGV_roles[role] = None # 如果没有找到对应的员工ID relationship_manager, area_manager, technician = [NGV_roles[role] for role in ['service_impl_principal', 'area_manager', 'technician']] UUid = time.strftime("%Y%m%d%H%M%S", time.localtime()) NGV_data_id = None reason = None create_exception =None create_date = None # 优先从 data_yichang_S 获取省市区信息 province_name = row.get('province_name') city_name = row.get('city_name') area_name = row.get('area_name') if 'area_name' in row else row.get('district_name') # 检查省市区是否完整(省市区是一体的,任意一个缺失就需要从NGV获取) use_ngv_location = False if (not province_name or province_name in ['', 'None', 'NA'] or not city_name or city_name in ['', 'None', 'NA'] or not area_name or area_name in ['', 'None', 'NA']): use_ngv_location = True logger.info(f"门店 {row['org_code']} 的省市区信息不完整,将从NGV_data_list获取") # 获取关联数据 for NGV_Data in self.NGV_data_list: # NGV_Data = NGV_Data.get("data") if row["org_code"] == NGV_Data.get("_widget_1734062123071"): # 门店编码 NGV_data_id = NGV_Data.get("_id") # 如果需要从 NGV_data_list 获取省市区信息 if use_ngv_location: province_name = NGV_Data.get("_widget_1734062123090") city_name = NGV_Data.get("_widget_1734062123092") area_name = NGV_Data.get("_widget_1734062123094") logger.info(f"【从NGV获取省市区】门店 {row['org_code']}: {province_name}, {city_name}, {area_name}") # 门店原因 reason = NGV_Data.get("_widget_1758617393828") logger.info(f"获取关联数据成功:{NGV_data_id}, {province_name}, {city_name}, {area_name}") # 是否生成异常待办 create_exception = NGV_Data.get("_widget_1758769279995") # 获取上线日期(文本) create_date = NGV_Data.get("_widget_1734062123176") break # 找到匹配的数据后退出循环 # 判断门店原因 # if reason in ["门店倒闭", "门店转让", "加盟其他连锁","切换竞品","虚拟门店","重新开户","已退款","二套系统"]: # continue # 判断是否继续生成异常待办 if create_exception == "否": continue # 新增:检查 create_date_str 是否存在且有效 if not create_date: logger.warning("上线日期为空,跳过该记录") continue # 定义可能的日期格式(灵活应对不同格式) date_formats = [ "%Y-%m-%d %H:%M:%S", # 含时间 "%Y-%m-%d", # 仅日期 "%Y/%m/%d", "%Y/%m/%d %H:%M:%S" ] parsed_date = None for fmt in date_formats: try: parsed_date = datetime.datetime.strptime(create_date.strip(), fmt).date() logger.debug(f"使用格式 {fmt} 成功解析日期: {parsed_date}") break except ValueError: continue if parsed_date is None: logger.error(f"无法解析上线日期: '{create_date}',支持的格式: %Y-%m-%d, %Y-%m-%d %H:%M:%S 等") continue # 解析失败,跳过 # 使用解析后的日期进行判断 now_date = datetime.date.today() delta = now_date - parsed_date days_diff = delta.days if days_diff > 30: logger.info(f"上线日期 {parsed_date} 超过30天({days_diff}天),生成待办") # ✅ 继续后续待办创建逻辑 else: logger.info(f"上线日期 {parsed_date} 在30天内,跳过处理") continue if not NGV_data_id: logger.warning(f"未找到关联数据,请检查门店编码: {row['org_code']}") # 根据省市区派发给异常回访客服 # 检查省市区是否都有值,如果有任何一个为空,则客服为空 if (not province_name or province_name in ['', 'None', 'NA'] or not city_name or city_name in ['', 'None', 'NA'] or not area_name or area_name in ['', 'None', 'NA']): customer_service = None logger.warning(f"【省市区信息缺失】门店 {row['org_code']} 省市区信息不完整,异常回访客服设置为空") logger.warning(f"省: {province_name}, 市: {city_name}, 区: {area_name}") else: customer_service = self.assign_customer_service(province_name, city_name, area_name, self.index) logger.info(f"【派发客服】门店 {row['org_code']} 派发给客服: {customer_service}") payload_dict.update({ "_widget_1748241895829": {"value": row["health_warning_info"]}, # 活跃健康状态变化 "_widget_1748241895830": {"value": row["org_name"]}, # 门店名称 "_widget_1748241895831": {"value": row["contacts"]}, # 联系人 "_widget_1748241895832": {"value": row['contact_mobile']}, # 联系方式 "_widget_1748241895833": { "value": int(time.mktime(time.strptime(date_obj1, "%Y-%m-%d")) * 1000) if row[ "init_day"] != '' else ''}, # 初始日 "_widget_1748241895834": { "value": int(time.mktime(time.strptime(date_obj2, "%Y-%m-%d")) * 1000) if row[ "push_day"] != '' else ''}, # 推进日 "_widget_1748246808678": {"value": customer_service}, # 当前跟进人 # "_widget_1748246808678": {"value": "083726094935447433"}, # 当前跟进人 "_widget_1748246808679": {"value": relationship_manager}, # 运营负责人 "_widget_1748246808680": {"value": customer_service}, # 区域客服 "_widget_1748241895839": { "value": int(time.mktime(time.strptime(row["saas_create_time"], "%Y-%m-%d")) * 1000) if row[ "saas_create_time"] != '' else ''}, # 开户时间 "_widget_1748246808681": {"value": technician}, # 技术专家 "_widget_1748246808682": {"value": area_manager}, # 区域经理 "_widget_1748241895842": {"value": row['org_code']}, # 门店编码 "_widget_1748241895844": {"value": row['group_name']}, # 公司名称 "_widget_1748241895846": {"value": row['group_grade']}, # 公司等级 "_widget_1748241895847": {"value": row['region_name']}, # 大区 "_widget_1748241895848": {"value": row['province_name']}, # 省 "_widget_1748241895849": {"value": row['org_type']}, # 门店类型 "_widget_1748241895850": {"value": row['saas_edition_fmt']}, # 系统版本 "_widget_1748241895851": {"value": row['saas_customer_type']}, # saas客户类型 "_widget_1748241895852": {"value": row['org_stage']}, # 门店阶段 "_widget_1748241895853": {"value": row['contact_mobile']}, # 操作模式E.L/E.S "_widget_1748241895855": {"value": row['city_name']}, # 城市 "_widget_1748247754304": {"value": NGV_data_id}, # 数据id "_widget_1748512176655": {"value": "未处理"}, # 跟进状态 }) routine_follow_up_payload = { "api_key": "675b900991ad2491c69389ca", "entry_id": "68340de79f116c0b66b6b0cc", # 异常服务跟进待办 "is_start_workflow": "true", "data": payload_dict, "transaction_id": UUid } res = api_instance.data_batch_create(routine_follow_up_payload) logger.info(f"创建结果:{res}") except: pass common_module.send_task_status(task_start_time, "异常服务待办派发") except Exception as e: error_task_logger.error(f"异常服务待办派发执行时发生异常: {e}") common_module.send_task_error(task_start_time, "异常服务待办派发", str(e)) if __name__ == '__main__': start = NewExceptionTask() start.main()