From 8c34b781e0d7ac5ddadbf0eb157a99f8afbd8118 Mon Sep 17 00:00:00 2001 From: z66 <1415243231@qq.com> Date: Thu, 4 Dec 2025 17:44:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=9B=9E=E8=AE=BF=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E6=8A=9B=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back_ground_module/Exception_Task.py | 10 +- back_ground_module/common_module.py | 38 +- back_ground_module/logs/error_task.log | 27 - ..._denominator_reporting_adjustment_to_bi.py | 2 +- ...ate_molecule_reporting_adjustment_to_bi.py | 2 + log_config.py | 16 +- test/json.json | 279 +++++++--- test/客户资料表.ipynb | 103 ++++ test/异常回访补拍.py | 500 ++++++++++++++++++ test/异常待办数据导出.py | 2 +- 10 files changed, 860 insertions(+), 119 deletions(-) create mode 100644 test/客户资料表.ipynb create mode 100644 test/异常回访补拍.py diff --git a/back_ground_module/Exception_Task.py b/back_ground_module/Exception_Task.py index d857547..3e52fad 100644 --- a/back_ground_module/Exception_Task.py +++ b/back_ground_module/Exception_Task.py @@ -248,7 +248,7 @@ class NewExceptionTask: # 对整个DataFrame的所有列应用替换函数 data_yichang = data_yichang.apply(replace_values) - + error_data= [] for index_num, row in data_yichang.iterrows(): # 对过滤后的每一条进行派发 try: # 每次循环前清空省市区变量 @@ -471,8 +471,14 @@ class NewExceptionTask: res = api_instance.data_batch_create(routine_follow_up_payload) logger.info(f"创建结果:{res}") - except: + except Exception as e: + error_task_logger.exception(f"异常服务待办派发执行时发生异常: {e}") + error_data.append(row) pass + if error_data: + error_df = pd.DataFrame(error_data) + error_df.to_csv(os.path.join(output_dir, "异常派发错误数据.csv")) + common_module.send_task_error(task_start_time = task_start_time,task_name= "异常服务待办派发",error_message="详情见失败文件", df = error_df) # ndf = pd.DataFrame(all_data) # ndf.to_csv(os.path.join(output_dir, "异常派发.csv")) common_module.send_task_status(task_start_time, "异常服务待办派发") diff --git a/back_ground_module/common_module.py b/back_ground_module/common_module.py index aca81b0..bd13477 100644 --- a/back_ground_module/common_module.py +++ b/back_ground_module/common_module.py @@ -6,7 +6,7 @@ import pandas as pd import pymysql from api import API from log_config import configure_task_logger, configure_error_task_logger - +import time api_instance = API() # 获取已经配置好的常规日志记录器 @@ -168,7 +168,6 @@ class CommonModule: cursor.close() conn.close() - return data_yichang except Exception as e: @@ -200,7 +199,7 @@ class CommonModule: # 执行语句并获取结果集 cursor.execute(sql) rows = cursor.fetchall() - all_fields = cursor.description # 获取所有字段名 + all_fields = cursor.description # 获取所有字段名 # 执行结果转化为dataframe col = [i[0] for i in all_fields] @@ -215,7 +214,6 @@ class CommonModule: error_task_logger.error(f"获取续约待办数据时出错: {e}") return None - def get_jcb_details(self, ): """ 从固定的数据库中获取前几天的借车宝。 @@ -510,7 +508,6 @@ class CommonModule: error_task_logger.error(f"获取履约表数据时出错: {e}") return None - def get_GroupNotification_details(self, ): """ 从f6operation_data_relay数据库中获取短信数据支撑数据。 @@ -612,9 +609,11 @@ class CommonModule: task_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") CommonModule.send_task_error(task_start_time, "发送任务状态", e) - def send_task_error(self, task_start_time: str, task_name: str, error_message: str) -> None: + def send_task_error(self, task_start_time: str, task_name: str, error_message: str, + df: pd.DataFrame = None) -> None: """ 将任务失败情况发送到简道云(影响业务数据时调用) + :param df: 失败文件 :param task_start_time: 任务开始时间(字符串格式:"%Y-%m-%d %H:%M:%S",表示北京时间 UTC+8) :param task_name: 任务名称 :param error_message: 失败详情 @@ -639,7 +638,26 @@ class CommonModule: task_end_iso = end_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ") task_start_iso = task_start_utc.strftime("%Y-%m-%dT%H:%M:%SZ") - # 6. 构造请求数据(所有时间以 UTC 格式发送) + # 6.上传附件 + UUid = time.strftime("%Y%m%d%H%M%S", time.localtime()) + if df is not None: + df.to_excel("upload_file.xlsx", index=False) + file_path = "upload_file.xlsx" + + up_data = api_instance.get_upload_token( + {"api_key": "6694d3c4fcb69ca9a111a6c4", "entry_id": "689ae65da00c17578e27cd74", + "transaction_id": UUid}) + + upload_url = up_data.get("upload_url") + upload_token = up_data.get("upload_token") + + upload_result = api_instance.upload_file( + {"upload_url": upload_url, "upload_token": upload_token, "file_path": file_path}) + upload_key = upload_result.get("key") + else: + upload_key = "" + + # 7. 构造请求数据(所有时间以 UTC 格式发送) payload = { "api_key": Config.SCHEDULED_TASKS_APP_ID, "entry_id": Config.JDY_TASKS_ERROR_ENTRY_ID, @@ -650,10 +668,12 @@ class CommonModule: "_widget_1744873387502": {"value": task_end_iso}, # UTC 结束时间 "_widget_1744873387504": {"value": run_time_sec}, "_widget_1754981992215": {"value": error_message}, # 错误信息 - } + "_widget_1764830825356": {"value": [upload_key]} + }, + "transaction_id": UUid } - # 7. 发送请求 + # 8. 发送请求 response = api_instance.data_batch_create(payload) logger.info(f"任务错误发生成功: {response}") diff --git a/back_ground_module/logs/error_task.log b/back_ground_module/logs/error_task.log index 3bb096b..e69de29 100644 --- a/back_ground_module/logs/error_task.log +++ b/back_ground_module/logs/error_task.log @@ -1,27 +0,0 @@ -2025-11-17 09:27:24,426 - common_module.py - error_task_logger - ERROR - 获取异常明细时出错: can't execute an empty query -2025-11-17 17:08:41,207 - Exception_Task.py - error_task_logger - ERROR - 异常服务待办派发执行时发生异常: [Errno 13] Permission denied: 'output\\异常派发.csv' -2025-11-18 09:04:21,887 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:06:54,279 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:09:10,231 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:11:15,907 - update_BI_CRM_info.py - error_task_logger - ERROR - 导入数据时发生错误: Failed executing the operation; Python type dict cannot be converted -2025-11-18 09:11:16,451 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:14:02,671 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:14:18,875 - update_BI_CRM_info.py - error_task_logger - ERROR - 导入数据时发生错误: 1054 (42S22): Unknown column 'nan' in 'field list' -2025-11-18 09:14:19,185 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:15:37,826 - update_BI_CRM_info.py - error_task_logger - ERROR - 导入数据时发生错误: 1054 (42S22): Unknown column 'nan' in 'field list' -2025-11-18 09:15:38,068 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:16:52,668 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-18 09:17:05,585 - update_BI_CRM_info.py - error_task_logger - ERROR - 导入数据时发生错误: 1054 (42S22): Unknown column 'nan' in 'field list' -2025-11-18 09:17:05,912 - update_BI_CRM_info.py - error_task_logger - ERROR - 任务简道云海外项目CRM客户档案迁移BI执行失败。 -2025-11-27 09:08:41,105 - common_module.py - error_task_logger - ERROR - 获取借车宝NGV明细时出错: Length mismatch: Expected axis has 0 elements, new values have 16 elements -2025-11-27 09:08:41,278 - JCB_efficient_car_pickup.py - error_task_logger - ERROR - 接车宝日常派发执行出错:获取接车宝数据失败,返回None -2025-12-03 09:35:35,923 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:37:19,719 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:38:02,309 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:39:56,925 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:41:22,721 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:42:57,097 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:45:13,572 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:50:07,647 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:51:28,042 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' -2025-12-03 09:55:47,222 - update_molecule_reporting_adjustment_to_bi.py - error_task_logger - ERROR - 任务执行失败: unsupported operand type(s) for //: 'NoneType' and 'int' diff --git a/back_ground_module/update_denominator_reporting_adjustment_to_bi.py b/back_ground_module/update_denominator_reporting_adjustment_to_bi.py index ef02e09..58d5ada 100644 --- a/back_ground_module/update_denominator_reporting_adjustment_to_bi.py +++ b/back_ground_module/update_denominator_reporting_adjustment_to_bi.py @@ -98,7 +98,7 @@ class DenominatorReportingAdjustment: if id_in_map == field_id: transformed_data[display_name] = value break - # print(transformed_data.get("是否上传衡石")) + # print(transformed_data.get("是否上传衡石"))# BI上已经实现 # if transformed_data.get("是否上传衡石") == "否" or transformed_data.get("是否上传衡石") is None: # continue self.denominator_data_list.append(transformed_data) diff --git a/back_ground_module/update_molecule_reporting_adjustment_to_bi.py b/back_ground_module/update_molecule_reporting_adjustment_to_bi.py index dea469a..a2fd508 100644 --- a/back_ground_module/update_molecule_reporting_adjustment_to_bi.py +++ b/back_ground_module/update_molecule_reporting_adjustment_to_bi.py @@ -88,6 +88,7 @@ class MoleculeReportingAdjustment: if id_in_map == field_id: transformed_data[display_name] = value break + # BI上已经实现 # if transformed_data.get("是否上传衡石") == "否" or transformed_data.get("是否上传衡石") is None: # continue self.molecule_data_list.append(transformed_data) @@ -190,6 +191,7 @@ class MoleculeReportingAdjustment: logger.info(f"获取宜搭数据成功") df = pd.DataFrame(self.molecule_data_list) + # df.to_csv('molecule_data.csv', index=False) if '归属月份' in df.columns: # 确保是整数类型 df['归属月份'] = df['归属月份'].astype('Int64') diff --git a/log_config.py b/log_config.py index d3d8be4..75b5627 100644 --- a/log_config.py +++ b/log_config.py @@ -106,4 +106,18 @@ def configure_detail_logger(): # 预配置日志记录器 task_logger = configure_task_logger() error_logger = configure_error_task_logger() -detail_logger = configure_detail_logger() \ No newline at end of file +detail_logger = configure_detail_logger() + +# ===== 新增:自动为 error_logger.error 添加 traceback 支持 ===== +import types +import sys + +_original_error = error_logger.error + +def enhanced_error(self, msg, *args, **kwargs): + if 'exc_info' not in kwargs: + if sys.exc_info()[0] is not None: + kwargs['exc_info'] = True + return _original_error(msg, *args, **kwargs) + +error_logger.error = types.MethodType(enhanced_error, error_logger) \ No newline at end of file diff --git a/test/json.json b/test/json.json index f848883..37cde2e 100644 --- a/test/json.json +++ b/test/json.json @@ -1,79 +1,202 @@ { - 'data': { - '_id': '68d39a00bc54693f3c1e47d4', - 'creator': { - 'name': '王兴财', - 'username': '02480455276729107961', - 'status': 1, - 'type': 0, - 'departments': [ - 177751223 - ], - 'integrate_id': '02480455276729107961' - }, - 'updater': { - 'name': '王兴财', - 'username': '02480455276729107961', - 'status': 1, - 'type': 0, - 'departments': [ - 177751223 - ], - 'integrate_id': '02480455276729107961' - }, - 'deleter': None, - 'createTime': '2025-09-24T07:13:04.967Z', - 'updateTime': '2025-09-24T07:13:04.967Z', - 'deleteTime': None, - '_widget_1734589432084': '67ebe4846c3e7153268e9004', - '_widget_1735268263061': '续约后180天回访', - '_widget_1735268263079': '检测单', - '_widget_1735268263063': '是', - '_widget_1735268263065': '否', - '_widget_1735268263067': '', - '_widget_1735527329557': '', - '_widget_1736414617462': '是', - '_widget_1735268263069': '', - '_widget_1735268263070': '多次去电未接听,需区域协助联系', - '_widget_1735268263071': '', - '_widget_1735268263072': '', - '_widget_1764293754939': '', - '_widget_1735280720550': '10546443563957342000', - '_widget_1735268263062': '2025-09-24T07:12:59.124Z', - '_widget_1734589782134': { - 'name': '王兴财', - 'username': '02480455276729107961', - 'status': 1, - 'type': 0, - 'departments': [ - 177751223 - ], - 'integrate_id': '02480455276729107961' - }, - '_widget_1743128977141': { - 'name': '吴间锐', - 'username': '283227523421943504', - 'status': 1, - 'type': 0, - 'departments': [ - 122323520 - ], - 'integrate_id': '283227523421943504' - }, - '_widget_1743129332378': { - 'name': '熊斌', - 'username': '2530511650927042', - 'status': 1, - 'type': 0, - 'departments': [ - 122323520 - ], - 'integrate_id': '2530511650927042' - }, - '_widget_1745207582477': '其他', - '_widget_1745207582479': '', - '_widget_1745207582481': '', - 'appId': '675b900991ad2491c69389ca', - 'entryId': '6763bbf657bd8fb76fcb41b2' - } -} + # 基础信息 + 'date_id': '_widget_1734062123065', + 'date_fmt': '_widget_1734062123066', + 'id_own_group': '_widget_1734062123067', + 'group_name': '_widget_1734062123068', + 'id_own_org': '_widget_1734062123069', + 'org_name': '_widget_1734062123070', + 'org_code': '_widget_1734062123071', + 'group_grade': '_widget_1734062123072', + 'org_type': '_widget_1734062123073', + 'org_status': '_widget_1734062123074', + + # SaaS相关 + 'saas_version': '_widget_1734062123075', + 'is_wechat': '_widget_1734062123076', + 'is_mini_app': '_widget_1734062123077', + 'is_wx_shop': '_widget_1734062123078', + 'is_camera_service': '_widget_1734062123079', + 'is_maintenance_service': '_widget_1734062123080', + 'saas_create_time': '_widget_1734062123081', + 'expiry_time': '_widget_1734062123082', + 'saas_use_days': '_widget_1734062123083', + 'saas_use_year': '_widget_1734062123084', + 'is_main_org': '_widget_1734062123085', + + # 证照信息 + 'license_code': '_widget_1734062123086', + 'license_name': '_widget_1734062123087', + 'org_crm_id': '_widget_1734062123088', + + # 地理信息 + 'province_id': '_widget_1734062123089', + 'province_name': '_widget_1734062123090', + 'city_id': '_widget_1734062123091', + 'city_name': '_widget_1734062123092', + 'area_id': '_widget_1734062123093', + 'area_name': '_widget_1734062123094', + 'region_name': '_widget_1734062123095', + 'region_short_name': '_widget_1734062123096', + 'branch_name': '_widget_1734062123097', + + # 车主邦相关 + 'carzone_store_id': '_widget_1734062123098', + 'carzone_store_name': '_widget_1734062123099', + 'customer_carzone_id': '_widget_1734062123100', + + # 人员信息 + 'salesmen': '_widget_1734062123101', + 'area_manager': '_widget_1734062123102', + 'service_salesmen': '_widget_1734062123103', + 'impl_principal': '_widget_1734062123104', + 'service_impl_principal': '_widget_1734062123105', + + # 用户统计 + 'active_user_count': '_widget_1734062123106', + 'active_user_type': '_widget_1734062123107', + 'limit_user_count': '_widget_1734062123108', + 'limit_user_type': '_widget_1734062123109', + + # NGV标记 + 'is_n': '_widget_1734062123110', + 'is_g': '_widget_1734062123111', + 'is_v': '_widget_1734062123112', + 'is_visited': '_widget_1734062123113', + 'is_active': '_widget_1734062123114', + 'active_status_fmt': '_widget_1734062123115', + + # 单据统计 + 'bill_count_last_30_day': '_widget_1734062123116', + 'bill_day_count_last_30_day': '_widget_1734062123117', + 'bill_day_count_this_month': '_widget_1734062123118', + 'bill_count_last_7_day': '_widget_1734062123119', + 'bill_day_count_last_7_day': '_widget_1734062123120', + 'pv_count': '_widget_1734062123121', + 'uv_count': '_widget_1734062123122', + + # 每日单据数(1-31天) + 'bill_count_1d': '_widget_1734062123123', + 'bill_count_2d': '_widget_1734062123124', + 'bill_count_3d': '_widget_1734062123125', + 'bill_count_4d': '_widget_1734062123126', + 'bill_count_5d': '_widget_1734062123127', + 'bill_count_6d': '_widget_1734062123128', + 'bill_count_7d': '_widget_1734062123129', + 'bill_count_8d': '_widget_1734062123130', + 'bill_count_9d': '_widget_1734062123131', + 'bill_count_10d': '_widget_1734062123132', + 'bill_count_11d': '_widget_1734062123133', + 'bill_count_12d': '_widget_1734062123134', + 'bill_count_13d': '_widget_1734062123135', + 'bill_count_14d': '_widget_1734062123136', + 'bill_count_15d': '_widget_1734062123137', + 'bill_count_16d': '_widget_1734062123138', + 'bill_count_17d': '_widget_1734062123139', + 'bill_count_18d': '_widget_1734062123140', + 'bill_count_19d': '_widget_1734062123141', + 'bill_count_20d': '_widget_1734062123142', + 'bill_count_21d': '_widget_1734062123143', + 'bill_count_22d': '_widget_1734062123144', + 'bill_count_23d': '_widget_1734062123145', + 'bill_count_24d': '_widget_1734062123146', + 'bill_count_25d': '_widget_1734062123147', + 'bill_count_26d': '_widget_1734062123148', + 'bill_count_27d': '_widget_1734062123149', + 'bill_count_28d': '_widget_1734062123150', + 'bill_count_29d': '_widget_1734062123151', + 'bill_count_30d': '_widget_1734062123152', + 'bill_count_31d': '_widget_1734062123153', + + # ETL时间 + 'etl_time': '_widget_1734062123154', + + # 业务类型统计 + 'maintain_bill_count_last_30_day': '_widget_1734062123155', + 'washing_bill_count_last_30_day': '_widget_1734062123156', + 'maintain_bill_day_count_last_30_day': '_widget_1734062123157', + 'washing_bill_day_count_last_30_day': '_widget_1734062123158', + 'retail_bill_count_last_30_day': '_widget_1734062123159', + 'retail_bill_day_count_last_30_day': '_widget_1734062123160', + 'purchase_bill_count_last_30_day': '_widget_1734062123161', + 'purchase_bill_day_count_last_30_day': '_widget_1734062123162', + 'card_bill_count_last_30_day': '_widget_1734062123163', + 'card_bill_day_count_last_30_day': '_widget_1734062123164', + 'gd_sales_bill_count_last_30_day': '_widget_1734062123165', + 'gd_sales_bill_day_count_last_30_day': '_widget_1734062123166', + + # G标记相关 + 'g_change_flag': '_widget_1734062123167', + 'saas_package': '_widget_1734062123168', + 'manage_model': '_widget_1734062123169', + + # 联系信息 + 'contacts': '_widget_1734062123170', + 'contact_number': '_widget_1734062123171', + 'contact_mobile': '_widget_1734062123172', + + # G月度统计 + 'g_month_count': '_widget_1734062123173', + 'g_month_percentage': '_widget_1734062123174', + + # 安装服务 + 'is_install_service': '_widget_1734062123175', + 'install_create_time': '_widget_1734062123176', + 'last_end_date': '_widget_1734062123177', + 'renew_date': '_widget_1734062123178', + + # 连锁信息 + 'is_chain_owner': '_widget_1734062123179', + 'group_org_count': '_widget_1734062123180', + + # 预警信息 + 'recent_bill_warning_days': '_widget_1734062123181', + 'g_change_flag_d': '_widget_1734062123182', + 'g_lost_warning_days': '_widget_1734062123183', + + # SaaS版本 + 'saas_edition_fmt': '_widget_1734062123184', + + # G标记月度(1-6月) + 'g_flag_1m': '_widget_1734062123185', + 'g_flag_2m': '_widget_1734062123186', + 'g_flag_3m': '_widget_1734062123187', + 'g_flag_4m': '_widget_1734062123188', + 'g_flag_5m': '_widget_1734062123189', + 'g_flag_6m': '_widget_1734062123190', + 'g_flag_day_count': '_widget_1734062123191', + + # 其他标记 + 'add_org_flag': '_widget_1734062123192', + 'pt': '_widget_1734062123193', + + # 门店属性 + 'org_size': '_widget_1734062123194', + 'qualification_type_fmt': '_widget_1734062123195', + 'business_scope_fmt': '_widget_1734062123196', + 'store_type_fmt': '_widget_1734062123197', + 'area': '_widget_1734062123198', + 'station_number': '_widget_1734062123199', + 'header_type_fmt': '_widget_1734062123200', + 'org_stage': '_widget_1734062123201', + + # 本月统计 + 'g_count_this_month': '_widget_1734062123202', + 'saas_customer_type': '_widget_1734062123203', + 'technician': '_widget_1734062123204', + 'tmall_maintain_service_status_desc': '_widget_1734062123205', + + # 日期字段(UTC格式) + 'date_fmt_date': '_widget_1749000071375', + 'saas_create_time_date': '_widget_1749000071377', + 'expiry_time_date': '_widget_1749000071382', + 'install_create_time_date': '_widget_1749000071384', + 'last_end_date_date': '_widget_1749000071389', + 'renew_date_date': '_widget_1749000071391', + + # 人员ID字段 + 'area_manager_staff_id': '_widget_1748496855779', + 'service_salesmen_staff_id': '_widget_1748496855778', + 'service_impl_principal_staff_id': '_widget_1748496855780', + 'technician_staff_id': '_widget_1751877712235', + } \ No newline at end of file diff --git a/test/客户资料表.ipynb b/test/客户资料表.ipynb new file mode 100644 index 0000000..b712cce --- /dev/null +++ b/test/客户资料表.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-12-04T09:43:46.485266Z", + "start_time": "2025-12-04T09:43:15.261034Z" + } + }, + "source": [ + "from datetime import datetime, timezone, timedelta, date, UTC\n", + "import holidays\n", + "from config import Config\n", + "import psycopg2\n", + "import pandas as pd\n", + "import pymysql\n", + "from api import API\n", + "from log_config import configure_task_logger, configure_error_task_logger\n", + "import time\n", + "\n", + "def get_ngv_details(days_back=1):\n", + " \"\"\"\n", + " 从固定的数据库中获取前几天的NGV明细。\n", + " 参数 `days_back` 表示相对于今天的天数偏移量,默认为1(即前一天)。\n", + " 返回包含NGV明细的pandas DataFrame。\n", + " \"\"\"\n", + " try:\n", + " # 获得连接\n", + " conn = Config.CONN_INFO\n", + " conn = psycopg2.connect(**conn)\n", + " cursor = conn.cursor()\n", + "\n", + " # 获取指定天数前的日期\n", + " now_time = datetime.now()\n", + " target_time = now_time + timedelta(days=-days_back)\n", + " target_date_id = int(target_time.strftime('%Y%m%d')) # 获取目标日期\n", + "\n", + " # sql语句查询\n", + " sql = f\"\"\"\n", + " SELECT * FROM \"public\".\"holo_ads_report_saas_profile_ngv_detail_d\" WHERE \"date_id\" = '{target_date_id}' ;\n", + " \"\"\"\n", + "\n", + " # 执行语句并获取结果集\n", + " cursor.execute(sql)\n", + " rows = cursor.fetchall()\n", + " all_fields = cursor.description\n", + "\n", + " # 执行结果转化为dataframe\n", + " col = [i[0] for i in all_fields]\n", + " data_NGV = pd.DataFrame(rows, columns=col)\n", + "\n", + " # 尝试自动解析日期时间字符串\n", + " time_format = \"%Y-%m-%d %H:%M:%S\"\n", + " if 'saas_create_time' in data_NGV.columns:\n", + " data_NGV['saas_create_time'] = pd.to_datetime(data_NGV['saas_create_time'], format=time_format,\n", + " errors='coerce')\n", + " data_NGV['saas_create_time'] = data_NGV['saas_create_time'].dt.strftime('%Y-%m-%d')\n", + "\n", + " # 关闭游标和连接\n", + " cursor.close()\n", + " conn.close()\n", + "\n", + " return data_NGV\n", + "\n", + " except Exception as e:\n", + " print(e)\n", + " return None\n", + "\n", + "data_NGV_j = get_ngv_details(days_back=1)\n", + "data_NGV_j1 = get_ngv_details(days_back=2)\n", + "\n", + "data_NGV_j.to_csv('data_NGV_j.csv', index=False)\n", + "data_NGV_j1.to_csv('data_NGV_j1.csv', index=False)" + ], + "outputs": [], + "execution_count": 6 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/test/异常回访补拍.py b/test/异常回访补拍.py new file mode 100644 index 0000000..abb83cc --- /dev/null +++ b/test/异常回访补拍.py @@ -0,0 +1,500 @@ +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 +import traceback + +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", + "filter": {"rel": "and", + "cond": [{"field": "flowState", "type": "flowstate", "method": "eq", "value": [0]}]}} + 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") + + all_data = [] + try: + self.load_all_data() + + data = pd.read_excel(fr"C:\Users\zy187\Desktop\钉钉文件\异常待办数据(异常的有10条).xlsx", sheet_name="Sheet2") + self.data_yichang_S = pd.DataFrame() if data is None or data.empty else data.astype(str) + self.index = self.build_index(self.json_list) + + logger.info("开始运行SaaS异常回访") + if self.data_yichang_S.empty: + logger.info("未获取到数据或数据为空") + common_module.send_task_status(task_start_time, "异常服务待办派发") + return + + 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) + + error_data = [] + + 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']: + 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") + # 获取上线日期(文本)# 202512.3改为开户日 + create_date = NGV_Data.get("_widget_1734062123081") + 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']}") + + logger.info(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 + } + all_data.append(routine_follow_up_payload) + + res = api_instance.data_batch_create(routine_follow_up_payload) + logger.info(f"创建结果:{res}") + + except Exception as e : + error_task_logger.error(f"异常服务待办派发执行时发生异常: {e}") + error_data.append(row) + pass + error_df = pd.DataFrame(error_data) + error_df.to_csv(os.path.join(output_dir, "异常派发错误数据.csv")) + common_module.send_task_error(task_start_time = task_start_time,task_name= "异常服务待办派发",error_message="详情见失败文件", df = error_df) + # ndf = pd.DataFrame(all_data) + # ndf.to_csv(os.path.join(output_dir, "异常派发.csv")) + 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() diff --git a/test/异常待办数据导出.py b/test/异常待办数据导出.py index 2e7f730..9be31ae 100644 --- a/test/异常待办数据导出.py +++ b/test/异常待办数据导出.py @@ -19,7 +19,7 @@ error_task_logger = configure_error_task_logger() output_dir = "output" # 设置输出目录 os.makedirs(output_dir, exist_ok=True) -data = common_module.get_yichang_details(days_back=3) +data = common_module.get_yichang_details(days_back=1) df = pd.DataFrame(data) df.to_excel(os.path.join(output_dir, "异常待办数据.xlsx"), index=False)