trae-git
This commit is contained in:
@@ -7,3 +7,12 @@
|
||||
out
|
||||
gen
|
||||
.logs
|
||||
.log
|
||||
.csv
|
||||
.excel
|
||||
.xlsx
|
||||
output/
|
||||
__pycache__/
|
||||
.env
|
||||
.vscode/
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,323 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-11-05T09:03:45.525420Z",
|
||||
"start_time": "2025-11-05T09:03:44.127181Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"# -*- coding: utf-8 -*-\n",
|
||||
"import pandas as pd\n",
|
||||
"import datetime\n",
|
||||
"from config import Config\n",
|
||||
"from api import API\n",
|
||||
"from back_ground_module import CommonModule\n",
|
||||
"from log_config import configure_task_logger, configure_error_task_logger\n",
|
||||
"import time\n",
|
||||
"timestamp = time.time() # 返回 float,单位:秒\n",
|
||||
"\n",
|
||||
"logger = configure_task_logger()\n",
|
||||
"# 获取已经配置好的错误任务日志记录器\n",
|
||||
"error_task_logger = configure_error_task_logger()\n",
|
||||
"start_time = datetime.datetime.now()\n",
|
||||
"api_instance = API()\n",
|
||||
"common_module = CommonModule()\n",
|
||||
"output_dir = \"output\" # 设置输出目录\n",
|
||||
"# 创建输出目录(如果不存在)\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.makedirs(output_dir, exist_ok=True)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class UpdateNGVData:\n",
|
||||
" \"\"\"NGV数据每日新增\"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self):\n",
|
||||
" self.staff_id_list = None\n",
|
||||
" self.field_mapping = {}\n",
|
||||
" self.fields()\n",
|
||||
"\n",
|
||||
" def load_all_data(self):\n",
|
||||
" # 获取简道云员工id\n",
|
||||
" payload = {\"api_key\": \"6694d3c4fcb69ca9a111a6c4\",\n",
|
||||
" \"entry_id\": \"6769204a1902c9341340a1bc\",\n",
|
||||
" }\n",
|
||||
" staff_id = api_instance.entry_data_list(payload)\n",
|
||||
" self.staff_id_list = staff_id.get(\"data\") # api请求格式,将数据封装在data字典里\n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def get_staff_id(row_item, name):\n",
|
||||
" \"\"\"辅助函数,用于获取员工ID\"\"\"\n",
|
||||
" if str(row_item[\"_widget_1734942794144\"]) == str(name): # 检查姓名是否匹配\n",
|
||||
" return row_item[\"_widget_1734942794145\"] # 返回员工ID\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
" def main(self):\n",
|
||||
" task_start_time = datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n",
|
||||
" try:\n",
|
||||
" self.load_all_data()\n",
|
||||
" logger.info(f\"数据加载完成\")\n",
|
||||
" #\n",
|
||||
" # data_NGV_j = common_module.get_ngv_details(days_back=1)\n",
|
||||
" # data_NGV_j1 = common_module.get_ngv_details(days_back=2)\n",
|
||||
" timestamp = time.time() # 返回 float,单位:秒\n",
|
||||
" #\n",
|
||||
" # data_NGV_j.to_csv(os.path.join(output_dir, f\"{timestamp}up_NGV_j.csv\"))\n",
|
||||
" # data_NGV_j1.to_csv(os.path.join(output_dir, f\"{timestamp}up_NGV_j1.csv\"))\n",
|
||||
" #\n",
|
||||
" # # 找出在 data_NGV_j 中存在但在 data_NGV_j1 中不存在的 data_id\n",
|
||||
" # unique_data_ids = data_NGV_j[~data_NGV_j['org_code'].isin(data_NGV_j1['org_code'])]\n",
|
||||
" #\n",
|
||||
" # # 创建一个新的 DataFrame 保存这些唯一的 data_id 及其对应的数据\n",
|
||||
" # new_df = data_NGV_j[data_NGV_j['org_code'].isin(unique_data_ids['org_code'])]\n",
|
||||
" #\n",
|
||||
" # # 对 new_df 进行进一步的过滤,只保留 org_type 为 \"一般\" 的记录\n",
|
||||
" # data_NGV_j = data_NGV_j[data_NGV_j['org_type'] == '一般']\n",
|
||||
" # data_NGV_j1 = data_NGV_j1[data_NGV_j1['org_type'] == '一般']\n",
|
||||
" # filtered_df = new_df[new_df['org_type'] == '一般']\n",
|
||||
" # 默认未删除\n",
|
||||
" filtered_df = pd.read_excel(r\"C:\\Users\\zy187\\Downloads\\异常服务跟进待办_20251105170140.xlsx\",sheet_name=\"Sheet1\")\n",
|
||||
" filtered_df['源ngv是否已删除'] = '未删除'\n",
|
||||
"\n",
|
||||
" # 日期字段转换为日期格式\n",
|
||||
" time_columns = ['date_fmt', 'saas_create_time', 'expiry_time', 'install_create_time', \"last_end_date\",\n",
|
||||
" \"renew_date\"]\n",
|
||||
" new_filtered_df = filtered_df.copy() # 复制df,以调整时间\n",
|
||||
" for col in time_columns:\n",
|
||||
" # 1. 转换为datetime类型(带错误处理)\n",
|
||||
" # 使用.loc安全赋值\n",
|
||||
" new_filtered_df[col] = pd.to_datetime(filtered_df[col], errors='coerce', utc=False)\n",
|
||||
"\n",
|
||||
" # 2. 优化后的时区转换(高效向量化操作)\n",
|
||||
" filtered_df[col + '_date'] = (\n",
|
||||
" new_filtered_df[col]\n",
|
||||
" # 本地化为北京时间(东八区)\n",
|
||||
" .dt.tz_localize('Asia/Shanghai', ambiguous='infer', nonexistent='NaT')\n",
|
||||
" # 转换为UTC时区\n",
|
||||
" .dt.tz_convert('UTC')\n",
|
||||
" # 格式化为ISO8601字符串\n",
|
||||
" .dt.strftime('%Y-%m-%dT%H:%M:%SZ')\n",
|
||||
" )\n",
|
||||
" logger.info(f\"时间转换完成\")\n",
|
||||
"\n",
|
||||
" # 人员字段转换为人员字段\n",
|
||||
" staff_columns = ['area_manager', 'service_impl_principal', \"service_salesmen\", \"technician\"]\n",
|
||||
" # 将员工列表转为DataFrame\n",
|
||||
" # 三重循环临时方案(确保可写入)\n",
|
||||
" for col in staff_columns:\n",
|
||||
" staff_ids = []\n",
|
||||
" for _, row in filtered_df.iterrows():\n",
|
||||
" matched = False\n",
|
||||
" for staff in self.staff_id_list:\n",
|
||||
" if str(staff['_widget_1734942794144']) == str(row[col]):\n",
|
||||
" staff_ids.append(staff['_widget_1734942794145'])\n",
|
||||
" matched = True\n",
|
||||
" break\n",
|
||||
" if not matched:\n",
|
||||
" staff_ids.append(None)\n",
|
||||
" filtered_df[col + \"_staff_id\"] = staff_ids\n",
|
||||
" logger.info(f\"人员转换完成\")\n",
|
||||
"\n",
|
||||
" # filtered_df.to_csv(r\"D:\\Idea Project\\SaaS_V1.3\\back_ground_module\\output\\NGV.csv\")\n",
|
||||
"\n",
|
||||
" # 生成包含所有行转换后的字典列表\n",
|
||||
" # all_data = [self.row_to_dict(row, self.field_mapping) for index, row in data_NGV_j1.iterrows()] # 前两天的全部数据\n",
|
||||
" # all_data = [self.row_to_dict(row, self.field_mapping) for index, row in data_NGV_j.iterrows()] # 前一天的全部数据\n",
|
||||
" all_data = [self.row_to_dict(row, self.field_mapping) for index, row in filtered_df.iterrows()] # 增量数据\n",
|
||||
"\n",
|
||||
" try:\n",
|
||||
" filtered_df.to_csv(os.path.join(output_dir, f\"{timestamp}NGV.csv\"))\n",
|
||||
" except Exception as e:\n",
|
||||
" error_task_logger.error(f\"NGV过滤后数据保存异常: {e}\")\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
" #\n",
|
||||
" data = {'api_key': Config.SaaS_Tasks_APP_ID, 'entry_id': Config.NGV_TASKS_ENTRY_ID, \"data_list\": all_data,\n",
|
||||
" \"is_start_trigger\": \"true\"}\n",
|
||||
"\n",
|
||||
" result = api_instance.entry_data_batch_create(data)\n",
|
||||
" logger.info(f\"数据已推送:{result}\")\n",
|
||||
" # result_str = str(result)\n",
|
||||
" # print(result_str[:500])\n",
|
||||
"\n",
|
||||
" # 保存到Excel文件\n",
|
||||
" # output_path = r'D:\\Idea Project\\F6+宜搭+其它(1)\\new\\文件输出\\ngv明细1.xlsx'\n",
|
||||
" # filtered_df.to_excel(output_path, index=False)\n",
|
||||
" # data_NGV_j1.to_excel( r'D:\\Idea Project\\F6+宜搭+其它(1)\\new\\文件输出\\ngv明细j1.xlsx', index=False)\n",
|
||||
" # data_NGV_j.to_excel( r'D:\\Idea Project\\F6+宜搭+其它(1)\\new\\文件输出\\ngv明细j.xlsx', index=False)\n",
|
||||
" # new_df.to_excel(r'D:\\Idea Project\\F6+宜搭+其它(1)\\new\\文件输出\\ngv明细ndf.xlsx', index=False)\n",
|
||||
"\n",
|
||||
" common_module.send_task_status(task_start_time, \"NGV新增数据\")\n",
|
||||
" logger.info(f\"任务完成。\")\n",
|
||||
" except Exception as e:\n",
|
||||
" error_task_logger.error(f\"任务执行时发生异常: {e}\")\n",
|
||||
" # common_module.send_task_error(task_start_time, \"NGV新增数据\", str(e))\n",
|
||||
"\n",
|
||||
" @staticmethod\n",
|
||||
" def row_to_dict(row, field_mapping):\n",
|
||||
" \"\"\"将一行数据转换为指定格式的字典,并确保时间类型可JSON序列化\"\"\"\n",
|
||||
" result = {}\n",
|
||||
" for col_name, widget_id in field_mapping.items():\n",
|
||||
" if col_name in row:\n",
|
||||
" value = row[col_name]\n",
|
||||
" if pd.isna(value):\n",
|
||||
" clean_value = None\n",
|
||||
" elif isinstance(value, (pd.Timestamp, pd.Timedelta)):\n",
|
||||
" clean_value = value.isoformat() # 或 str(value)\n",
|
||||
" elif hasattr(value, 'strftime'): # 兼容 datetime.datetime\n",
|
||||
" clean_value = value.strftime('%Y-%m-%dT%H:%M:%SZ')\n",
|
||||
" else:\n",
|
||||
" clean_value = value\n",
|
||||
" result[widget_id] = {\"value\": clean_value}\n",
|
||||
" return result\n",
|
||||
"\n",
|
||||
" def fields(self):\n",
|
||||
" self.field_mapping = dict(date_id='_widget_1734062123065', date_fmt='_widget_1734062123066',\n",
|
||||
" id_own_group='_widget_1734062123067', group_name='_widget_1734062123068',\n",
|
||||
" id_own_org='_widget_1734062123069', org_name='_widget_1734062123070',\n",
|
||||
" org_code='_widget_1734062123071', group_grade='_widget_1734062123072',\n",
|
||||
" org_type='_widget_1734062123073', org_status='_widget_1734062123074',\n",
|
||||
" saas_version='_widget_1734062123075', is_wechat='_widget_1734062123076',\n",
|
||||
" is_mini_app='_widget_1734062123077', is_wx_shop='_widget_1734062123078',\n",
|
||||
" is_camera_service='_widget_1734062123079',\n",
|
||||
" is_maintenance_service='_widget_1734062123080',\n",
|
||||
" saas_create_time='_widget_1734062123081', expiry_time='_widget_1734062123082',\n",
|
||||
" saas_use_days='_widget_1734062123083', saas_use_year='_widget_1734062123084',\n",
|
||||
" is_main_org='_widget_1734062123085', license_code='_widget_1734062123086',\n",
|
||||
" license_name='_widget_1734062123087', org_crm_id='_widget_1734062123088',\n",
|
||||
" province_id='_widget_1734062123089', province_name='_widget_1734062123090',\n",
|
||||
" city_id='_widget_1734062123091', city_name='_widget_1734062123092',\n",
|
||||
" area_id='_widget_1734062123093', area_name='_widget_1734062123094',\n",
|
||||
" region_name='_widget_1734062123095', region_short_name='_widget_1734062123096',\n",
|
||||
" branch_name='_widget_1734062123097', carzone_store_id='_widget_1734062123098',\n",
|
||||
" carzone_store_name='_widget_1734062123099',\n",
|
||||
" customer_carzone_id='_widget_1734062123100', salesmen='_widget_1734062123101',\n",
|
||||
" area_manager='_widget_1734062123102', service_salesmen='_widget_1734062123103',\n",
|
||||
" impl_principal='_widget_1734062123104',\n",
|
||||
" service_impl_principal='_widget_1734062123105',\n",
|
||||
" active_user_count='_widget_1734062123106', active_user_type='_widget_1734062123107',\n",
|
||||
" limit_user_count='_widget_1734062123108', limit_user_type='_widget_1734062123109',\n",
|
||||
" is_n='_widget_1734062123110', is_g='_widget_1734062123111',\n",
|
||||
" is_v='_widget_1734062123112', is_visited='_widget_1734062123113',\n",
|
||||
" is_active='_widget_1734062123114', active_status_fmt='_widget_1734062123115',\n",
|
||||
" bill_count_last_30_day='_widget_1734062123116',\n",
|
||||
" bill_day_count_last_30_day='_widget_1734062123117',\n",
|
||||
" bill_day_count_this_month='_widget_1734062123118',\n",
|
||||
" bill_count_last_7_day='_widget_1734062123119',\n",
|
||||
" bill_day_count_last_7_day='_widget_1734062123120', pv_count='_widget_1734062123121',\n",
|
||||
" uv_count='_widget_1734062123122', bill_count_1d='_widget_1734062123123',\n",
|
||||
" bill_count_2d='_widget_1734062123124', bill_count_3d='_widget_1734062123125',\n",
|
||||
" bill_count_4d='_widget_1734062123126', bill_count_5d='_widget_1734062123127',\n",
|
||||
" bill_count_6d='_widget_1734062123128', bill_count_7d='_widget_1734062123129',\n",
|
||||
" bill_count_8d='_widget_1734062123130', bill_count_9d='_widget_1734062123131',\n",
|
||||
" bill_count_10d='_widget_1734062123132', bill_count_11d='_widget_1734062123133',\n",
|
||||
" bill_count_12d='_widget_1734062123134', bill_count_13d='_widget_1734062123135',\n",
|
||||
" bill_count_14d='_widget_1734062123136', bill_count_15d='_widget_1734062123137',\n",
|
||||
" bill_count_16d='_widget_1734062123138', bill_count_17d='_widget_1734062123139',\n",
|
||||
" bill_count_18d='_widget_1734062123140', bill_count_19d='_widget_1734062123141',\n",
|
||||
" bill_count_20d='_widget_1734062123142', bill_count_21d='_widget_1734062123143',\n",
|
||||
" bill_count_22d='_widget_1734062123144', bill_count_23d='_widget_1734062123145',\n",
|
||||
" bill_count_24d='_widget_1734062123146', bill_count_25d='_widget_1734062123147',\n",
|
||||
" bill_count_26d='_widget_1734062123148', bill_count_27d='_widget_1734062123149',\n",
|
||||
" bill_count_28d='_widget_1734062123150', bill_count_29d='_widget_1734062123151',\n",
|
||||
" bill_count_30d='_widget_1734062123152', bill_count_31d='_widget_1734062123153',\n",
|
||||
" etl_time='_widget_1734062123154',\n",
|
||||
" maintain_bill_count_last_30_day='_widget_1734062123155',\n",
|
||||
" washing_bill_count_last_30_day='_widget_1734062123156',\n",
|
||||
" maintain_bill_day_count_last_30_day='_widget_1734062123157',\n",
|
||||
" washing_bill_day_count_last_30_day='_widget_1734062123158',\n",
|
||||
" retail_bill_count_last_30_day='_widget_1734062123159',\n",
|
||||
" retail_bill_day_count_last_30_day='_widget_1734062123160',\n",
|
||||
" purchase_bill_count_last_30_day='_widget_1734062123161',\n",
|
||||
" purchase_bill_day_count_last_30_day='_widget_1734062123162',\n",
|
||||
" card_bill_count_last_30_day='_widget_1734062123163',\n",
|
||||
" card_bill_day_count_last_30_day='_widget_1734062123164',\n",
|
||||
" gd_sales_bill_count_last_30_day='_widget_1734062123165',\n",
|
||||
" gd_sales_bill_day_count_last_30_day='_widget_1734062123166',\n",
|
||||
" g_change_flag='_widget_1734062123167', saas_package='_widget_1734062123168',\n",
|
||||
" manage_model='_widget_1734062123169', contacts='_widget_1734062123170',\n",
|
||||
" contact_number='_widget_1734062123171', contact_mobile='_widget_1734062123172',\n",
|
||||
" g_month_count='_widget_1734062123173', g_month_percentage='_widget_1734062123174',\n",
|
||||
" is_install_service='_widget_1734062123175',\n",
|
||||
" install_create_time='_widget_1734062123176', last_end_date='_widget_1734062123177',\n",
|
||||
" renew_date='_widget_1734062123178', is_chain_owner='_widget_1734062123179',\n",
|
||||
" group_org_count='_widget_1734062123180',\n",
|
||||
" recent_bill_warning_days='_widget_1734062123181',\n",
|
||||
" g_change_flag_d='_widget_1734062123182', g_lost_warning_days='_widget_1734062123183',\n",
|
||||
" saas_edition_fmt='_widget_1734062123184', g_flag_1m='_widget_1734062123185',\n",
|
||||
" g_flag_2m='_widget_1734062123186', g_flag_3m='_widget_1734062123187',\n",
|
||||
" g_flag_4m='_widget_1734062123188', g_flag_5m='_widget_1734062123189',\n",
|
||||
" g_flag_6m='_widget_1734062123190', g_flag_day_count='_widget_1734062123191',\n",
|
||||
" add_org_flag='_widget_1734062123192', pt='_widget_1734062123193',\n",
|
||||
" org_size='_widget_1734062123194', qualification_type_fmt='_widget_1734062123195',\n",
|
||||
" business_scope_fmt='_widget_1734062123196', store_type_fmt='_widget_1734062123197',\n",
|
||||
" area='_widget_1734062123198', station_number='_widget_1734062123199',\n",
|
||||
" header_type_fmt='_widget_1734062123200', org_stage='_widget_1734062123201',\n",
|
||||
" g_count_this_month='_widget_1734062123202',\n",
|
||||
" saas_customer_type='_widget_1734062123203', technician='_widget_1734062123204',\n",
|
||||
" tmall_maintain_service_status_desc='_widget_1734062123205',\n",
|
||||
" date_fmt_date='_widget_1749000071375',\n",
|
||||
" area_manager_staff_id='_widget_1748496855779',\n",
|
||||
" service_impl_principal_staff_id=\"_widget_1748496855780\",\n",
|
||||
" service_salesmen_staff_id=\"_widget_1748496855778\",\n",
|
||||
" technician_staff_id=\"_widget_1751877712235\",\n",
|
||||
" saas_create_time_date=\"_widget_1749000071377\",\n",
|
||||
" expiry_time_date=\"_widget_1749000071382\",\n",
|
||||
" install_create_time_date=\"_widget_1749000071384\",\n",
|
||||
" last_end_date_date=\"_widget_1749000071389\", renew_date_date=\"_widget_1749000071391\"\n",
|
||||
" , 源NGV是否已删除=\"_widget_1754285499851\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"if __name__ == '__main__':\n",
|
||||
" start = UpdateNGVData()\n",
|
||||
" start.main()\n"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"已获取 100 条数据\n",
|
||||
"已获取 146 条数据\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001B[92m2025-11-05 17:03:45,497 - api.py - task_logger - INFO - 获取了146条数据\u001B[0m\n",
|
||||
"\u001B[92m2025-11-05 17:03:45,498 - 4224831806.py - task_logger - INFO - 数据加载完成\u001B[0m\n",
|
||||
"\u001B[91m2025-11-05 17:03:45,523 - 4224831806.py - error_task_logger - ERROR - 任务执行时发生异常: 'date_fmt'\u001B[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def keep_latest_by_time(df: pd.DataFrame, store_col: str, time_col: str) -> pd.DataFrame:
|
||||
if store_col not in df.columns:
|
||||
raise ValueError(f"缺少列: {store_col}")
|
||||
if time_col not in df.columns:
|
||||
raise ValueError(f"缺少列: {time_col}")
|
||||
|
||||
working = df.copy()
|
||||
working[store_col] = working[store_col].astype(str).fillna("")
|
||||
working[time_col] = working[time_col].astype(str).fillna("")
|
||||
|
||||
working["_parsed_time"] = pd.to_datetime(working[time_col], errors="coerce")
|
||||
working["_row_order"] = range(len(working))
|
||||
|
||||
working = working.sort_values(
|
||||
by=["_parsed_time", "_row_order"],
|
||||
ascending=[True, True],
|
||||
kind="mergesort",
|
||||
na_position="first",
|
||||
)
|
||||
latest = working.groupby(store_col, sort=False).tail(1)
|
||||
latest = latest.drop(columns=["_parsed_time", "_row_order"]).reset_index(drop=True)
|
||||
return latest
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--input", "-i", required=False, help="输入 Excel 路径(.xlsx)")
|
||||
parser.add_argument("--output", "-o", required=False, help="输出 Excel 路径(.xlsx)")
|
||||
parser.add_argument("--sheet", default="需要保留一条", help="Sheet 名称")
|
||||
parser.add_argument("--store-col", default="门店编码", help="门店编码列名")
|
||||
parser.add_argument("--time-col", default="创建时间", help="创建时间列名")
|
||||
parser.add_argument("--codes-output", required=False, help="可选:输出门店编码清单(.txt 或 .csv)")
|
||||
parser.add_argument("--demo", action="store_true", help="运行内置示例(不读写 Excel)")
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = build_parser().parse_args()
|
||||
|
||||
if args.demo:
|
||||
demo_df = pd.DataFrame(
|
||||
[
|
||||
{"门店编码": "A001", "创建时间": "2026-03-01 10:00:00", "其他": "x"},
|
||||
{"门店编码": "A001", "创建时间": "2026-03-05 09:00:00", "其他": "y"},
|
||||
{"门店编码": "B002", "创建时间": "2026/03/02 12:00", "其他": "m"},
|
||||
{"门店编码": "B002", "创建时间": "无效时间", "其他": "n"},
|
||||
]
|
||||
)
|
||||
result = keep_latest_by_time(demo_df, store_col=args.store_col, time_col=args.time_col)
|
||||
print(result)
|
||||
print("门店编码:", ",".join(result[args.store_col].astype(str).tolist()))
|
||||
return 0
|
||||
|
||||
if not args.input:
|
||||
raise SystemExit("缺少参数 --input")
|
||||
|
||||
input_path = Path(args.input).expanduser().resolve()
|
||||
if not input_path.exists():
|
||||
raise SystemExit(f"输入文件不存在: {input_path}")
|
||||
|
||||
df = pd.read_excel(input_path, sheet_name=args.sheet, dtype=str).fillna("")
|
||||
latest = keep_latest_by_time(df, store_col=args.store_col, time_col=args.time_col)
|
||||
|
||||
output_path = Path(args.output).expanduser().resolve() if args.output else None
|
||||
if output_path is None:
|
||||
output_path = input_path.with_name(f"{input_path.stem}_保留最新.xlsx")
|
||||
|
||||
with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
|
||||
latest.to_excel(writer, sheet_name="保留最新", index=False)
|
||||
|
||||
store_codes = latest[args.store_col].astype(str).tolist()
|
||||
print(f"保留行数: {len(latest)}")
|
||||
print(f"门店编码数量: {len(store_codes)}")
|
||||
|
||||
if args.codes_output:
|
||||
codes_path = Path(args.codes_output).expanduser().resolve()
|
||||
if codes_path.suffix.lower() == ".csv":
|
||||
pd.DataFrame({args.store_col: store_codes}).to_csv(codes_path, index=False, encoding="utf-8-sig")
|
||||
else:
|
||||
codes_path.write_text("\n".join(store_codes), encoding="utf-8")
|
||||
|
||||
print(f"输出文件: {output_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+51
-100
@@ -1,13 +1,7 @@
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import requests
|
||||
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
from api import API
|
||||
from back_ground_module import CommonModule
|
||||
import pandas as pd
|
||||
@@ -224,10 +218,6 @@ class NewExceptionTask:
|
||||
"""根据省市区派发给异常回访客服"""
|
||||
# try:
|
||||
customer_service_info = self.find_customer_service(province_name, city_name, area_name, index)
|
||||
if not isinstance(customer_service_info, dict):
|
||||
logger.warning(
|
||||
f"【省市区未匹配到客服】省={province_name} 市={city_name} 区={area_name} raw={customer_service_info}")
|
||||
return None
|
||||
customer_service = customer_service_info.get('_widget_1734677164870', {}).get('username') # 异常回访客服
|
||||
return customer_service
|
||||
# except Exception as e:
|
||||
@@ -242,18 +232,7 @@ class NewExceptionTask:
|
||||
try:
|
||||
self.load_all_data()
|
||||
|
||||
data = None
|
||||
for days_back in range(1, 15):
|
||||
target_date_id = int(
|
||||
(datetime.datetime.now() - datetime.timedelta(days=days_back)).strftime("%Y%m%d")
|
||||
)
|
||||
logger.info(f"尝试获取异常明细:pt={target_date_id} days_back={days_back}")
|
||||
data = common_module.get_yichang_details(days_back=days_back)
|
||||
if data is not None and not data.empty:
|
||||
logger.info(f"获取异常明细成功:pt={target_date_id} rows={len(data)} days_back={days_back}")
|
||||
break
|
||||
logger.info(f"异常明细为空:pt={target_date_id} days_back={days_back}")
|
||||
|
||||
data = common_module.get_yichang_details(days_back=1)
|
||||
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)
|
||||
|
||||
@@ -274,24 +253,6 @@ class NewExceptionTask:
|
||||
# 对整个DataFrame的所有列应用替换函数
|
||||
data_yichang = data_yichang.apply(replace_values)
|
||||
error_data = []
|
||||
|
||||
def extract_widget_value(widget_value):
|
||||
if widget_value is None:
|
||||
return None
|
||||
if isinstance(widget_value, dict):
|
||||
return widget_value.get("value")
|
||||
return widget_value
|
||||
|
||||
existing_org_codes = set()
|
||||
for exception_service in self.exception_service_todo:
|
||||
try:
|
||||
existing_value = extract_widget_value(exception_service.get("_widget_1748241895842"))
|
||||
if existing_value is not None and str(existing_value).strip() != "":
|
||||
existing_org_codes.add(str(existing_value).strip())
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
dispatched_org_codes = set()
|
||||
for index_num, row in data_yichang.iterrows(): # 对过滤后的每一条进行派发
|
||||
try:
|
||||
# 每次循环前清空省市区变量
|
||||
@@ -299,17 +260,15 @@ class NewExceptionTask:
|
||||
city_name = None
|
||||
area_name = None
|
||||
|
||||
org_code = str(row.get("org_code", "")).strip()
|
||||
if not org_code:
|
||||
logger.warning(f"数据缺少门店编码,跳过。index={index_num}")
|
||||
continue
|
||||
is_pass = False
|
||||
for exception_service in self.exception_service_todo:
|
||||
# 通过查询筛选进行中的逻辑
|
||||
if exception_service['_widget_1748241895842'] == row['org_code']:
|
||||
is_pass = True
|
||||
break
|
||||
|
||||
if org_code in dispatched_org_codes:
|
||||
logger.info(f"同一轮数据中门店编码重复,跳过派发。org_code={org_code} index={index_num}")
|
||||
continue
|
||||
|
||||
if org_code in existing_org_codes:
|
||||
logger.info(f"已存在待办,跳过派发。org_code={org_code} index={index_num}")
|
||||
if is_pass:
|
||||
logger.info(f"已存在待办,跳过该条记录: {row}")
|
||||
continue
|
||||
|
||||
payload_dict = {}
|
||||
@@ -361,7 +320,7 @@ class NewExceptionTask:
|
||||
# 获取关联数据
|
||||
for NGV_Data in self.NGV_data_list:
|
||||
# NGV_Data = NGV_Data.get("data")
|
||||
if org_code == str(NGV_Data.get("_widget_1734062123071", "")).strip(): # 门店编码
|
||||
if row["org_code"] == NGV_Data.get("_widget_1734062123071"): # 门店编码
|
||||
NGV_data_id = NGV_Data.get("_id")
|
||||
|
||||
# 如果需要从 NGV_data_list 获取省市区信息
|
||||
@@ -381,8 +340,6 @@ class NewExceptionTask:
|
||||
create_date = NGV_Data.get("_widget_1734062123081")
|
||||
# 获取暂停派发日期
|
||||
stop_date = NGV_Data.get("_widget_1772610343227", None)
|
||||
logger.info(
|
||||
f"【暂停派发字段】org_code={org_code} stop_date={stop_date} type={type(stop_date).__name__}")
|
||||
break # 找到匹配的数据后退出循环
|
||||
|
||||
# 定义可能的日期格式(灵活应对不同格式)
|
||||
@@ -394,13 +351,13 @@ class NewExceptionTask:
|
||||
]
|
||||
|
||||
if stop_date:
|
||||
# 解析暂停派发日期
|
||||
parsed_stop_date = None
|
||||
stop_value = extract_widget_value(stop_date)
|
||||
local_tz = datetime.timezone(datetime.timedelta(hours=8))
|
||||
now_local = datetime.datetime.now(local_tz)
|
||||
|
||||
stop_value = stop_date.get("value") if isinstance(stop_date, dict) else stop_date
|
||||
if isinstance(stop_value, (int, float)):
|
||||
parsed_stop_date = datetime.datetime.fromtimestamp(stop_value / 1000, tz=local_tz)
|
||||
parsed_stop_date = datetime.datetime.fromtimestamp(
|
||||
stop_value / 1000, tz=datetime.timezone.utc
|
||||
).replace(tzinfo=None)
|
||||
elif isinstance(stop_value, str):
|
||||
stop_str = stop_value.strip()
|
||||
iso_candidate = stop_str[:-1] + "+00:00" if stop_str.endswith("Z") else stop_str
|
||||
@@ -410,25 +367,25 @@ class NewExceptionTask:
|
||||
iso_dt = None
|
||||
|
||||
if iso_dt is not None:
|
||||
parsed_stop_date = iso_dt.replace(tzinfo=local_tz) if iso_dt.tzinfo is None else iso_dt.astimezone(local_tz)
|
||||
parsed_stop_date = iso_dt.astimezone(datetime.timezone.utc).replace(tzinfo=None) if iso_dt.tzinfo else iso_dt
|
||||
else:
|
||||
for fmt in date_formats:
|
||||
try:
|
||||
parsed_stop_date = datetime.datetime.strptime(stop_str, fmt).replace(
|
||||
tzinfo=local_tz)
|
||||
parsed_stop_date = datetime.datetime.strptime(stop_str, fmt)
|
||||
logger.debug(f"使用格式 {fmt} 成功解析暂停派发日期: {parsed_stop_date}")
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if parsed_stop_date is None:
|
||||
logger.warning(
|
||||
f"【暂停派发解析失败】org_code={org_code} stop_value={stop_value} type={type(stop_value).__name__}")
|
||||
else:
|
||||
logger.info(
|
||||
f"【暂停派发校验】org_code={org_code} now={now_local} stop_until={parsed_stop_date}")
|
||||
if now_local < parsed_stop_date:
|
||||
logger.info(
|
||||
f"【暂停派发生效】org_code={org_code} 当前时间未到暂停派发截止时间,跳过派发")
|
||||
if parsed_stop_date:
|
||||
# 获取当前UTC时间
|
||||
current_utc_time = datetime.datetime.utcnow()
|
||||
logger.debug(f"当前UTC时间: {current_utc_time}")
|
||||
logger.debug(f"暂停派发日期: {parsed_stop_date}")
|
||||
|
||||
# 比较时间
|
||||
if current_utc_time < parsed_stop_date:
|
||||
logger.info(f"当前UTC时间低于暂停派发日期,跳过派发")
|
||||
continue
|
||||
|
||||
# 判断门店原因
|
||||
@@ -439,36 +396,37 @@ class NewExceptionTask:
|
||||
if create_exception == "否":
|
||||
continue
|
||||
# 新增:检查 create_date_str 是否存在且有效
|
||||
create_date_value = extract_widget_value(create_date)
|
||||
create_date_value = create_date.get("value") if isinstance(create_date, dict) else create_date
|
||||
if not create_date_value:
|
||||
logger.warning("上线日期为空,跳过该记录")
|
||||
continue
|
||||
|
||||
parsed_date = None
|
||||
for fmt in date_formats:
|
||||
if isinstance(create_date_value, (int, float)):
|
||||
local_tz = datetime.timezone(datetime.timedelta(hours=8))
|
||||
parsed_date = datetime.datetime.fromtimestamp(create_date_value / 1000, tz=local_tz).date()
|
||||
elif isinstance(create_date_value, str):
|
||||
create_str = create_date_value.strip()
|
||||
iso_candidate = create_str[:-1] + "+00:00" if create_str.endswith("Z") else create_str
|
||||
try:
|
||||
if isinstance(create_date_value, (int, float)):
|
||||
local_tz = datetime.timezone(datetime.timedelta(hours=8))
|
||||
parsed_date = datetime.datetime.fromtimestamp(create_date_value / 1000, tz=local_tz).date()
|
||||
else:
|
||||
create_str = str(create_date_value).strip()
|
||||
iso_candidate = create_str[:-1] + "+00:00" if create_str.endswith("Z") else create_str
|
||||
try:
|
||||
iso_dt = datetime.datetime.fromisoformat(iso_candidate)
|
||||
except ValueError:
|
||||
iso_dt = None
|
||||
if iso_dt is not None:
|
||||
parsed_date = (iso_dt.date() if iso_dt.tzinfo is None else iso_dt.astimezone(datetime.timezone(datetime.timedelta(hours=8))).date())
|
||||
else:
|
||||
parsed_date = datetime.datetime.strptime(create_str, fmt).date()
|
||||
logger.debug(f"使用格式 {fmt} 成功解析日期: {parsed_date}")
|
||||
break
|
||||
iso_dt = datetime.datetime.fromisoformat(iso_candidate)
|
||||
except ValueError:
|
||||
continue
|
||||
iso_dt = None
|
||||
|
||||
if iso_dt is not None:
|
||||
local_tz = datetime.timezone(datetime.timedelta(hours=8))
|
||||
parsed_date = iso_dt.date() if iso_dt.tzinfo is None else iso_dt.astimezone(local_tz).date()
|
||||
else:
|
||||
for fmt in date_formats:
|
||||
try:
|
||||
parsed_date = datetime.datetime.strptime(create_str, fmt).date()
|
||||
logger.debug(f"使用格式 {fmt} 成功解析日期: {parsed_date}")
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if parsed_date is None:
|
||||
logger.error(
|
||||
f"无法解析上线日期: '{create_date_value}',支持的格式: %Y-%m-%d, %Y-%m-%d %H:%M:%S 等")
|
||||
logger.error(f"无法解析上线日期: '{create_date_value}',支持的格式: %Y-%m-%d, %Y-%m-%d %H:%M:%S 等")
|
||||
continue # 解析失败,跳过
|
||||
|
||||
# 使用解析后的日期进行判断
|
||||
@@ -484,7 +442,7 @@ class NewExceptionTask:
|
||||
continue
|
||||
|
||||
if not NGV_data_id:
|
||||
logger.warning(f"未找到关联数据,请检查门店编码: {org_code}")
|
||||
logger.warning(f"未找到关联数据,请检查门店编码: {row['org_code']}")
|
||||
|
||||
# 根据省市区派发给异常回访客服
|
||||
# 检查省市区是否都有值,如果有任何一个为空,则客服为空
|
||||
@@ -496,11 +454,6 @@ class NewExceptionTask:
|
||||
logger.warning(f"省: {province_name}, 市: {city_name}, 区: {area_name}")
|
||||
else:
|
||||
customer_service = self.assign_customer_service(province_name, city_name, area_name, self.index)
|
||||
if customer_service is None:
|
||||
logger.warning(
|
||||
f"【派发客服失败】门店 {org_code} 省={province_name} 市={city_name} 区={area_name} 未匹配到客服,跳过派发")
|
||||
error_data.append(row)
|
||||
continue
|
||||
logger.info(f"【派发客服】门店 {row['org_code']} 派发给客服: {customer_service}")
|
||||
|
||||
payload_dict.update({
|
||||
@@ -564,7 +517,7 @@ class NewExceptionTask:
|
||||
|
||||
"_widget_1748512176655": {"value": "未处理"}, # 跟进状态
|
||||
|
||||
"_widget_1772761760440": {"value": "客服跟进节点"}, # 当前跟进节点
|
||||
"_widget_1772761760440":{"value": "客服跟进节点"}, # 当前跟进节点
|
||||
|
||||
})
|
||||
|
||||
@@ -576,7 +529,6 @@ class NewExceptionTask:
|
||||
"transaction_id": UUid
|
||||
}
|
||||
all_data.append(routine_follow_up_payload)
|
||||
dispatched_org_codes.add(org_code)
|
||||
|
||||
# res = api_instance.data_batch_create(routine_follow_up_payload)
|
||||
# logger.info(f"创建结果:{res}")
|
||||
@@ -602,4 +554,3 @@ class NewExceptionTask:
|
||||
if __name__ == '__main__':
|
||||
start = NewExceptionTask()
|
||||
start.main()
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
import pymysql
|
||||
import sys
|
||||
import time
|
||||
|
||||
# ================== 配置信息 ==================
|
||||
SOURCE_CONFIG = {
|
||||
'host': "f6-public.rwlb.rds.aliyuncs.com",
|
||||
'user': "rw_operation_data_relay",
|
||||
'password': "m+q5Z4%IVuF9bf",
|
||||
'database': "f6operation_data_relay",
|
||||
'connect_timeout': 30,
|
||||
'read_timeout': 600,
|
||||
'write_timeout': 600
|
||||
}
|
||||
|
||||
TARGET_CONFIG = {
|
||||
'host': "db-f6operation-sst.f6car.org",
|
||||
'user': "rw_operation",
|
||||
'password': "tDm45eBj@upzLydHc",
|
||||
'database': "f6operation_data_relay",
|
||||
'connect_timeout': 30,
|
||||
'read_timeout': 600,
|
||||
'write_timeout': 600
|
||||
}
|
||||
|
||||
TABLE_NAME = 'rpt_customized_maintain_detail'
|
||||
READ_BATCH_SIZE = 1000 # 每次从源库读 5000 行
|
||||
WRITE_BATCH_SIZE = 1000 # 每次向目标库写 5000 行
|
||||
# ==============================================
|
||||
|
||||
|
||||
def main():
|
||||
print("🔧 正在从源数据库读取表结构...")
|
||||
|
||||
# === 第一步:获取建表语句 ===
|
||||
source_conn = None
|
||||
try:
|
||||
source_conn = pymysql.connect(**SOURCE_CONFIG)
|
||||
with source_conn.cursor() as cursor:
|
||||
cursor.execute(f"SHOW CREATE TABLE `{TABLE_NAME}`")
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
raise Exception(f"表 {TABLE_NAME} 不存在于源数据库")
|
||||
create_table_sql = result[1]
|
||||
except Exception as e:
|
||||
print(f"❌ 读取表结构失败: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if source_conn:
|
||||
source_conn.close()
|
||||
|
||||
# === 第二步:获取总行数(用于进度)===
|
||||
total_rows = 0
|
||||
try:
|
||||
source_conn = pymysql.connect(**SOURCE_CONFIG)
|
||||
with source_conn.cursor() as cursor:
|
||||
cursor.execute(f"SELECT COUNT(*) FROM `{TABLE_NAME}`")
|
||||
total_rows = cursor.fetchone()[0]
|
||||
except Exception as e:
|
||||
print(f"⚠️ 无法获取总行数(不影响迁移): {e}")
|
||||
finally:
|
||||
if source_conn:
|
||||
source_conn.close()
|
||||
|
||||
print(f"📊 表共约 {total_rows} 行,将分批读取和写入...")
|
||||
|
||||
# === 第三步:重建目标表 ===
|
||||
target_conn = None
|
||||
columns = []
|
||||
try:
|
||||
target_conn = pymysql.connect(**TARGET_CONFIG)
|
||||
with target_conn.cursor() as cursor:
|
||||
cursor.execute(f"DROP TABLE IF EXISTS `{TABLE_NAME}`")
|
||||
new_create_sql = create_table_sql.replace(f"`{SOURCE_CONFIG['database']}`.", "")
|
||||
cursor.execute(new_create_sql)
|
||||
# 获取列名(从建表语句解析较复杂,改用查一次空结果)
|
||||
cursor.execute(f"SELECT * FROM `{TABLE_NAME}` LIMIT 0")
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
target_conn.commit()
|
||||
print("✅ 目标表已重建")
|
||||
except Exception as e:
|
||||
print(f"❌ 重建目标表失败: {e}")
|
||||
if target_conn:
|
||||
target_conn.close()
|
||||
sys.exit(1)
|
||||
|
||||
# === 第四步:分批读取 + 分批写入 ===
|
||||
offset = 0
|
||||
inserted_total = 0
|
||||
|
||||
while True:
|
||||
time.sleep(5)
|
||||
# 从源库读取一批
|
||||
batch_rows = []
|
||||
try:
|
||||
source_conn = pymysql.connect(**SOURCE_CONFIG)
|
||||
with source_conn.cursor(pymysql.cursors.DictCursor) as cursor:
|
||||
cursor.execute(
|
||||
f"SELECT * FROM `{TABLE_NAME}` LIMIT %s OFFSET %s",
|
||||
(READ_BATCH_SIZE, offset)
|
||||
)
|
||||
batch_rows = cursor.fetchall()
|
||||
source_conn.close()
|
||||
except Exception as e:
|
||||
print(f"❌ 读取第 {offset//READ_BATCH_SIZE + 1} 批数据失败: {e}")
|
||||
break
|
||||
|
||||
if not batch_rows:
|
||||
print("🔚 数据读取完成")
|
||||
break
|
||||
|
||||
# 转换为元组
|
||||
data_tuples = [tuple(row[col] for col in columns) for row in batch_rows]
|
||||
|
||||
# 写入目标库(可再分小批,但这里 batch_rows 已是 5000)
|
||||
try:
|
||||
with target_conn.cursor() as cursor:
|
||||
placeholders = ', '.join(['%s'] * len(columns))
|
||||
insert_sql = f"INSERT INTO `{TABLE_NAME}` (`{'`, `'.join(columns)}`) VALUES ({placeholders})"
|
||||
cursor.executemany(insert_sql, data_tuples)
|
||||
target_conn.commit()
|
||||
inserted_total += len(data_tuples)
|
||||
print(f"📤 已写入 {inserted_total} / {total_rows} 行")
|
||||
except Exception as e:
|
||||
print(f"❌ 写入失败(批次 offset={offset}): {e}")
|
||||
target_conn.rollback()
|
||||
break
|
||||
|
||||
offset += READ_BATCH_SIZE
|
||||
|
||||
# === 清理 ===
|
||||
if target_conn and target_conn.open:
|
||||
target_conn.close()
|
||||
|
||||
print(f"🎉 迁移完成!共写入 {inserted_total} 行")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,899 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 数据库验证脚本 - 数据处理部分\n",
|
||||
"\n",
|
||||
"本notebook用于调试和验证数据库验证脚本的数据处理逻辑(260-425行)\n",
|
||||
"\n",
|
||||
"## 使用说明\n",
|
||||
"1. 先执行数据加载部分(第2个单元格),这部分比较耗时\n",
|
||||
"2. 数据加载完成后,再执行后续的数据处理单元格\n",
|
||||
"3. 每个单元格都可以单独执行和调试\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T06:53:03.604128900Z",
|
||||
"start_time": "2026-01-16T06:53:01.840121200Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"库导入完成\n",
|
||||
"项目根目录: D:\\Idea Project\\SaaS_V1.7\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 导入必要的库\n",
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"import pandas as pd\n",
|
||||
"import datetime\n",
|
||||
"from datetime import datetime, timedelta\n",
|
||||
"import re\n",
|
||||
"\n",
|
||||
"# 添加项目根目录到路径(notebook文件在test目录下,需要添加父目录)\n",
|
||||
"current_dir = os.getcwd()\n",
|
||||
"# 如果当前目录是test,则添加父目录;否则添加当前目录\n",
|
||||
"if os.path.basename(current_dir) == 'test':\n",
|
||||
" project_root = os.path.dirname(current_dir)\n",
|
||||
"else:\n",
|
||||
" project_root = current_dir\n",
|
||||
"sys.path.insert(0, project_root)\n",
|
||||
"\n",
|
||||
"from api import API\n",
|
||||
"from back_ground_module import CommonModule\n",
|
||||
"from log_config import configure_task_logger, configure_error_task_logger\n",
|
||||
"\n",
|
||||
"# 初始化API和CommonModule\n",
|
||||
"api_instance = API()\n",
|
||||
"common_module = CommonModule()\n",
|
||||
"\n",
|
||||
"# 获取日志记录器\n",
|
||||
"logger = configure_task_logger()\n",
|
||||
"error_task_logger = configure_error_task_logger()\n",
|
||||
"\n",
|
||||
"print(\"库导入完成\")\n",
|
||||
"print(f\"项目根目录: {project_root}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 步骤1: 数据加载(耗时操作,可单独执行)\n",
|
||||
"\n",
|
||||
"这部分会加载所有必要的数据,包括:\n",
|
||||
"- 省市区人员关系表\n",
|
||||
"- 员工ID列表\n",
|
||||
"- 权限表\n",
|
||||
"- NGV数据列表\n",
|
||||
"- 服务提醒数据\n",
|
||||
"- 智能检测数据\n",
|
||||
"- 功能使用情况表\n",
|
||||
"- 保单识别表\n",
|
||||
"- 私域/公域小程序数据\n",
|
||||
"- 异业合作数据\n",
|
||||
"- 短信数据\n",
|
||||
"- 多公司过滤表\n",
|
||||
"- NGV明细数据(从数据库获取)\n",
|
||||
"- 节假日列表\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ========== 数据加载部分 ==========\n",
|
||||
"# 这部分比较耗时,可以单独执行\n",
|
||||
"\n",
|
||||
"print(\"开始加载数据...\")\n",
|
||||
"\n",
|
||||
"# 省市区人员关系表\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"676512ac3e54dc3159460c0a\"}\n",
|
||||
"json_dict = api_instance.entry_data_list(payload)\n",
|
||||
"if json_dict and \"data\" in json_dict:\n",
|
||||
" json_list = json_dict.get(\"data\")\n",
|
||||
"else:\n",
|
||||
" print(\"加载省市区人员关系表失败\")\n",
|
||||
" json_list = []\n",
|
||||
"print(f\"省市区人员关系表: {len(json_list)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取简道云员工id\n",
|
||||
"payload = {\"api_key\": \"6694d3c4fcb69ca9a111a6c4\", \"entry_id\": \"6769204a1902c9341340a1bc\"}\n",
|
||||
"staff_id = api_instance.entry_data_list(payload)\n",
|
||||
"staff_id_list = staff_id.get(\"data\")\n",
|
||||
"print(f\"员工ID列表: {len(staff_id_list)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取权限表信息\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"675b96c14e839f90fef1647c\"}\n",
|
||||
"permissions_table = api_instance.entry_data_list(payload).get(\"data\")\n",
|
||||
"print(f\"权限表: {len(permissions_table)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取NGV数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"675bb02bd2d53c2034c665e4\"}\n",
|
||||
"NGV_data_list = api_instance.entry_data_list(payload).get(\"data\")\n",
|
||||
"print(f\"NGV数据列表: {len(NGV_data_list)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取服务提醒-数据支持表单数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"676bb7bda3029720f1083e99\"}\n",
|
||||
"service_remind = api_instance.entry_data_list(payload).get(\"data\")\n",
|
||||
"print(f\"服务提醒数据: {len(service_remind)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取智能检测-数据支持表单数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"676bb99649ab3ac975af6e39\"}\n",
|
||||
"Smart_detection = api_instance.entry_data_list(payload).get(\"data\")\n",
|
||||
"print(f\"智能检测数据: {len(Smart_detection)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取功能使用情况表\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"6763bbf657bd8fb76fcb41b2\"}\n",
|
||||
"get_feature_usage = api_instance.entry_data_list(payload).get(\"data\", [])\n",
|
||||
"print(f\"功能使用情况表: {len(get_feature_usage)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取保单识别表\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"6773a60d30ed87ff9f68d3c5\"}\n",
|
||||
"policy_recognition = api_instance.entry_data_list(payload).get(\"data\")\n",
|
||||
"widget_list = [item['_widget_1735632397600'] for item in policy_recognition]\n",
|
||||
"print(f\"保单识别表: {len(policy_recognition)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取私域小程序-数据支持表单数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"67e0f0fae622896749ba5087\"}\n",
|
||||
"private_domain = api_instance.entry_data_list(payload).get(\"data\", [])\n",
|
||||
"print(f\"私域小程序数据: {len(private_domain)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取公域小程序-数据支持表单数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"67e0c702c8f603b997980999\"}\n",
|
||||
"public_domain = api_instance.entry_data_list(payload).get(\"data\", [])\n",
|
||||
"public_domain_list = [item['_widget_1742784257506'] for item in public_domain]\n",
|
||||
"print(f\"公域小程序数据: {len(public_domain)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取异业合作-数据支持表单数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"67e24fdd8dfcfa918e17c30b\"}\n",
|
||||
"different_industries = api_instance.entry_data_list(payload).get(\"data\", [])\n",
|
||||
"different_industries_list = [item['_widget_1742884829007'] for item in different_industries]\n",
|
||||
"print(f\"异业合作数据: {len(different_industries)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取短信-数据支持表单数据\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"67e5107198ba1b20d5df3974\"}\n",
|
||||
"groupnotification = api_instance.entry_data_list(payload).get(\"data\", [])\n",
|
||||
"print(f\"短信数据: {len(groupnotification)} 条\")\n",
|
||||
"\n",
|
||||
"# 获取多公司过滤表\n",
|
||||
"payload = {\"api_key\": \"675b900991ad2491c69389ca\", \"entry_id\": \"689bf5f8ba88a28cb0679ec9\"}\n",
|
||||
"get_filter_company_list = api_instance.entry_data_list(payload).get(\"data\", [])\n",
|
||||
"print(f\"多公司过滤表: {len(get_filter_company_list)} 条\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"# 获取节假日列表\n",
|
||||
"date_list = common_module.get_holiday_list()\n",
|
||||
"print(f\"节假日列表: {len(date_list)} 个日期\")\n",
|
||||
"\n",
|
||||
"# 获取NGV明细数据(从数据库获取,比较耗时)\n",
|
||||
"print(\"\\n开始从数据库获取NGV明细数据(这可能需要一些时间)...\")\n",
|
||||
"data_NGV = common_module.get_ngv_details(days_back=1)\n",
|
||||
"print(f\"NGV明细数据: {len(data_NGV)} 条\")\n",
|
||||
"print(f\"NGV明细数据列数: {len(data_NGV.columns)}\")\n",
|
||||
"\n",
|
||||
"# 构建省市区索引\n",
|
||||
"def build_index(json_list):\n",
|
||||
" index = {}\n",
|
||||
" for json_item in json_list:\n",
|
||||
" try:\n",
|
||||
" key = (json_item['_widget_1734677164861'], json_item['_widget_1734677164862'],\n",
|
||||
" json_item['_widget_1734677164863']) # 省市区\n",
|
||||
" if '_widget_1734677164871' not in json_item: # 日常回访客服\n",
|
||||
" raise KeyError(\"缺少 '日常回访客服' 键\")\n",
|
||||
" index[key] = json_item\n",
|
||||
" except KeyError as e:\n",
|
||||
" print(f\"警告:{e},跳过该条记录\")\n",
|
||||
" continue\n",
|
||||
" return index\n",
|
||||
"\n",
|
||||
"index = build_index(json_list)\n",
|
||||
"print(f\"省市区索引构建完成: {len(index)} 条\")\n",
|
||||
"\n",
|
||||
"print(\"\\n========== 数据加载完成 ==========\")\n",
|
||||
"print(f\"数据加载时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 步骤2: 数据处理逻辑(260-425行)\n",
|
||||
"\n",
|
||||
"这部分包含主要的数据处理逻辑,包括:\n",
|
||||
"- 获取多公司过滤公司id\n",
|
||||
"- 数据清洗和转换\n",
|
||||
"- 优先级排序\n",
|
||||
"- 数据过滤和合并\n",
|
||||
"- 日期计算和扩展\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ========== 获取多公司过滤公司id ==========\n",
|
||||
"logger.info(\"获取多公司过滤公司id\")\n",
|
||||
"all_filter_company_list = [] # 获取多公司过滤公司id\n",
|
||||
"for company in get_filter_company_list:\n",
|
||||
" company_list = company.get(\"_widget_1755052002491\")\n",
|
||||
" if company_list:\n",
|
||||
" for company_item in company_list:\n",
|
||||
" if company_item.get(\"_widget_1755052002496\") == \"否\":\n",
|
||||
" all_filter_company_list.append(company_item.get(\"_widget_1755052002495\"))\n",
|
||||
"logger.info(f\"过滤公司条数:{len(all_filter_company_list)}\")\n",
|
||||
"print(f\"过滤公司条数: {len(all_filter_company_list)}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:27:13.175060200Z",
|
||||
"start_time": "2026-01-16T07:27:09.857076900Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"数据预处理完成,当前数据量: 45672 条\n",
|
||||
"数据列数: 143\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 数据预处理:日期转换和数据清洗 ==========\n",
|
||||
"# 将A列和B列的日期字符串转换为日期格式\n",
|
||||
"data_NGV = data_NGV.copy()\n",
|
||||
"data_NGV['A'] = pd.to_datetime(data_NGV['expiry_time'])\n",
|
||||
"data_NGV['B'] = pd.to_datetime(data_NGV['renew_date'])\n",
|
||||
"\n",
|
||||
"def replace_values(series):\n",
|
||||
" # 使用条件判断来进行替换\n",
|
||||
" return series.apply(lambda x: '' if pd.isna(x) or x in ['NA', 'None', ''] else x)\n",
|
||||
"\n",
|
||||
"# 处理字符串数据并显式指定数据类型\n",
|
||||
"data_NGV = data_NGV.apply(replace_values)\n",
|
||||
"\n",
|
||||
"print(f\"数据预处理完成,当前数据量: {len(data_NGV)} 条\")\n",
|
||||
"print(f\"数据列数: {len(data_NGV.columns)}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:28:01.298494800Z",
|
||||
"start_time": "2026-01-16T07:28:01.106113700Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"过滤多公司后数据量: 45653 条\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 过滤多公司 ==========\n",
|
||||
"# 针对公司主店过期,取公司最高等级版本派发\n",
|
||||
"# 过滤多公司\n",
|
||||
"data_NGV = data_NGV[~data_NGV['id_own_group'].isin(all_filter_company_list)]\n",
|
||||
"print(f\"过滤多公司后数据量: {len(data_NGV)} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:28:04.235079300Z",
|
||||
"start_time": "2026-01-16T07:28:04.185820400Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"优先级映射完成\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 定义优先级顺序和创建映射字典 ==========\n",
|
||||
"# 定义优先级顺序\n",
|
||||
"edition_order = ['皇冠版', '至尊版', '尊享版', '旗舰版', '标准版', '进阶版', '基础版', '入门版']\n",
|
||||
"customer_type_order = [\"F\", \"E\", \"D\", \"C\", \"B\", \"A\"] # 索引越小优先级越高\n",
|
||||
"group_grade_order = ['全国KA(FMVP)', '区域KA(MVP)', '重要客户(SVIP)', '普通客户(VIP)']\n",
|
||||
"\n",
|
||||
"# 创建映射字典,并为不在列表中的值设置默认值\n",
|
||||
"edition_map = {edition: idx for idx, edition in enumerate(edition_order)}\n",
|
||||
"customer_type_map = {ctype: idx for idx, ctype in enumerate(customer_type_order)}\n",
|
||||
"group_grade_map = {grade: idx for idx, grade in enumerate(group_grade_order)}\n",
|
||||
"\n",
|
||||
"# 添加用于排序的新列,并处理不在映射字典中的值\n",
|
||||
"data_NGV['edition_rank'] = data_NGV['saas_edition_fmt'].map(edition_map).fillna(0).astype(int) # 缺失值用最高优先级填充\n",
|
||||
"data_NGV['customer_type_rank'] = data_NGV['saas_customer_type'].map(customer_type_map).fillna(0).astype(int)\n",
|
||||
"data_NGV['group_grade_rank'] = data_NGV['group_grade'].map(group_grade_map).fillna(0).astype(int)\n",
|
||||
"\n",
|
||||
"print(\"优先级映射完成\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:28:08.790102700Z",
|
||||
"start_time": "2026-01-16T07:28:08.399298Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"最佳值查找完成\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 找到每组中的最佳值 ==========\n",
|
||||
"# 找到每组中 edition_rank 最小值对应的行\n",
|
||||
"best_edition_idx = data_NGV.groupby('id_own_group')['edition_rank'].idxmin()\n",
|
||||
"best_edition_rows = data_NGV.loc[best_edition_idx]\n",
|
||||
"best_edition_rows['max_saas_edition'] = best_edition_rows['saas_edition_fmt']\n",
|
||||
"\n",
|
||||
"# 找到每组中 customer_type_rank 最小值对应的行\n",
|
||||
"best_customer_type_idx = data_NGV.groupby('id_own_group')['customer_type_rank'].idxmin()\n",
|
||||
"best_customer_type_rows = data_NGV.loc[best_customer_type_idx]\n",
|
||||
"best_customer_type_rows['max_saas_customer_type'] = best_customer_type_rows['customer_type_rank'].apply(\n",
|
||||
" lambda x: customer_type_order[x])\n",
|
||||
"\n",
|
||||
"# 找到每组中 group_grade_rank 最小值对应的行\n",
|
||||
"best_group_grade_idx = data_NGV.groupby('id_own_group')['group_grade_rank'].idxmin()\n",
|
||||
"best_group_grade_rows = data_NGV.loc[best_group_grade_idx]\n",
|
||||
"best_group_grade_rows['max_group_grade'] = best_group_grade_rows['group_grade']\n",
|
||||
"\n",
|
||||
"print(\"最佳值查找完成\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:28:11.374632Z",
|
||||
"start_time": "2026-01-16T07:28:11.141730700Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"最佳值合并完成,当前数据量: 45653 条\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 合并最佳值回到原数据集 ==========\n",
|
||||
"# 合并最佳值回到原数据集\n",
|
||||
"best_values = (\n",
|
||||
" best_edition_rows[['id_own_group', 'max_saas_edition']]\n",
|
||||
" .merge(best_customer_type_rows[['id_own_group', 'max_saas_customer_type']], on='id_own_group',\n",
|
||||
" how='outer')\n",
|
||||
" .merge(best_group_grade_rows[['id_own_group', 'max_group_grade']], on='id_own_group', how='outer')\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# 将最佳值合并回原数据集\n",
|
||||
"data_NGV = data_NGV.merge(best_values, on='id_own_group', how='left')\n",
|
||||
"\n",
|
||||
"print(f\"最佳值合并完成,当前数据量: {len(data_NGV)} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:30:06.358604500Z",
|
||||
"start_time": "2026-01-16T07:30:05.752861600Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"============================================================\n",
|
||||
"调试信息:处理主店过期情况\n",
|
||||
"============================================================\n",
|
||||
"\n",
|
||||
"当前 data_NGV 数据量: 45653 条\n",
|
||||
"\n",
|
||||
"【字段检查】\n",
|
||||
"is_main_org 数据类型: object\n",
|
||||
"is_main_org 唯一值: ['0', '1']\n",
|
||||
"is_main_org 值分布:\n",
|
||||
"is_main_org\n",
|
||||
"1 37628\n",
|
||||
"0 8025\n",
|
||||
"Name: count, dtype: int64\n",
|
||||
"\n",
|
||||
"org_status 数据类型: object\n",
|
||||
"org_status 唯一值: ['留存', '过期']\n",
|
||||
"org_status 值分布:\n",
|
||||
"org_status\n",
|
||||
"留存 27985\n",
|
||||
"过期 17668\n",
|
||||
"Name: count, dtype: int64\n",
|
||||
"\n",
|
||||
"org_type 数据类型: object\n",
|
||||
"org_type 唯一值: ['一般', '天猫']\n",
|
||||
"org_type 值分布:\n",
|
||||
"org_type\n",
|
||||
"一般 42985\n",
|
||||
"天猫 2668\n",
|
||||
"Name: count, dtype: int64\n",
|
||||
"\n",
|
||||
"【步骤1: 筛选主店过期】\n",
|
||||
"警告: is_main_org 是字符串类型,尝试转换为数值\n",
|
||||
"条件筛选结果数量: 15065 条\n",
|
||||
"主店过期数据量 (ngvv2): 15065 条\n",
|
||||
"ngvv2 中的 id_own_group 数量: 15065 个\n",
|
||||
"ngvv2 中的 id_own_group 示例: ['10545055917999655906', '10545055917999678943', '10545055917999702656', '10545055917999726421', '10545055917999791008', '10545055917999907421', '10545055917999958815', '10545055917999963314', '10545055917999973061', '10545055918000062921']\n",
|
||||
"\n",
|
||||
"【步骤2: 筛选分店留存】\n",
|
||||
"data_NGV_V2 初始数据量: 45653 条\n",
|
||||
"\n",
|
||||
"area_manager 唯一值数量: 16\n",
|
||||
"area_manager 值分布(前10):\n",
|
||||
"area_manager\n",
|
||||
"肖军 10824\n",
|
||||
"景东强 8408\n",
|
||||
"陈庆伟 8322\n",
|
||||
"张凯 8269\n",
|
||||
"关磊 7028\n",
|
||||
"孙玉蕾 2006\n",
|
||||
"殷昊 556\n",
|
||||
"王涛 161\n",
|
||||
"刘伟 52\n",
|
||||
" 8\n",
|
||||
"Name: count, dtype: int64\n",
|
||||
"\n",
|
||||
"各条件筛选结果:\n",
|
||||
" org_type == '一般': 42985 条\n",
|
||||
" org_status == '留存': 27985 条\n",
|
||||
" area_manager != '殷昊': 45097 条\n",
|
||||
" area_manager != '孙玉蕾': 43647 条\n",
|
||||
" is_main_org != 1: 8025 条\n",
|
||||
"\n",
|
||||
"所有条件合并后数据量: 4882 条\n",
|
||||
"data_NGV_V2_filtered 中的 id_own_group 数量: 1564 个\n",
|
||||
"data_NGV_V2_filtered 中的 id_own_group 示例: ['10545055917999659357', '10545055917999659357', '10545055917999688607', '10545055917999688607', '10545055917999688607', '10545055917999719687', '10545055917999791008', '10545055917999791008', '10545055917999995278', '10545055918000106937']\n",
|
||||
"\n",
|
||||
"【步骤3: 检查 id_own_group 交集】\n",
|
||||
"ngvv2 中的 id_own_group 数量: 15065\n",
|
||||
"data_NGV_V2_filtered 中的 id_own_group 数量: 1564\n",
|
||||
"交集数量: 316\n",
|
||||
"交集中的 id_own_group 示例: ['11240984669917478021', '10546172455175803787', '10546172455166018835', '10546172455220322161', '10546443563657780587', '10546172455213602644', '10546443563816611858', '11240984669917430021', '11240984669917329620', '11240984669917352563']\n",
|
||||
"\n",
|
||||
"【步骤4: 最终过滤】\n",
|
||||
"过滤后的数据量: 468 条\n",
|
||||
"\n",
|
||||
"============================================================\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\hp_z66\\AppData\\Local\\Temp\\ipykernel_14280\\4275068286.py:100: SettingWithCopyWarning: \n",
|
||||
"A value is trying to be set on a copy of a slice from a DataFrame.\n",
|
||||
"Try using .loc[row_indexer,col_indexer] = value instead\n",
|
||||
"\n",
|
||||
"See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
|
||||
" data_NGV_V2_filtered['exists_in_ngvv2'] = data_NGV_V2_filtered['id_own_group'].isin(ngvv2['id_own_group'])\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 处理主店过期的情况 ==========\n",
|
||||
"# 调试信息:检查数据状态\n",
|
||||
"print(\"=\" * 60)\n",
|
||||
"print(\"调试信息:处理主店过期情况\")\n",
|
||||
"print(\"=\" * 60)\n",
|
||||
"print(f\"\\n当前 data_NGV 数据量: {len(data_NGV)} 条\")\n",
|
||||
"\n",
|
||||
"# 检查关键字段的数据类型和唯一值\n",
|
||||
"print(f\"\\n【字段检查】\")\n",
|
||||
"print(f\"is_main_org 数据类型: {data_NGV['is_main_org'].dtype}\")\n",
|
||||
"print(f\"is_main_org 唯一值: {sorted(data_NGV['is_main_org'].unique())}\")\n",
|
||||
"print(f\"is_main_org 值分布:\\n{data_NGV['is_main_org'].value_counts()}\")\n",
|
||||
"\n",
|
||||
"print(f\"\\norg_status 数据类型: {data_NGV['org_status'].dtype}\")\n",
|
||||
"print(f\"org_status 唯一值: {sorted(data_NGV['org_status'].unique())}\")\n",
|
||||
"print(f\"org_status 值分布:\\n{data_NGV['org_status'].value_counts()}\")\n",
|
||||
"\n",
|
||||
"print(f\"\\norg_type 数据类型: {data_NGV['org_type'].dtype}\")\n",
|
||||
"print(f\"org_type 唯一值: {sorted(data_NGV['org_type'].unique())}\")\n",
|
||||
"print(f\"org_type 值分布:\\n{data_NGV['org_type'].value_counts()}\")\n",
|
||||
"\n",
|
||||
"# 步骤1: 筛选主店过期的情况\n",
|
||||
"print(f\"\\n【步骤1: 筛选主店过期】\")\n",
|
||||
"# 确保 is_main_org 是数值类型\n",
|
||||
"if data_NGV['is_main_org'].dtype == 'object':\n",
|
||||
" print(\"警告: is_main_org 是字符串类型,尝试转换为数值\")\n",
|
||||
" data_NGV['is_main_org'] = pd.to_numeric(data_NGV['is_main_org'], errors='coerce')\n",
|
||||
"\n",
|
||||
"condition = (data_NGV['is_main_org'] == 1) & (data_NGV['org_status'] == '过期')\n",
|
||||
"print(f\"条件筛选结果数量: {condition.sum()} 条\")\n",
|
||||
"\n",
|
||||
"ngvv2 = data_NGV[condition]\n",
|
||||
"print(f\"主店过期数据量 (ngvv2): {len(ngvv2)} 条\")\n",
|
||||
"\n",
|
||||
"if len(ngvv2) > 0:\n",
|
||||
" print(f\"ngvv2 中的 id_own_group 数量: {ngvv2['id_own_group'].nunique()} 个\")\n",
|
||||
" print(f\"ngvv2 中的 id_own_group 示例: {ngvv2['id_own_group'].head(10).tolist()}\")\n",
|
||||
"else:\n",
|
||||
" print(\"⚠️ 警告: ngvv2 为空,没有主店过期的情况!\")\n",
|
||||
"\n",
|
||||
"# 步骤2: 检查分店留存的情况\n",
|
||||
"print(f\"\\n【步骤2: 筛选分店留存】\")\n",
|
||||
"# 在合并最佳值之前保存原始数据副本(重要!)\n",
|
||||
"data_NGV_V2 = data_NGV.copy()\n",
|
||||
"print(f\"data_NGV_V2 初始数据量: {len(data_NGV_V2)} 条\")\n",
|
||||
"\n",
|
||||
"# 检查 area_manager 字段\n",
|
||||
"print(f\"\\narea_manager 唯一值数量: {data_NGV_V2['area_manager'].nunique()}\")\n",
|
||||
"print(f\"area_manager 值分布(前10):\\n{data_NGV_V2['area_manager'].value_counts().head(10)}\")\n",
|
||||
"\n",
|
||||
"# 确保 is_main_org 是数值类型\n",
|
||||
"if data_NGV_V2['is_main_org'].dtype == 'object':\n",
|
||||
" data_NGV_V2['is_main_org'] = pd.to_numeric(data_NGV_V2['is_main_org'], errors='coerce')\n",
|
||||
"\n",
|
||||
"# 逐步检查每个条件\n",
|
||||
"cond1 = (data_NGV_V2['org_type'] == \"一般\")\n",
|
||||
"cond2 = (data_NGV_V2['org_status'] == '留存')\n",
|
||||
"cond3 = (data_NGV_V2['area_manager'] != '殷昊')\n",
|
||||
"cond4 = (data_NGV_V2['area_manager'] != '孙玉蕾')\n",
|
||||
"cond5 = (data_NGV_V2['is_main_org'] != 1)\n",
|
||||
"\n",
|
||||
"print(f\"\\n各条件筛选结果:\")\n",
|
||||
"print(f\" org_type == '一般': {cond1.sum()} 条\")\n",
|
||||
"print(f\" org_status == '留存': {cond2.sum()} 条\")\n",
|
||||
"print(f\" area_manager != '殷昊': {cond3.sum()} 条\")\n",
|
||||
"print(f\" area_manager != '孙玉蕾': {cond4.sum()} 条\")\n",
|
||||
"print(f\" is_main_org != 1: {cond5.sum()} 条\")\n",
|
||||
"\n",
|
||||
"data_NGV_V2['条件'] = cond1 & cond2 & cond3 & cond4 & cond5\n",
|
||||
"data_NGV_V2_filtered = data_NGV_V2.loc[data_NGV_V2[\"条件\"]]\n",
|
||||
"print(f\"\\n所有条件合并后数据量: {len(data_NGV_V2_filtered)} 条\")\n",
|
||||
"\n",
|
||||
"if len(data_NGV_V2_filtered) > 0:\n",
|
||||
" print(f\"data_NGV_V2_filtered 中的 id_own_group 数量: {data_NGV_V2_filtered['id_own_group'].nunique()} 个\")\n",
|
||||
" print(f\"data_NGV_V2_filtered 中的 id_own_group 示例: {data_NGV_V2_filtered['id_own_group'].head(10).tolist()}\")\n",
|
||||
"\n",
|
||||
"# 步骤3: 检查交集\n",
|
||||
"print(f\"\\n【步骤3: 检查 id_own_group 交集】\")\n",
|
||||
"if len(ngvv2) > 0 and len(data_NGV_V2_filtered) > 0:\n",
|
||||
" ngvv2_groups = set(ngvv2['id_own_group'].unique())\n",
|
||||
" v2_groups = set(data_NGV_V2_filtered['id_own_group'].unique())\n",
|
||||
" intersection = ngvv2_groups & v2_groups\n",
|
||||
" \n",
|
||||
" print(f\"ngvv2 中的 id_own_group 数量: {len(ngvv2_groups)}\")\n",
|
||||
" print(f\"data_NGV_V2_filtered 中的 id_own_group 数量: {len(v2_groups)}\")\n",
|
||||
" print(f\"交集数量: {len(intersection)}\")\n",
|
||||
" \n",
|
||||
" if len(intersection) > 0:\n",
|
||||
" print(f\"交集中的 id_own_group 示例: {list(intersection)[:10]}\")\n",
|
||||
" else:\n",
|
||||
" print(\"⚠️ 警告: 没有交集!这可能是问题所在。\")\n",
|
||||
" print(f\"ngvv2 中的前10个 id_own_group: {list(ngvv2_groups)[:10]}\")\n",
|
||||
" print(f\"data_NGV_V2_filtered 中的前10个 id_own_group: {list(v2_groups)[:10]}\")\n",
|
||||
"else:\n",
|
||||
" print(\"⚠️ 警告: ngvv2 或 data_NGV_V2_filtered 为空,无法检查交集\")\n",
|
||||
"\n",
|
||||
"# 步骤4: 过滤存在的记录\n",
|
||||
"print(f\"\\n【步骤4: 最终过滤】\")\n",
|
||||
"if len(ngvv2) > 0:\n",
|
||||
" data_NGV_V2_filtered['exists_in_ngvv2'] = data_NGV_V2_filtered['id_own_group'].isin(ngvv2['id_own_group'])\n",
|
||||
" filtered_data = data_NGV_V2_filtered[data_NGV_V2_filtered['exists_in_ngvv2']]\n",
|
||||
" print(f\"过滤后的数据量: {len(filtered_data)} 条\")\n",
|
||||
" \n",
|
||||
" if len(filtered_data) == 0:\n",
|
||||
" print(\"\\n❌ 问题诊断:\")\n",
|
||||
" print(\" 过滤后数据为空,可能的原因:\")\n",
|
||||
" print(\" 1. ngvv2 为空(没有主店过期的情况)\")\n",
|
||||
" print(\" 2. data_NGV_V2_filtered 为空(没有满足条件的分店留存数据)\")\n",
|
||||
" print(\" 3. 两者的 id_own_group 没有交集\")\n",
|
||||
" print(\"\\n建议:\")\n",
|
||||
" print(\" - 检查数据源是否正确\")\n",
|
||||
" print(\" - 检查字段值是否匹配(注意数据类型和格式)\")\n",
|
||||
" print(\" - 检查是否有主店过期但分店留存的情况\")\n",
|
||||
"else:\n",
|
||||
" print(\"⚠️ 警告: ngvv2 为空,无法进行过滤\")\n",
|
||||
" filtered_data = pd.DataFrame() # 创建空DataFrame\n",
|
||||
"\n",
|
||||
"print(\"\\n\" + \"=\" * 60)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:30:16.487181500Z",
|
||||
"start_time": "2026-01-16T07:30:16.440099400Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"排序去重后数据量: 316 条\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\hp_z66\\AppData\\Local\\Temp\\ipykernel_14280\\2835892650.py:5: SettingWithCopyWarning: \n",
|
||||
"A value is trying to be set on a copy of a slice from a DataFrame.\n",
|
||||
"Try using .loc[row_indexer,col_indexer] = value instead\n",
|
||||
"\n",
|
||||
"See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
|
||||
" filtered_data['sort_key'] = filtered_data['saas_edition_fmt'].map(fixed_order_map)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 对过滤数据进行排序和去重 ==========\n",
|
||||
"fixed_order = ['皇冠版', '至尊版', '尊享版', '旗舰版', '标准版', '进阶版', '基础版', '入门版']\n",
|
||||
"\n",
|
||||
"fixed_order_map = {edition: index for index, edition in enumerate(fixed_order)}\n",
|
||||
"filtered_data['sort_key'] = filtered_data['saas_edition_fmt'].map(fixed_order_map)\n",
|
||||
"filtered_data = filtered_data.sort_values(by='sort_key').drop('sort_key', axis=1)\n",
|
||||
"\n",
|
||||
"result = filtered_data.drop_duplicates(subset='id_own_group', keep='first')\n",
|
||||
"\n",
|
||||
"print(f\"排序去重后数据量: {len(result)} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ========== 合并主店留存数据和分店数据 ==========\n",
|
||||
"data_NGV['条件'] = (data_NGV['org_type'] == \"一般\") & (data_NGV['org_status'] == '留存') & (\n",
|
||||
" data_NGV['area_manager'] != '殷昊') & (\n",
|
||||
" data_NGV['area_manager'] != '孙玉蕾') & (\n",
|
||||
" data_NGV['is_main_org'] == 1)\n",
|
||||
"data_NGV = data_NGV.loc[data_NGV[\"条件\"]]\n",
|
||||
"\n",
|
||||
"data_NGV = pd.concat([data_NGV, result], axis=0)\n",
|
||||
"data_details = data_NGV.copy()\n",
|
||||
"\n",
|
||||
"# 重置索引\n",
|
||||
"data_details = data_details.reset_index(drop=True)\n",
|
||||
"\n",
|
||||
"print(f\"合并后数据量: {len(data_details)} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:30:21.600199200Z",
|
||||
"start_time": "2026-01-16T07:30:20.828225500Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"日期计算后数据量: 9845 条\n",
|
||||
"需要扩展的数据行数: 9845 条\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 判断日期差并计算年数 ==========\n",
|
||||
"# 判断A列的日期是否大于B列的日期730天,如果是的话,将B列的值设置为天数差\n",
|
||||
"data_details['条件'] = data_details.apply(\n",
|
||||
" lambda row: (\n",
|
||||
" (pd.to_datetime(row['A']) - pd.to_datetime(row['B'])).days\n",
|
||||
" if pd.to_datetime(row['A']) - pd.to_datetime(row['B']) >= pd.Timedelta(days=730)\n",
|
||||
" else 0\n",
|
||||
" ),\n",
|
||||
" axis=1\n",
|
||||
")\n",
|
||||
"data_details = data_details.loc[data_details[\"条件\"] > 0]\n",
|
||||
"\n",
|
||||
"# 定义一个函数,用于将数字除以365并取整数\n",
|
||||
"def divide_by_365(x):\n",
|
||||
" if isinstance(x, (int, float)):\n",
|
||||
" return int(x / 365)\n",
|
||||
" else:\n",
|
||||
" return x\n",
|
||||
"\n",
|
||||
"# 使用apply函数将divide_by_365函数应用到DataFrame的列\n",
|
||||
"data_details['年'] = data_details['条件'].apply(divide_by_365)\n",
|
||||
"\n",
|
||||
"# 重置索引\n",
|
||||
"data_details = data_details.reset_index(drop=True)\n",
|
||||
"\n",
|
||||
"print(f\"日期计算后数据量: {len(data_details)} 条\")\n",
|
||||
"print(f\"需要扩展的数据行数: {len(data_details[data_details['年'] > 1])} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ========== 扩展数据:根据年数复制行并修改日期 ==========\n",
|
||||
"# 创建一个新的空的DataFrame\n",
|
||||
"new_df = pd.DataFrame()\n",
|
||||
"\n",
|
||||
"# 遍历原始DataFrame的每一行\n",
|
||||
"for index, row in data_details.iterrows():\n",
|
||||
" # 根据年数来决定复制的次数\n",
|
||||
" if row[\"renew_date\"] != \"2024-02-29\":\n",
|
||||
" for i_new in range(1, row['年']):\n",
|
||||
" # 修改日期\n",
|
||||
" row_new = row.copy()\n",
|
||||
" c = row_new[\"renew_date\"]\n",
|
||||
" date_obj = datetime.strptime(c, \"%Y-%m-%d\")\n",
|
||||
" new_year = date_obj.year + i_new\n",
|
||||
" new_date_obj = date_obj.replace(year=new_year)\n",
|
||||
" new_c = new_date_obj.strftime(\"%Y-%m-%d\")\n",
|
||||
" row_new[\"renew_date\"] = new_c\n",
|
||||
" # 将当前行添加到新的DataFrame中\n",
|
||||
" new_df = pd.concat([new_df, pd.DataFrame([row_new])], ignore_index=True)\n",
|
||||
"\n",
|
||||
"print(f\"扩展后的新数据量: {len(new_df)} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-01-16T07:39:28.813848800Z",
|
||||
"start_time": "2026-01-16T07:39:28.255902200Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"合并后总数据量: 39599 条\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ========== 合并原始数据和扩展数据 ==========\n",
|
||||
"# 合并两个DataFrame\n",
|
||||
"merged_df = pd.concat([data_NGV, new_df], axis=0, ignore_index=True)\n",
|
||||
"data_details = merged_df.copy() # 替换名称\n",
|
||||
"\n",
|
||||
"data_details_not_null = data_details[data_details['renew_date'].notnull()]\n",
|
||||
"# 重置索引\n",
|
||||
"data_details_not_null = data_details_not_null.reset_index(drop=True)\n",
|
||||
"data_details = data_details_not_null.copy() # 替换名称 v2\n",
|
||||
"\n",
|
||||
"print(f\"合并后总数据量: {len(data_details)} 条\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ========== 最终过滤:排除创建时间等于续约时间的记录 ==========\n",
|
||||
"data_details['saas_create_time'] = data_details['saas_create_time'].str[:4] # 截取前4位(年份)\n",
|
||||
"data_details['renew_date_new'] = data_details['renew_date'].str[:4] # 截取前4位(年份)\n",
|
||||
"data_details = data_details[\n",
|
||||
" data_details['saas_create_time'] != data_details['renew_date_new']] # 过滤掉等于renew_date的行\n",
|
||||
"\n",
|
||||
"data_details = data_details.reset_index(drop=True)\n",
|
||||
"\n",
|
||||
"logger.info(f\"过滤后的数据长度为: {len(data_details)}\")\n",
|
||||
"print(f\"\\n========== 数据处理完成 ==========\")\n",
|
||||
"print(f\"最终数据量: {len(data_details)} 条\")\n",
|
||||
"print(f\"处理完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 数据验证和检查\n",
|
||||
"\n",
|
||||
"可以在这里添加数据验证代码,检查处理结果的正确性\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"start_time": "2026-01-16T09:00:49.656903800Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ========== 数据验证 ==========\n",
|
||||
"# 查看数据基本信息\n",
|
||||
"print(\"数据基本信息:\")\n",
|
||||
"print(f\"数据形状: {data_details.shape}\")\n",
|
||||
"print(f\"\\n数据列名:\")\n",
|
||||
"print(data_details.columns.tolist())\n",
|
||||
"\n",
|
||||
"# 查看前几行数据\n",
|
||||
"print(\"\\n前5行数据:\")\n",
|
||||
"print(data_details.head())\n",
|
||||
"\n",
|
||||
"# 检查关键字段的数据分布\n",
|
||||
"if 'saas_edition_fmt' in data_details.columns:\n",
|
||||
" print(\"\\n版本分布:\")\n",
|
||||
" print(data_details['saas_edition_fmt'].value_counts())\n",
|
||||
"\n",
|
||||
"if 'org_status' in data_details.columns:\n",
|
||||
" print(\"\\n组织状态分布:\")\n",
|
||||
" print(data_details['org_status'].value_counts())\n",
|
||||
"\n",
|
||||
"# 可以保存到CSV文件进行进一步检查\n",
|
||||
"# data_details.to_csv(\"处理后的数据.csv\", index=False, encoding='utf-8-sig')\n",
|
||||
"# print(\"\\n数据已保存到: 处理后的数据.csv\")\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
import pandas as pd
|
||||
import datetime
|
||||
from config import Config
|
||||
from api import API
|
||||
import pymysql # 使用 pymysql 替代 mysql.connector
|
||||
from back_ground_module import CommonModule
|
||||
import os
|
||||
import mysql.connector
|
||||
import pandas as pd
|
||||
import json
|
||||
import numpy as np
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from log_config import configure_task_logger, configure_error_task_logger
|
||||
import math
|
||||
|
||||
logger = configure_task_logger()
|
||||
error_task_logger = configure_error_task_logger()
|
||||
output_dir = "output" # 设置输出目录
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
common_module = CommonModule()
|
||||
api_instance = API()
|
||||
|
||||
|
||||
class ProvinceCityPersonRelationToBI:
|
||||
def __init__(self):
|
||||
self.pvc_data = None
|
||||
self.field_mapping = {
|
||||
"省": "_widget_1734677164861",
|
||||
"市": "_widget_1734677164862",
|
||||
"运营顾问": "_widget_1734677164864",
|
||||
"区域经理": "_widget_1734677164865",
|
||||
"运营专家": "_widget_1734677164866",
|
||||
"战区": "_widget_1734677164867",
|
||||
"新签回访客服": "_widget_1734677164868",
|
||||
"续约回访客服": "_widget_1734677164869",
|
||||
"异常待办客服": "_widget_1734677164870",
|
||||
"日常回访客服": "_widget_1734677164871",
|
||||
}
|
||||
|
||||
def load_all_data(self):
|
||||
payload = {"api_key": "675b900991ad2491c69389ca",
|
||||
"entry_id": "676512ac3e54dc3159460c0a",
|
||||
}
|
||||
pvc_data = api_instance.entry_data_list(payload)
|
||||
self.pvc_data = pvc_data.get("data") # api请求格式,将数据封装在data字典里
|
||||
|
||||
def data_process(self):
|
||||
df = pd.DataFrame(self.pvc_data)
|
||||
# 反转映射字典
|
||||
reverse_mapping = {v: k for k, v in self.field_mapping.items()}
|
||||
# 1.列明替换
|
||||
df.columns = [reverse_mapping.get(col, col) for col in df.columns]
|
||||
|
||||
# 2.成员字段取值
|
||||
user_columns = ["运营顾问", "区域经理", "运营专家", "新签回访客服", "续约回访客服",
|
||||
"异常待办客服", "日常回访客服"]
|
||||
|
||||
for col in user_columns:
|
||||
df[col] = df[col].map(lambda x: x.get("name", "") if isinstance(x, dict) else "")
|
||||
|
||||
# 3.根据省市去重
|
||||
df = df.drop_duplicates(subset=['省', '市'])
|
||||
|
||||
return df
|
||||
|
||||
def clear_table_data(self):
|
||||
"""
|
||||
清空指定 MySQL 表的数据。
|
||||
参数已写死在函数内部,直接调用即可。
|
||||
"""
|
||||
# 数据库连接信息
|
||||
HS_DB_Config = {
|
||||
'host': "f6-public.rwlb.rds.aliyuncs.com",
|
||||
'user': "rw_operation_data_relay",
|
||||
'password': "m+q5Z4%IVuF9bf",
|
||||
'database': "f6operation_data_relay"
|
||||
}
|
||||
table_name = "province_city_person_relation_to_bi" # 要清空的表名
|
||||
|
||||
connection = None
|
||||
try:
|
||||
# 建立数据库连接
|
||||
connection = mysql.connector.connect(
|
||||
host=HS_DB_Config["host"],
|
||||
user=HS_DB_Config["user"],
|
||||
password=HS_DB_Config["password"],
|
||||
database=HS_DB_Config["database"]
|
||||
)
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor()
|
||||
|
||||
# 使用TRUNCATE清空表数据
|
||||
cursor.execute(f"TRUNCATE TABLE {table_name}")
|
||||
connection.commit()
|
||||
|
||||
logger.info(f"成功清空表 {table_name} 中的所有数据")
|
||||
|
||||
except Error as e:
|
||||
error_task_logger.error(f"清空表时发生错误: {e}")
|
||||
if connection and connection.is_connected():
|
||||
connection.rollback()
|
||||
finally:
|
||||
if connection and connection.is_connected():
|
||||
cursor.close()
|
||||
connection.close()
|
||||
logger.info("数据库连接已关闭")
|
||||
|
||||
def write_to_bi(self, df):
|
||||
HS_DB_Config = Config.HS_DB_Config
|
||||
table_name = "province_city_person_relation_to_bi"
|
||||
chunk_size = 1000 # 每批插入 1000 行
|
||||
|
||||
# 清理 DataFrame 中的 NaN/None 等值
|
||||
df = df.replace([None, np.nan, pd.NA, 'nan', 'NaN', 'NAN', ''], None)
|
||||
|
||||
connection = mysql.connector.connect(
|
||||
host=HS_DB_Config["host"],
|
||||
user=HS_DB_Config["user"],
|
||||
password=HS_DB_Config["password"],
|
||||
database=HS_DB_Config["database"]
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
# 获取数据库表的列名
|
||||
cursor.execute(f"SHOW COLUMNS FROM `{table_name}`")
|
||||
db_columns = [col[0] for col in cursor.fetchall()]
|
||||
|
||||
# 保留与数据库匹配的列
|
||||
filtered_df = df[df.columns.intersection(db_columns)]
|
||||
if filtered_df.empty:
|
||||
print("DataFrame 中没有与数据库表结构匹配的列。")
|
||||
return
|
||||
|
||||
# 处理 dict/list 类型字段:转为 JSON 字符串
|
||||
filtered_df = filtered_df.copy()
|
||||
for col in filtered_df.columns:
|
||||
if filtered_df[col].apply(lambda x: isinstance(x, (dict, list)) if x is not None else False).any():
|
||||
filtered_df[col] = filtered_df[col].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else x
|
||||
)
|
||||
|
||||
# 构建 INSERT 语句(只构建一次)
|
||||
columns = [f"`{col}`" for col in filtered_df.columns]
|
||||
placeholders = ', '.join(['%s'] * len(columns))
|
||||
insert_sql = f"INSERT INTO `{table_name}` ({', '.join(columns)}) VALUES ({placeholders})"
|
||||
|
||||
total_rows = len(filtered_df)
|
||||
num_chunks = math.ceil(total_rows / chunk_size)
|
||||
|
||||
for i in range(num_chunks):
|
||||
start_idx = i * chunk_size
|
||||
end_idx = min(start_idx + chunk_size, total_rows)
|
||||
chunk_df = filtered_df.iloc[start_idx:end_idx]
|
||||
|
||||
# 转为元组列表
|
||||
data_to_insert = [
|
||||
tuple(row) for row in chunk_df.values
|
||||
]
|
||||
|
||||
# 批量执行(executemany 更高效)
|
||||
cursor.executemany(insert_sql, data_to_insert)
|
||||
|
||||
connection.commit()
|
||||
logger.info(f"成功写入 {total_rows} 条记录到 {table_name} 表中(分 {num_chunks} 批)。")
|
||||
|
||||
except Exception as e:
|
||||
error_task_logger.error(f"写入数据库时发生错误: {e}", exc_info=True)
|
||||
connection.rollback()
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
def main(self):
|
||||
task_start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
try:
|
||||
logger.info("任务开始")
|
||||
# step1: 获取数据
|
||||
self.load_all_data()
|
||||
logger.info("加载数据完成")
|
||||
# step2:数据处理
|
||||
df = self.data_process()
|
||||
# df.to_csv(os.path.join(output_dir, "new_dealer_service_order_to_bi.csv"))
|
||||
logger.info("数据处理完成")
|
||||
# step3:数据库删除
|
||||
self.clear_table_data()
|
||||
logger.info("目标数据库已清空")
|
||||
# step4:数据写入BI
|
||||
self.write_to_bi(df)
|
||||
logger.info("数据已写入数据库中")
|
||||
common_module.send_task_status(task_start_time, "省市区人员关系表转BI")
|
||||
except Exception as e:
|
||||
error_task_logger.error(f"省市区人员关系表转BI发生错误{e}")
|
||||
common_module.send_task_error(task_start_time, "省市区人员关系表转BI", str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
province_city_person_relation_to_bi = ProvinceCityPersonRelationToBI()
|
||||
province_city_person_relation_to_bi.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
import requests
|
||||
import pandas as pd
|
||||
|
||||
cookies = {
|
||||
'auth_token': 's%3A.9uztgExtmqUJXHCi00hv9SGq6eVYSvH%2BxQSwrox1Yls',
|
||||
'fx-lang': 'zh_cn',
|
||||
'tenantId': 'agndqbuttb7ipfciraxcokgqyu',
|
||||
'AGL_USER_ID': 'a50da526-dd43-4a78-ace1-ba810a6f2168',
|
||||
'_ga': 'GA1.1.626243428.1772260541',
|
||||
'_clck': 'y7ldwu%5E2%5Eg3y%5E0%5E2250',
|
||||
'_ga_JTDW9M3LHZ': 'GS2.1.s1772266909$o3$g0$t1772266909$j60$l0$h0',
|
||||
'sensorsdata2015jssdkcross': '%7B%22distinct_id%22%3A%2257956c24ceedab0c48c17b4e%22%2C%22first_id%22%3A%2219b6dd1a10c148a-063e3a033b10ed-4c657b58-2073600-19b6dd1a10d145b%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTliNmRkMWExMGMxNDhhLTA2M2UzYTAzM2IxMGVkLTRjNjU3YjU4LTIwNzM2MDAtMTliNmRkMWExMGQxNDViIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiNTc5NTZjMjRjZWVkYWIwYzQ4YzE3YjRlIn0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%2257956c24ceedab0c48c17b4e%22%7D%7D',
|
||||
'sensorsdata2015jssdkcross': '%7B%22distinct_id%22%3A%2257956c24ceedab0c48c17b4e%22%2C%22first_id%22%3A%2219b6dd1a10c148a-063e3a033b10ed-4c657b58-2073600-19b6dd1a10d145b%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTliNmRkMWExMGMxNDhhLTA2M2UzYTAzM2IxMGVkLTRjNjU3YjU4LTIwNzM2MDAtMTliNmRkMWExMGQxNDViIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiNTc5NTZjMjRjZWVkYWIwYzQ4YzE3YjRlIn0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%2257956c24ceedab0c48c17b4e%22%7D%7D',
|
||||
'_csrf': 's%3AE3ildFF7aOT874ca1lQaPpJh.M4vLvJ9TdzyukOLVlNNYO9ysSRLlssqxQh1%2FwMawRCY',
|
||||
'Hm_lvt_de47dd1629940fe88b02865de93dd9fe': '1774410585,1774417748,1774487358,1774503122',
|
||||
'HMACCOUNT': 'A6A0585E8C70051D',
|
||||
'Hm_lpvt_de47dd1629940fe88b02865de93dd9fe': '1774503273',
|
||||
'GSuvNKHqfvX2r6v7P8HkZv2bow': 's%3ALj5VHNEzUxprmpIJHWpNg7SzUp7GR3H1.yp8HeYMA0ARAeFC0CqxucSfBxvudd4xO76ZwAACWvTc',
|
||||
'JDY_SID': 's%3AZrD40YQAsP9Ui53M1j0lud3AvwbdgzEl.ljjCkitsu2tDtbx0kJTROsKeNh8QfX2mz6SehH%2B%2FD5E',
|
||||
}
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
'content-type': 'application/json',
|
||||
'origin': 'https://www.jiandaoyun.com',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.jiandaoyun.com/dashboard/app/675b900991ad2491c69389ca/form/6931063d64187eaf6b927557/edit',
|
||||
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
|
||||
'x-csrf-token': 'Mape7z8g-8iFP_vxFm8qSzVprTb4XsGfvs1o',
|
||||
'x-jdy-ver': '10.19.6',
|
||||
'x-request-id': '7bb1d48a-0fdb-46b7-bf67-ebd52c1a1f9a',
|
||||
# 'cookie': 'auth_token=s%3A.9uztgExtmqUJXHCi00hv9SGq6eVYSvH%2BxQSwrox1Yls; fx-lang=zh_cn; tenantId=agndqbuttb7ipfciraxcokgqyu; AGL_USER_ID=a50da526-dd43-4a78-ace1-ba810a6f2168; _ga=GA1.1.626243428.1772260541; _clck=y7ldwu%5E2%5Eg3y%5E0%5E2250; _ga_JTDW9M3LHZ=GS2.1.s1772266909$o3$g0$t1772266909$j60$l0$h0; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2257956c24ceedab0c48c17b4e%22%2C%22first_id%22%3A%2219b6dd1a10c148a-063e3a033b10ed-4c657b58-2073600-19b6dd1a10d145b%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTliNmRkMWExMGMxNDhhLTA2M2UzYTAzM2IxMGVkLTRjNjU3YjU4LTIwNzM2MDAtMTliNmRkMWExMGQxNDViIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiNTc5NTZjMjRjZWVkYWIwYzQ4YzE3YjRlIn0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%2257956c24ceedab0c48c17b4e%22%7D%7D; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2257956c24ceedab0c48c17b4e%22%2C%22first_id%22%3A%2219b6dd1a10c148a-063e3a033b10ed-4c657b58-2073600-19b6dd1a10d145b%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTliNmRkMWExMGMxNDhhLTA2M2UzYTAzM2IxMGVkLTRjNjU3YjU4LTIwNzM2MDAtMTliNmRkMWExMGQxNDViIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiNTc5NTZjMjRjZWVkYWIwYzQ4YzE3YjRlIn0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%2257956c24ceedab0c48c17b4e%22%7D%7D; _csrf=s%3AE3ildFF7aOT874ca1lQaPpJh.M4vLvJ9TdzyukOLVlNNYO9ysSRLlssqxQh1%2FwMawRCY; Hm_lvt_de47dd1629940fe88b02865de93dd9fe=1774410585,1774417748,1774487358,1774503122; HMACCOUNT=A6A0585E8C70051D; Hm_lpvt_de47dd1629940fe88b02865de93dd9fe=1774503273; GSuvNKHqfvX2r6v7P8HkZv2bow=s%3ALj5VHNEzUxprmpIJHWpNg7SzUp7GR3H1.yp8HeYMA0ARAeFC0CqxucSfBxvudd4xO76ZwAACWvTc; JDY_SID=s%3AZrD40YQAsP9Ui53M1j0lud3AvwbdgzEl.ljjCkitsu2tDtbx0kJTROsKeNh8QfX2mz6SehH%2B%2FD5E',
|
||||
}
|
||||
|
||||
df = pd.read_excel(fr"C:\Users\hp_z66\Downloads\续约服务流程_20260327115935.xlsx",sheet_name="需要查询补充120天节点数据", dtype=str).fillna("")
|
||||
|
||||
json_data = {
|
||||
'skip': 0,
|
||||
'limit': 20,
|
||||
'appId': '675b900991ad2491c69389ca',
|
||||
'entryId': '6931063d64187eaf6b927557',
|
||||
'formId': '6931063d64187eaf6b927557',
|
||||
'dataId': '69ab8f89837038e4e81bff21',
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
'https://www.jiandaoyun.com/_/admin/data/log/list_changes',
|
||||
cookies=cookies,
|
||||
headers=headers,
|
||||
json=json_data,
|
||||
)
|
||||
@@ -0,0 +1,791 @@
|
||||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
from datetime import datetime, timezone
|
||||
import pandas as pd
|
||||
import os
|
||||
from typing import Dict
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import numpy as np # 导入numpy库用于处理numpy数组
|
||||
|
||||
output_dir = "output" # 设置输出目录
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
|
||||
class Config:
|
||||
JIANDAOYUN_API_TOKEN = 'Bearer qygHulymo1fekJk4CIZyNKjyQAzG8CFN' # token
|
||||
|
||||
|
||||
class API:
|
||||
def entry_data_list(self, data: dict, replace: bool = False, max_retries: int = 20) -> Dict: # 获取多条表单数据
|
||||
"""
|
||||
获取多条表单数据
|
||||
:param max_retries: 最大重试次数
|
||||
:param replace: 是否替换字段
|
||||
:param data:
|
||||
api_key: 应用id
|
||||
entry_id: 表单id
|
||||
:return:
|
||||
"""
|
||||
|
||||
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/list'
|
||||
|
||||
headers = {
|
||||
'Authorization': Config.JIANDAOYUN_API_TOKEN, # 曹伟应用api测试 app_key
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
all_data_batches = [] # 用于存储每次请求返回的数据批次
|
||||
last_data_id = None
|
||||
exit_flag = False
|
||||
while True:
|
||||
payload = json.dumps({
|
||||
"app_id": data['api_key'], # 应用ID
|
||||
"entry_id": data['entry_id'], # 表单ID
|
||||
"limit": 90,
|
||||
"data_id": last_data_id,
|
||||
"filter": data.get('filter', None)
|
||||
})
|
||||
retries = 0
|
||||
while retries <= max_retries:
|
||||
try:
|
||||
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
||||
res.raise_for_status() # 检查HTTP响应状态码,如果不等于200会抛出异常
|
||||
data_get = res.json()
|
||||
if data_get["data"]:
|
||||
all_data_batches.extend(data_get['data'])
|
||||
last_data_id = data_get['data'][-1].get('_id')
|
||||
print(f"已获取 {len(all_data_batches)} 条数据")
|
||||
break # 成功则跳出循环
|
||||
else:
|
||||
if 'data' not in data_get or len(data_get['data']) == 0:
|
||||
exit_flag = True
|
||||
break
|
||||
# logger.warning(f"请求异常, 将重新请求")
|
||||
retries += 1
|
||||
time.sleep(0.1) # 在重试之间稍作停顿
|
||||
except requests.exceptions.RequestException as e:
|
||||
# logger.warning(f"请求异常: {e}, 将重新请求")
|
||||
retries += 1
|
||||
time.sleep(0.1) # 在重试之间稍作停顿
|
||||
if retries > max_retries:
|
||||
# error_task_logger.error(f"任务 {last_data_id}组 连续{max_retries}次请求失败,放弃此次请求。")
|
||||
all_data_batches.append(None) # 或者可以选择记录失败的payload以便后续处理
|
||||
|
||||
if exit_flag:
|
||||
break
|
||||
|
||||
# 构建最终返回的字典
|
||||
final_data = {
|
||||
'data': all_data_batches # 'data' 键对应的值是列表的列表
|
||||
}
|
||||
# logger.info(f"获取了{len(all_data_batches)}条数据")
|
||||
if replace:
|
||||
print("进行了替换")
|
||||
return_data = self.field_replacement(data, final_data) # 字段替换,由id替换为标签名
|
||||
|
||||
return return_data
|
||||
else:
|
||||
return final_data
|
||||
|
||||
def field_replacement(self, data: dict, data_get: dict) -> dict:
|
||||
"""
|
||||
字段替换,将id替换为标签名,即唯一值替换为表单中显示字段的名字
|
||||
:param data: 简道云插件发送过来的data,包含表单id、数据id、应用id
|
||||
:param data_get: 简道云请求的数据,一般是根据数据id获取到表单的数据
|
||||
:return: 将根据数据id获取到的表单数据,进行替换,返回替换后的数据
|
||||
"""
|
||||
|
||||
# 获取表单对应字段标签名称
|
||||
widget_list = self.entry_widget_list(data)
|
||||
|
||||
# 检查widget_list是否有效
|
||||
if not widget_list or 'widgets' not in widget_list or not isinstance(widget_list['widgets'], list):
|
||||
raise ValueError("映射表没有接受到数据")
|
||||
|
||||
# 创建一个映射表,将_widget_名称映射到label
|
||||
name_to_label = {widget['name']: widget['label'] for widget in widget_list['widgets']}
|
||||
|
||||
def replace_keys(obj):
|
||||
"""递归替换字典中的键名"""
|
||||
if isinstance(obj, dict):
|
||||
new_dict = {}
|
||||
for key, value in obj.items():
|
||||
new_key = name_to_label.get(key, key)
|
||||
new_dict[new_key] = replace_keys(value)
|
||||
return new_dict
|
||||
elif isinstance(obj, list):
|
||||
return [replace_keys(item) for item in obj]
|
||||
else:
|
||||
return obj
|
||||
|
||||
# 复制 data_get,避免修改原始数据
|
||||
data_get_copy = json.loads(json.dumps(data_get)) # 深拷贝
|
||||
|
||||
if 'data' in data_get_copy:
|
||||
data_get_copy['data'] = replace_keys(data_get_copy['data'])
|
||||
|
||||
return data_get_copy
|
||||
|
||||
@staticmethod
|
||||
def workflow_instance_get(data: dict, max_retries: int = 20) -> dict:
|
||||
"""
|
||||
查询实例流程信息
|
||||
:param max_retries:
|
||||
:param data: 简道云插件发送过来的data,包含应用id
|
||||
:return: 查询简道云流程实例信息返回的结果
|
||||
"""
|
||||
url = 'https://api.jiandaoyun.com/api/v6/workflow/instance/get'
|
||||
|
||||
headers = {
|
||||
'Authorization': Config.JIANDAOYUN_API_TOKEN, # 曹伟应用api测试 appKey
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
payload = json.dumps({
|
||||
"instance_id": data['data_id'],
|
||||
"tasks_type": 1
|
||||
}
|
||||
)
|
||||
print("payload:", payload)
|
||||
data_get = None
|
||||
retries = 0
|
||||
while retries <= max_retries:
|
||||
try:
|
||||
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
||||
# res.raise_for_status() # 检查HTTP响应状态码,如果不等于200会抛出异常
|
||||
data_get = res.json()
|
||||
# print( "返回结果:", data_get)
|
||||
if res.status_code == 200:
|
||||
break # 成功则跳出循环
|
||||
else:
|
||||
# logger.warning(f"请求异常, 将重新请求")
|
||||
retries += 1
|
||||
time.sleep(3) # 在重试之间稍作停顿
|
||||
except requests.exceptions.RequestException as e:
|
||||
# logger.warning(f"请求异常: {e}, 将重新请求")
|
||||
retries += 1
|
||||
time.sleep(0.1) # 在重试之间稍作停顿
|
||||
if retries > max_retries:
|
||||
# error_task_logger.error(f"任务 {data['data_id']} 连续{max_retries}次请求失败,放弃此次请求。")
|
||||
print("请求失败")
|
||||
|
||||
return data_get
|
||||
|
||||
@staticmethod
|
||||
def entry_data_update(data: dict, max_retries: int = 20) -> dict: # 修改数据
|
||||
"""
|
||||
修改数据
|
||||
:param max_retries: 最大重试次数,此处设置100次
|
||||
:param data: 简道云插件发送过来的data,包含应用id、表单id、数据id等信息
|
||||
:return: 修改数据后简道云返回的结果
|
||||
"""
|
||||
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/update'
|
||||
|
||||
headers = {
|
||||
'Authorization': Config.JIANDAOYUN_API_TOKEN, # 曹伟应用api测试 appKey
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
payload = json.dumps({
|
||||
"app_id": data['api_key'], # 应用ID
|
||||
"entry_id": data['entry_id'], # 表单ID
|
||||
"data_id": data['data_id'], # 数据ID
|
||||
"data": data['data'],
|
||||
"is_start_trigger": True
|
||||
}
|
||||
)
|
||||
|
||||
data_get = None
|
||||
retries = 0
|
||||
while retries <= max_retries:
|
||||
try:
|
||||
res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
||||
res.raise_for_status() # 检查HTTP响应状态码,如果不等于200会抛出异常
|
||||
data_get = res.json()
|
||||
# print(data_get)
|
||||
if res.status_code == 200:
|
||||
break # 成功则跳出循环
|
||||
else:
|
||||
# logger.warning(f"请求异常, 将重新请求")
|
||||
retries += 1
|
||||
time.sleep(3) # 在重试之间稍作停顿
|
||||
except requests.exceptions.RequestException as e:
|
||||
# logger.warning(f"请求异常: {e}, 将重新请求")
|
||||
retries += 1
|
||||
time.sleep(10) # 在重试之间稍作停顿
|
||||
if retries > max_retries:
|
||||
# error_task_logger.error(f"任务 {data['data_id']} 连续{max_retries}次请求失败,放弃此次请求。")
|
||||
continue
|
||||
return data_get
|
||||
|
||||
@staticmethod
|
||||
def workflow_instance_end(data: dict, max_retries: int = 20) -> dict:
|
||||
"""
|
||||
关闭流程
|
||||
:param max_retries:
|
||||
:param data: 简道云插件发送过来的data,包含应用id
|
||||
:return: 查询简道云流程实例信息返回的结果
|
||||
"""
|
||||
url = 'https://api.jiandaoyun.com/api/v1/workflow/instance/close'
|
||||
|
||||
headers = {
|
||||
'Authorization': Config.JIANDAOYUN_API_TOKEN, # 曹伟应用api测试 appKey
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
payload = json.dumps({
|
||||
"instance_id": data['data_id'],
|
||||
}
|
||||
)
|
||||
print("payload:", payload)
|
||||
data_get = None
|
||||
retries = 0
|
||||
while retries <= max_retries:
|
||||
try:
|
||||
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
||||
# res.raise_for_status() # 检查HTTP响应状态码,如果不等于200会抛出异常
|
||||
data_get = res.json()
|
||||
print("返回结果:", data_get)
|
||||
if res.status_code == 200:
|
||||
break # 成功则跳出循环
|
||||
else:
|
||||
|
||||
retries += 1
|
||||
time.sleep(3) # 在重试之间稍作停顿
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
retries += 1
|
||||
time.sleep(0.1) # 在重试之间稍作停顿
|
||||
|
||||
return data_get
|
||||
|
||||
|
||||
class NpEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, np.integer):
|
||||
return int(obj)
|
||||
elif isinstance(obj, np.floating):
|
||||
return float(obj)
|
||||
elif isinstance(obj, np.ndarray):
|
||||
return obj.tolist()
|
||||
else:
|
||||
return super(NpEncoder, self).default(obj)
|
||||
|
||||
|
||||
class YDAPI:
|
||||
appKey = "ding5kqocon5s9oph5uq"
|
||||
appSecret = "HL1jgsIIfLAC0eTH0A1m4mwxUDqbgsiPeCCGGE3ocM6qJBTIW7Ivt9drxF_Z4Kb_"
|
||||
|
||||
@staticmethod
|
||||
def get_ids_query(token, formUuid, appType, systemToken, formInstanceIdList=None, max_retries=10, delay=2):
|
||||
"""
|
||||
函数功能:读取表单的所有数据,并加入重试机制。
|
||||
|
||||
Args:
|
||||
token (str): 登录验证token,用于API调用的身份验证。
|
||||
formUuid (str): 表单唯一标识符,用于指定需要读取哪个表单的实例数据。
|
||||
page (int): 分页参数,指定请求的数据页码。
|
||||
n (int): 每页显示的数据条数。
|
||||
appType (str): 应用类型标识符,默认为 "APP_UYZ0KG6L0CCNV80GZ66O"
|
||||
systemToken (str): 系统token,默认为固定值
|
||||
instanceStatus (str): 流程实例状态,默认为"RUNNING"
|
||||
max_retries (int): 最大重试次数,默认为10次
|
||||
delay (int): 每次重试之间的延迟秒数,默认为2秒
|
||||
|
||||
Returns:
|
||||
dict: 返回从API获取的流程表单实例数据的JSON解析结果。
|
||||
|
||||
Raises:
|
||||
Exception: 如果达到最大重试次数仍未成功,则抛出异常。
|
||||
"""
|
||||
|
||||
attempt = 0
|
||||
api = f'https://api.dingtalk.com/v1.0/yida/forms/instances/ids/query'
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-acs-dingtalk-access-token": token
|
||||
}
|
||||
formData = {
|
||||
"appType": appType,
|
||||
"systemToken": systemToken,
|
||||
"userId": "yida_pub_account", # 超级管理员账号
|
||||
"language": "zh_CN",
|
||||
"formUuid": formUuid,
|
||||
"formInstanceIdList": formInstanceIdList,
|
||||
}
|
||||
# print(formData)
|
||||
|
||||
while True:
|
||||
if attempt >= max_retries:
|
||||
break
|
||||
|
||||
try:
|
||||
res = requests.post(api, headers=headers, json=formData)
|
||||
# print(res.json())
|
||||
res.raise_for_status() # 如果返回状态码不是2xx,抛出异常
|
||||
return res.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
time.sleep(delay)
|
||||
attempt += 1
|
||||
|
||||
def generateToken(self) -> str:
|
||||
"""
|
||||
函数功能:生成访问令牌(token)
|
||||
|
||||
Returns:
|
||||
str: 返回生成的访问令牌字符串。此token用于后续API调用的身份验证。
|
||||
"""
|
||||
token_api = 'https://api.dingtalk.com/v1.0/oauth2/accessToken'
|
||||
data = {
|
||||
"appKey": f"{self.appKey}",
|
||||
"appSecret": f'{self.appSecret}'
|
||||
}
|
||||
res = requests.post(token_api, json=data)
|
||||
token = res.json().get('accessToken')
|
||||
return token
|
||||
|
||||
def read_processes_instances(self, token, formUuid, page, n, appType="APP_UYZ0KG6L0CCNV80GZ66O",
|
||||
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", instanceStatus="RUNNING",
|
||||
max_retries=10, delay=2, createFromTimeGMT=None, createToTimeGMT=None,
|
||||
modifiedFromTimeGMT=None,
|
||||
modifiedToTimeGMT=None, searchFieldJson={}):
|
||||
"""
|
||||
函数功能:读取流程表单的所有数据,并加入重试机制。
|
||||
|
||||
Args:
|
||||
token (str): 登录验证token,用于API调用的身份验证。
|
||||
formUuid (str): 表单唯一标识符,用于指定需要读取哪个表单的实例数据。
|
||||
page (int): 分页参数,指定请求的数据页码。
|
||||
n (int): 每页显示的数据条数。
|
||||
appType (str): 应用类型标识符,默认为 "APP_UYZ0KG6L0CCNV80GZ66O"
|
||||
systemToken (str): 系统token,默认为固定值
|
||||
instanceStatus (str): 流程实例状态,默认为"RUNNING"
|
||||
max_retries (int): 最大重试次数,默认为10次
|
||||
delay (int): 每次重试之间的延迟秒数,默认为2秒
|
||||
|
||||
Returns:
|
||||
dict: 返回从API获取的流程表单实例数据的JSON解析结果。
|
||||
|
||||
Raises:
|
||||
Exception: 如果达到最大重试次数仍未成功,则抛出异常。
|
||||
"""
|
||||
|
||||
attempt = 0
|
||||
api = f'https://api.dingtalk.com/v1.0/yida/processes/instances?pageNumber={page}&pageSize={n}'
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-acs-dingtalk-access-token": token
|
||||
}
|
||||
formData = {
|
||||
"appType": appType,
|
||||
"systemToken": systemToken,
|
||||
"userId": "yida_pub_account", # 超级管理员账号
|
||||
"language": "zh_CN",
|
||||
"formUuid": formUuid,
|
||||
"instanceStatus": instanceStatus, # 运行中
|
||||
"createFromTimeGMT": createFromTimeGMT,
|
||||
"createToTimeGMT": createToTimeGMT,
|
||||
"modifiedFromTimeGMT": modifiedFromTimeGMT,
|
||||
"modifiedToTimeGMT": modifiedToTimeGMT,
|
||||
"searchFieldJson": json.dumps(
|
||||
searchFieldJson
|
||||
)
|
||||
}
|
||||
# print(formData)
|
||||
|
||||
while True:
|
||||
if attempt >= max_retries:
|
||||
# error_task_logger.error(f"请求失败,已达最大重试次数 {max_retries},无法获取流程实例数据,跳过本次请求。")
|
||||
break
|
||||
|
||||
try:
|
||||
res = requests.post(api, headers=headers, json=formData)
|
||||
# print(res.json())
|
||||
res.raise_for_status() # 如果返回状态码不是2xx,抛出异常
|
||||
return res.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
# logger.warning(f"请求异常: {e},正在尝试第 {attempt + 1} 次重试...")
|
||||
time.sleep(delay)
|
||||
attempt += 1
|
||||
|
||||
def update_from(self, token, formInstanceId, data_new):
|
||||
"""
|
||||
函数功能:更新表单内容
|
||||
|
||||
Args:
|
||||
token (str): 登录验证token,用于API调用的身份验证。
|
||||
formInstanceId (str): 表单实例ID,读文件获取。
|
||||
data_new (dict): 新的数据内容,用于替换现有表单实例中的数据。读文件获取。
|
||||
|
||||
Returns:
|
||||
Response: 返回API请求的响应对象。
|
||||
"""
|
||||
|
||||
api = f'https://api.dingtalk.com//v1.0/yida/forms/instances'
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-acs-dingtalk-access-token": token
|
||||
}
|
||||
|
||||
payload = {
|
||||
"appType": "APP_UYZ0KG6L0CCNV80GZ66O",
|
||||
"systemToken": "XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2",
|
||||
"userId": "yida_pub_account", # 曹伟 id
|
||||
"language": "zh_CN",
|
||||
"useLatestVersion": "false",
|
||||
"formInstanceId": formInstanceId,
|
||||
"updateFormDataJson": json.dumps(data_new, cls=NpEncoder),
|
||||
|
||||
}
|
||||
|
||||
res = requests.put(api, headers=headers, json=payload)
|
||||
return res
|
||||
|
||||
def get_approval_records(self, token: str, processInstanceId: str, appType="APP_UYZ0KG6L0CCNV80GZ66O",
|
||||
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", max_retries=10, delay=2):
|
||||
"""
|
||||
函数功能:获取流程表单的审批记录,适用于"F6客户服务"应用,并且包含重试机制。
|
||||
|
||||
Args:
|
||||
token (str): 登录验证token,用于API调用的身份验证。
|
||||
processInstanceId (str): 流程实例ID,用于标识需要获取审批记录的具体流程实例。
|
||||
appType (str): 应用类型标识符,默认为 "APP_UYZ0KG6L0CCNV80GZ66O"
|
||||
systemToken (str): 系统token,默认为固定值
|
||||
max_retries (int): 最大重试次数,默认为10次
|
||||
delay (int): 每次重试之间的延迟秒数,默认为2秒
|
||||
|
||||
Returns:
|
||||
dict: 返回从API获取的审批记录的JSON解析结果。通常包括审批步骤、审批人、审批时间等信息。
|
||||
"""
|
||||
attempt = 0
|
||||
userId = "yida_pub_account"
|
||||
api = f'https://api.dingtalk.com/v1.0/yida/processes/operationRecords?appType={appType}&systemToken={systemToken}&userId={userId}&language=zh_CN&processInstanceId={processInstanceId}'
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-acs-dingtalk-access-token": token
|
||||
}
|
||||
|
||||
while True:
|
||||
if attempt >= max_retries:
|
||||
# error_task_logger.error(f"请求失败,已达最大重试次数 {max_retries},无法获取审批数据,跳过本次请求。")
|
||||
break
|
||||
|
||||
try:
|
||||
res = requests.get(api, headers=headers)
|
||||
res.raise_for_status() # 如果响应状态码不是2xx,则抛出HTTPError
|
||||
return res.json()
|
||||
except (requests.exceptions.RequestException, Exception) as e:
|
||||
# logger.warning(f"请求出现异常: {e}, 正在重试({attempt + 1}/{max_retries})...")
|
||||
time.sleep(delay) # 等待指定的延迟时间后再次尝试
|
||||
attempt += 1
|
||||
|
||||
def aggree_approval(self, token: str, taskId: str, processInstanceId: str, formData: dict, res_new):
|
||||
"""_summary_
|
||||
|
||||
函数功能:同意审批节点 --F6客户服务 应用
|
||||
|
||||
Args:
|
||||
token (str): 登录验证token
|
||||
taskId (str): 获取到的审批节点ID
|
||||
processInstanceId (str): 读取文件获得的实例ID
|
||||
formData (dict): 数据样式
|
||||
res_new (响应值): 从员工ID表里获取到员工名对应的员工ID
|
||||
|
||||
Returns:
|
||||
响应值: 返回请求结果
|
||||
"""
|
||||
api = 'https://api.dingtalk.com/v1.0/yida/tasks/execute'
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-acs-dingtalk-access-token": token
|
||||
}
|
||||
payload = {
|
||||
"outResult": "AGREE",
|
||||
"appType": "APP_UYZ0KG6L0CCNV80GZ66O",
|
||||
"systemToken": "XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2",
|
||||
"remark": "同意(接口自动)",
|
||||
"formDataJson": json.dumps(formData, cls=NpEncoder),
|
||||
"processInstanceId": processInstanceId,
|
||||
"userId": res_new,
|
||||
"language": "zh_CN",
|
||||
"taskId": int(taskId)
|
||||
}
|
||||
|
||||
res = requests.post(api, headers=headers, json=payload)
|
||||
return res
|
||||
|
||||
|
||||
api_instance = API()
|
||||
yd_api_instance = YDAPI()
|
||||
|
||||
|
||||
class YDToJDYRenewalToDo(object):
|
||||
def __init__(self):
|
||||
self.FORMID = "FORM-PE866MD1MJMU0WGLYRFLYEN5YN9L1I55Z7ZUK22"
|
||||
self.appType = "APP_UYZ0KG6L0CCNV80GZ66O"
|
||||
self.systemToken = "XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2"
|
||||
self.token = yd_api_instance.generateToken()
|
||||
|
||||
def load_all_data(self):
|
||||
end_dt = datetime.now() + timedelta(days=1)
|
||||
start_dt = end_dt - timedelta(days=90)
|
||||
start_time = start_dt.strftime("%Y-%m-%d")
|
||||
end_time = end_dt.strftime("%Y-%m-%d")
|
||||
|
||||
yd_data = yd_api_instance.read_processes_instances(
|
||||
token=self.token,
|
||||
formUuid=self.FORMID,
|
||||
page=1,
|
||||
n=100,
|
||||
appType=self.appType,
|
||||
systemToken=self.systemToken,
|
||||
instanceStatus="",
|
||||
modifiedFromTimeGMT=start_time,
|
||||
modifiedToTimeGMT=end_time,
|
||||
)
|
||||
|
||||
all_process_list = []
|
||||
|
||||
PAGES_two = yd_data.get('totalCount') // 100 + 1
|
||||
|
||||
for a in tqdm(range(1, PAGES_two + 1)):
|
||||
try:
|
||||
yd_data = yd_api_instance.read_processes_instances(
|
||||
token=self.token,
|
||||
formUuid=self.FORMID,
|
||||
page=a,
|
||||
n=100,
|
||||
appType=self.appType,
|
||||
systemToken=self.systemToken,
|
||||
instanceStatus="",
|
||||
modifiedFromTimeGMT=start_time,
|
||||
modifiedToTimeGMT=end_time,
|
||||
)
|
||||
all_process_list = all_process_list + yd_data.get("data")
|
||||
except Exception as e:
|
||||
print(f"获取流程实例数据时出错: {e}")
|
||||
continue
|
||||
|
||||
df_current = pd.DataFrame(all_process_list)
|
||||
current_file = f"{output_dir}/{start_time}_{end_time}_all_process_list.csv"
|
||||
|
||||
# === 新增:读取上次文件并计算差值 ===
|
||||
if os.path.exists(current_file):
|
||||
try:
|
||||
df_last = pd.read_csv(current_file)
|
||||
except Exception as e:
|
||||
print(f"读取历史文件失败: {e}")
|
||||
df_last = pd.DataFrame()
|
||||
else:
|
||||
df_last = None # 明确标记:无历史文件
|
||||
|
||||
id_col = 'processInstanceId'
|
||||
|
||||
if df_last is not None and not df_last.empty and not df_current.empty and id_col in df_current.columns and id_col in df_last.columns:
|
||||
# 有历史文件,计算新增
|
||||
last_ids = set(df_last[id_col].astype(str))
|
||||
current_ids = set(df_current[id_col].astype(str))
|
||||
new_ids = current_ids - last_ids
|
||||
diff_records = df_current[df_current[id_col].astype(str).isin(new_ids)].to_dict('records')
|
||||
else:
|
||||
# 没有历史文件 或 无法比对 → 返回全部当前数据
|
||||
diff_records = df_current.to_dict('records') # ← 关键修改点
|
||||
# 保存当前全量数据(覆盖)
|
||||
df_current.to_csv(current_file, index=False)
|
||||
|
||||
return diff_records
|
||||
|
||||
def filter_renewal_data(self, all_process_list):
|
||||
update_data_list = []
|
||||
for item in all_process_list:
|
||||
if item.get("data").get("textField_kto3q3ev"):
|
||||
org_id = item.get("data").get("textField_ksydghqw")
|
||||
order_code = item.get("data").get("textField_kto3q3ev") # 订单编码
|
||||
pay_time = item.get("data").get("dateField_kto3q3ex") # 订单支付日期
|
||||
|
||||
res = yd_api_instance.get_ids_query(
|
||||
token=self.token,
|
||||
formInstanceIdList=[item.get("processInstanceId")],
|
||||
formUuid="FORM-PE866MD1MJMU0WGLYRFLYEN5YN9L1I55Z7ZUK22",
|
||||
appType="APP_UYZ0KG6L0CCNV80GZ66O",
|
||||
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2",
|
||||
)
|
||||
|
||||
payment_amount = None
|
||||
bussiness_type = None
|
||||
# print(res)
|
||||
if res.get("result"):
|
||||
form_data = res["result"][0]["formData"]
|
||||
payment_amount = form_data.get("textField_kyjy1kkm") # "9987"
|
||||
bussiness_type = form_data.get("textField_kyjy1kkn") # "续约"
|
||||
# payment_amount = res.get("data").get("textField_kyjy1kkm") # 支付金额
|
||||
# bussiness_type = res.get("data").get("textField_kyjy1kkn") # 业务类型
|
||||
|
||||
if pay_time:
|
||||
# 确保是整数
|
||||
timestamp_ms = int(pay_time)
|
||||
# 转为 UTC datetime 对象
|
||||
pay_datetime_utc = datetime.fromtimestamp(timestamp_ms / 1000, tz=timezone.utc)
|
||||
print(pay_datetime_utc) # 例如: 2024-04-05 12:34:38.901000+00:00
|
||||
else:
|
||||
pay_datetime_utc = None
|
||||
|
||||
update_data_list.append({
|
||||
"order_code": order_code,
|
||||
"pay_time": pay_time,
|
||||
"payment_amount": payment_amount,
|
||||
"bussiness_type": bussiness_type,
|
||||
"org_id": org_id
|
||||
})
|
||||
return update_data_list
|
||||
|
||||
def check_jd_ydy_data(self, update_data_list):
|
||||
def extract_widget_value(record, widget_id):
|
||||
value = record.get(widget_id)
|
||||
if isinstance(value, dict) and "value" in value:
|
||||
return value.get("value")
|
||||
return value
|
||||
|
||||
def has_value(value):
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, str):
|
||||
return value.strip() != ""
|
||||
if isinstance(value, (list, dict)):
|
||||
return len(value) > 0
|
||||
return True
|
||||
|
||||
def pick_best_record(records):
|
||||
if not records:
|
||||
return None
|
||||
|
||||
time_fields = [
|
||||
"_updateTime",
|
||||
"_updated_at",
|
||||
"_createTime",
|
||||
"_created_at",
|
||||
"updateTime",
|
||||
"updated_at",
|
||||
"createTime",
|
||||
"created_at",
|
||||
]
|
||||
|
||||
def extract_sort_value(record):
|
||||
for field in time_fields:
|
||||
value = record.get(field)
|
||||
if value is None:
|
||||
continue
|
||||
try:
|
||||
return int(value)
|
||||
except Exception:
|
||||
try:
|
||||
return int(float(value))
|
||||
except Exception:
|
||||
continue
|
||||
return 0
|
||||
|
||||
return max(records, key=extract_sort_value)
|
||||
|
||||
def query_jdy_by_org_id(org_id, flow_state=None):
|
||||
cond = [{
|
||||
"field": "_widget_1764820541661",
|
||||
"type": "text",
|
||||
"method": "eq",
|
||||
"value": [org_id]
|
||||
}]
|
||||
if flow_state is not None:
|
||||
cond.append({
|
||||
"field": "flowState",
|
||||
"type": "flowstate",
|
||||
"method": "eq",
|
||||
"value": [flow_state]
|
||||
})
|
||||
|
||||
payload = {
|
||||
"api_key": "675b900991ad2491c69389ca",
|
||||
"entry_id": "6931063d64187eaf6b927557",
|
||||
"filter": {
|
||||
"rel": "and",
|
||||
"cond": cond
|
||||
}
|
||||
}
|
||||
return api_instance.entry_data_list(payload).get("data", [])
|
||||
|
||||
for item in update_data_list:
|
||||
data_list = query_jdy_by_org_id(item.get("org_id"), flow_state=0)
|
||||
if not data_list:
|
||||
data_list = query_jdy_by_org_id(item.get("org_id"), flow_state=None)
|
||||
|
||||
result = pick_best_record(data_list)
|
||||
data_id = result.get("_id") if result else None
|
||||
|
||||
if not data_id:
|
||||
# print(f"未找到订单 {item.get('order_code')} 的简道云数据,跳过。")
|
||||
continue
|
||||
|
||||
# 查询实例状态
|
||||
instance_status = api_instance.workflow_instance_get({"data_id": data_id})
|
||||
task_status = instance_status.get("status", -1)
|
||||
print(f"订单 {item.get('order_code')} 的简道云流程状态为 {task_status}")
|
||||
|
||||
if task_status == 0:
|
||||
print("简道云流程正在进行中,执行流程关闭")
|
||||
api_instance.workflow_instance_end({"data_id": data_id})
|
||||
|
||||
target_values = {
|
||||
"_widget_1764820541674": item.get("order_code"),
|
||||
"_widget_1764820541679": item.get("pay_time"),
|
||||
"_widget_1764820541676": item.get("payment_amount"),
|
||||
"_widget_1764820541680": item.get("bussiness_type"),
|
||||
}
|
||||
|
||||
data_to_update = {}
|
||||
for widget_id, new_value in target_values.items():
|
||||
existing_value = extract_widget_value(result, widget_id)
|
||||
if has_value(existing_value):
|
||||
continue
|
||||
if not has_value(new_value):
|
||||
continue
|
||||
data_to_update[widget_id] = {"value": new_value}
|
||||
|
||||
if not data_to_update:
|
||||
print(f"订单 {item.get('order_code')} 简道云字段已有值或无可写入内容,跳过同步")
|
||||
continue
|
||||
|
||||
print("开始同步数据")
|
||||
update_payload = {
|
||||
"api_key": "675b900991ad2491c69389ca",
|
||||
"entry_id": "6931063d64187eaf6b927557",
|
||||
"data_id": data_id,
|
||||
"data": data_to_update
|
||||
}
|
||||
print(update_payload)
|
||||
api_instance.entry_data_update(update_payload)
|
||||
print("数据同步完成")
|
||||
|
||||
def main(self):
|
||||
task_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
try:
|
||||
# step1 获取简道云与宜搭数据
|
||||
jd_ydy_data = self.load_all_data()
|
||||
if jd_ydy_data:
|
||||
# step2 过滤已经续约的单子
|
||||
update_data_list = self.filter_renewal_data(jd_ydy_data)
|
||||
# step3 校验简道云是否有进行中的单子并关闭
|
||||
self.check_jd_ydy_data(update_data_list)
|
||||
else:
|
||||
print("本次执行无处理数据")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
jd_ydy_renewal_to_do = YDToJDYRenewalToDo()
|
||||
jd_ydy_renewal_to_do.main()
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import pandas as pd
|
||||
+67
-15
@@ -233,6 +233,31 @@ class YDAPI:
|
||||
appKey = "ding5kqocon5s9oph5uq"
|
||||
appSecret = "HL1jgsIIfLAC0eTH0A1m4mwxUDqbgsiPeCCGGE3ocM6qJBTIW7Ivt9drxF_Z4Kb_"
|
||||
|
||||
@staticmethod
|
||||
def _to_int_task_id(task_id):
|
||||
if task_id is None:
|
||||
raise ValueError("taskId is None")
|
||||
if isinstance(task_id, bool):
|
||||
raise ValueError(f"taskId is bool: {task_id}")
|
||||
if isinstance(task_id, int):
|
||||
return task_id
|
||||
if isinstance(task_id, float):
|
||||
if task_id.is_integer():
|
||||
return int(task_id)
|
||||
raise ValueError(f"taskId is non-integer float: {task_id}")
|
||||
if isinstance(task_id, str):
|
||||
s = task_id.strip()
|
||||
if s.isdigit() or (s.startswith("-") and s[1:].isdigit()):
|
||||
return int(s)
|
||||
try:
|
||||
f = float(s)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"taskId is not numeric: {task_id}") from e
|
||||
if f.is_integer():
|
||||
return int(f)
|
||||
raise ValueError(f"taskId is non-integer numeric string: {task_id}")
|
||||
raise ValueError(f"taskId has unsupported type: {type(task_id).__name__}")
|
||||
|
||||
def generateToken(self) -> str:
|
||||
"""
|
||||
函数功能:生成访问令牌(token)
|
||||
@@ -301,10 +326,10 @@ class YDAPI:
|
||||
while True:
|
||||
if attempt >= max_retries:
|
||||
# error_task_logger.error(f"请求失败,已达最大重试次数 {max_retries},无法获取流程实例数据,跳过本次请求。")
|
||||
break
|
||||
return {"data": []}
|
||||
|
||||
try:
|
||||
res = requests.post(api, headers=headers, json=formData)
|
||||
res = requests.post(api, headers=headers, json=formData, timeout=15)
|
||||
# print(res.json())
|
||||
res.raise_for_status() # 如果返回状态码不是2xx,抛出异常
|
||||
return res.json()
|
||||
@@ -313,6 +338,11 @@ class YDAPI:
|
||||
# logger.warning(f"请求异常: {e},正在尝试第 {attempt + 1} 次重试...")
|
||||
time.sleep(delay)
|
||||
attempt += 1
|
||||
except Exception:
|
||||
time.sleep(delay)
|
||||
attempt += 1
|
||||
|
||||
return {"data": []}
|
||||
|
||||
def update_from(self, token, formInstanceId, data_new):
|
||||
"""
|
||||
@@ -345,7 +375,7 @@ class YDAPI:
|
||||
|
||||
}
|
||||
|
||||
res = requests.put(api, headers=headers, json=payload)
|
||||
res = requests.put(api, headers=headers, json=payload, timeout=15)
|
||||
return res
|
||||
|
||||
def get_approval_records(self, token: str, processInstanceId: str, appType="APP_UYZ0KG6L0CCNV80GZ66O",
|
||||
@@ -375,16 +405,17 @@ class YDAPI:
|
||||
while True:
|
||||
if attempt >= max_retries:
|
||||
# error_task_logger.error(f"请求失败,已达最大重试次数 {max_retries},无法获取审批数据,跳过本次请求。")
|
||||
break
|
||||
return {"result": []}
|
||||
|
||||
try:
|
||||
res = requests.get(api, headers=headers)
|
||||
res = requests.get(api, headers=headers, timeout=15)
|
||||
res.raise_for_status() # 如果响应状态码不是2xx,则抛出HTTPError
|
||||
return res.json()
|
||||
except (requests.exceptions.RequestException, Exception) as e:
|
||||
# logger.warning(f"请求出现异常: {e}, 正在重试({attempt + 1}/{max_retries})...")
|
||||
time.sleep(delay) # 等待指定的延迟时间后再次尝试
|
||||
attempt += 1
|
||||
return {"result": []}
|
||||
|
||||
def aggree_approval(self, token: str, taskId: str, processInstanceId: str, formData: dict, res_new):
|
||||
"""_summary_
|
||||
@@ -415,10 +446,10 @@ class YDAPI:
|
||||
"processInstanceId": processInstanceId,
|
||||
"userId": res_new,
|
||||
"language": "zh_CN",
|
||||
"taskId": int(taskId)
|
||||
"taskId": self._to_int_task_id(taskId)
|
||||
}
|
||||
|
||||
res = requests.post(api, headers=headers, json=payload)
|
||||
res = requests.post(api, headers=headers, json=payload, timeout=15)
|
||||
return res
|
||||
|
||||
|
||||
@@ -453,6 +484,18 @@ class JDYToYDRenewalToDo(object):
|
||||
"数据ID": "_id"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _as_list(val):
|
||||
if val is None:
|
||||
return []
|
||||
if isinstance(val, list):
|
||||
return val
|
||||
if isinstance(val, tuple):
|
||||
return list(val)
|
||||
if isinstance(val, dict):
|
||||
return [val]
|
||||
return []
|
||||
|
||||
def load_all_data(self):
|
||||
# 获取简道云已派发续约待办,若无数据直接返回
|
||||
today_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
@@ -464,7 +507,7 @@ class JDYToYDRenewalToDo(object):
|
||||
"value": ["否"]}]},
|
||||
}
|
||||
renewal = api_instance.entry_data_list(payload)
|
||||
self.renewal_data_list = renewal.get("data") or []
|
||||
self.renewal_data_list = self._as_list((renewal or {}).get("data"))
|
||||
if not self.renewal_data_list:
|
||||
self.renewal_data_df = pd.DataFrame()
|
||||
return
|
||||
@@ -479,7 +522,7 @@ class JDYToYDRenewalToDo(object):
|
||||
|
||||
all_data = []
|
||||
for _, row in self.renewal_data_df.iterrows():
|
||||
yd_data = yd_api_instance.read_processes_instances(
|
||||
yd_resp = yd_api_instance.read_processes_instances(
|
||||
token=self.token,
|
||||
formUuid=self.FORMID,
|
||||
page=1,
|
||||
@@ -488,9 +531,12 @@ class JDYToYDRenewalToDo(object):
|
||||
systemToken=self.systemToken,
|
||||
instanceStatus="",
|
||||
searchFieldJson={"textField_ksydghqw": row["_widget_1764820541661"]},
|
||||
).get("data", [])
|
||||
)
|
||||
yd_data = self._as_list((yd_resp or {}).get("data"))
|
||||
|
||||
for record in yd_data:
|
||||
if not isinstance(record, dict):
|
||||
continue
|
||||
enriched = {**record}
|
||||
enriched.update({k: row.get(v, "") for k, v in self.follow_up_fields.items()})
|
||||
all_data.append(enriched)
|
||||
@@ -705,20 +751,20 @@ class JDYToYDRenewalToDo(object):
|
||||
data = {"data_id": item["数据ID"]}
|
||||
jdy_workflow_data = api_instance.workflow_instance_get(data) or {}
|
||||
print("简道云流程日志:", jdy_workflow_data)
|
||||
jdy_logs = jdy_workflow_data.get("logs", [])
|
||||
jdy_logs = self._as_list(jdy_workflow_data.get("logs"))
|
||||
jdy_logs = sorted(
|
||||
jdy_logs,
|
||||
key=lambda x: parse_dt(x.get("finish_time") or x.get("create_time")),
|
||||
reverse=True,
|
||||
)
|
||||
tasks = jdy_workflow_data.get("tasks", []) or []
|
||||
tasks = self._as_list(jdy_workflow_data.get("tasks"))
|
||||
pending = [t for t in tasks if t.get("status") == 0]
|
||||
task_candidate = (pending[0] if pending else None) or (sorted(
|
||||
tasks,
|
||||
key=lambda x: parse_dt(x.get("create_time")),
|
||||
reverse=True
|
||||
)[0] if tasks else {})
|
||||
jdy_result_records = jdy_workflow_data.get("result", []) or []
|
||||
jdy_result_records = self._as_list(jdy_workflow_data.get("result"))
|
||||
|
||||
jdy_stage_candidates = [
|
||||
extract_stage_from_text(str(jdy_logs[0].get("flow_name", ""))) if jdy_logs else None,
|
||||
@@ -741,7 +787,7 @@ class JDYToYDRenewalToDo(object):
|
||||
systemToken=self.systemToken
|
||||
) or {}
|
||||
print("宜搭流程日志:", yd_workflow_data)
|
||||
yd_results = yd_workflow_data.get("result", []) or []
|
||||
yd_results = self._as_list(yd_workflow_data.get("result"))
|
||||
|
||||
# 展开宜搭 domainList 以获取所有动作
|
||||
yd_records = [
|
||||
@@ -785,6 +831,11 @@ class JDYToYDRenewalToDo(object):
|
||||
if not task_id or not operator_user_id:
|
||||
print(f"缺少 taskId 或 operatorUserId,无法自动同意 processInstanceId={item['processInstanceId']}")
|
||||
continue
|
||||
try:
|
||||
task_id = yd_api_instance._to_int_task_id(task_id)
|
||||
except Exception as e:
|
||||
print(f"taskId 非法,跳过自动同意 processInstanceId={item['processInstanceId']} taskId={task_id} err={e}")
|
||||
continue
|
||||
try:
|
||||
yd_api_instance.aggree_approval(
|
||||
token=self.token,
|
||||
@@ -799,10 +850,11 @@ class JDYToYDRenewalToDo(object):
|
||||
|
||||
def retrun_jdy(self, yd_update_list):
|
||||
for item in yd_update_list:
|
||||
data_id= str(item.get('数据ID'))
|
||||
data = {
|
||||
"api_key": "675b900991ad2491c69389ca",
|
||||
"entry_id": "6931063d64187eaf6b927557",
|
||||
"data_id": item.get('数据ID'),
|
||||
"data_id": data_id,
|
||||
"data":
|
||||
{
|
||||
"_widget_1766469131897": {"value": "是"},
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": "## 保存boss请求结果",
|
||||
"id": "311a82d4faf8e2d"
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-11-10T06:24:23.858755Z",
|
||||
"start_time": "2025-11-10T06:24:22.994108Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"# 标准库\n",
|
||||
"import os\n",
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import json\n",
|
||||
"import binascii\n",
|
||||
"from datetime import date, timedelta, datetime\n",
|
||||
"from urllib.parse import quote\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"# 第三方库\n",
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import requests\n",
|
||||
"from pyDes import des, CBC, PAD_PKCS5\n",
|
||||
"import mysql.connector\n",
|
||||
"from mysql.connector import Error\n",
|
||||
"\n",
|
||||
"# PostgreSQL(如果你用到了)\n",
|
||||
"import psycopg2\n",
|
||||
"\n",
|
||||
"# 自定义模块\n",
|
||||
"from config import Config\n",
|
||||
"from api import API\n",
|
||||
"from back_ground_module import CommonModule\n",
|
||||
"from log_config import configure_task_logger, configure_error_task_logger\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"logger = configure_task_logger()\n",
|
||||
"error_task_logger = configure_error_task_logger()\n",
|
||||
"api_instance = API()\n",
|
||||
"common_module = CommonModule()\n",
|
||||
"output_dir = \"output\" # 设置输出目录\n",
|
||||
"os.makedirs(output_dir, exist_ok=True)\n",
|
||||
"\n",
|
||||
"def des_encrypt(s):\n",
|
||||
" \"\"\"\n",
|
||||
" DES 加密\n",
|
||||
" :param s: 原始字符串\n",
|
||||
" :return: 加密后字符串,16进制\n",
|
||||
" \"\"\"\n",
|
||||
" secret_key = 'HwdMBW8o'\n",
|
||||
" iv = secret_key\n",
|
||||
" k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)\n",
|
||||
" en = k.encrypt(s, padmode=PAD_PKCS5)\n",
|
||||
" return binascii.b2a_base64(en, newline=False)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def des_descrypt(s):\n",
|
||||
" \"\"\"\n",
|
||||
" DES 解密\n",
|
||||
" :param s: 加密后的字符串,16进制\n",
|
||||
" :return: 解密后的字符串\n",
|
||||
" \"\"\"\n",
|
||||
" secret_key = 'HwdMBW8o'\n",
|
||||
" iv = secret_key\n",
|
||||
" k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)\n",
|
||||
" de = k.decrypt(binascii.a2b_base64(s), padmode=PAD_PKCS5)\n",
|
||||
" return de\n",
|
||||
"\n",
|
||||
"data_NGV = common_module.get_renewal_details()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"for i in range(0,len(data_NGV[\"date_fmt\"])):\n",
|
||||
" t = time.time()\n",
|
||||
" ts = int(round(t * 1000))\n",
|
||||
" randint = random.randint(100000000, 999999999)\n",
|
||||
" req = data_NGV['id_own_org'][i] + \"_\" + str(ts) + \"_\" + str(randint)\n",
|
||||
" str_en = des_encrypt(req)\n",
|
||||
" req_new = str_en.decode('utf-8')\n",
|
||||
"\n",
|
||||
" url = f\"http://manage.f6yc.com/hive-admin/py/yida/renewal/orgInfo\"\n",
|
||||
" data = {\n",
|
||||
" 'req':req_new,\n",
|
||||
" 't':ts,\n",
|
||||
" 'r':randint\n",
|
||||
" }\n",
|
||||
" res = requests.post(url,data=data)\n",
|
||||
" # print(res.json.json())\n",
|
||||
"\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
"print(len(data_NGV))"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"距离今天还有120天的日期是:2026-03-10\n",
|
||||
"29\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 2
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
,_id,提交人,updater,deleter,提交时间,更新时间,deleteTime,flowState,报备类型,协作内容,情况说明,订单编号,年限,版本,实付金额,商品名称,履约金额,门店编码,门店名称,支付日期,开户/处理日期,业绩归属日期,业绩类型,公司名称,公司ID,报备业绩金额-区域提交,业绩归属小六-区域提交,业绩归属月,是否同步衡石,小六业绩金额,区域业绩金额,报备业绩归属小六,报备业绩归属区域经理,报备业绩归属大区,原业绩归属人,原业绩归属区域经理,原业绩归属大区,小六业绩比例,区域业绩比例,运营专家,业绩动作,提成类型,新签阶段及提成比例,提成金额,SaaS新签提成比例,服务包提成比例,新签提成比例-首年,新签提成比例-非首年,提成动作,业绩类型-聚合,业绩分组,流程是否结束,appId,entryId
|
||||
0,68d1039a408016fe13556b06,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:06:50,2025-12-25 15:32:02,,2,多年补差价,,6月订单,9月补差价2年,1757735155184,2,入门版,999,,,CHS202506010300195,上海汨晨汽车服务有限公司,2025-09-13 00:00:00,2025-06-01 00:00:00,2025-09-13 00:00:00,新签,,,,,,是,999.0,999.0,刘鑫烨,张凯,华南沪,刘鑫烨,张凯,华南沪,1.0,1.0,周聪,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
1,68d103cdb8f662bfdf1b7ea2,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:07:41,2025-12-25 15:32:02,,2,新签超3年,,新签5年,1758076816064,2,标准版,3000,,,CHS202302130204882,江阴市华士爱驹汽车养护店,2025-09-17 00:00:00,2025-09-17 00:00:00,2025-09-17 00:00:00,新签,,,,,,是,3000.0,3000.0,赵旭伟,肖军,江苏,赵旭伟,肖军,江苏,1.0,1.0,陈博,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
2,68d103f17f34705c8dbe360a,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:08:17,2025-12-25 15:32:02,,2,新签超3年,,新签5年,1758181826462,2,基础版,1600,,,CHS202504240297202,古城区鸿远轮胎服务中心,2025-09-18 00:00:00,2025-09-18 00:00:00,2025-09-18 00:00:00,新签,,,,,,是,1600.0,1600.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,崔智杰,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
3,68d10415f3728b4cd791630e,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:08:53,2025-12-25 15:32:02,,2,新签超3年,,5年订单,1758259644403,2,基础版,1759,,,CHS202509190309704,金堂县赵镇四达汽修厂,2025-09-19 00:00:00,2025-09-19 00:00:00,2025-09-19 00:00:00,新签,,,,,,是,1759.0,1759.0,陈致欣,肖军,西南,陈致欣,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
4,68d1042ee3ee1af6d0d7f8ee,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:09:18,2025-12-25 15:32:02,,2,新签超3年,,,1756814144233,2,入门版,1000,,,CHS202509020309092,天津市滨海新区安驰汽车修理服务部,2025-09-03 00:00:00,2025-09-02 00:00:00,2025-09-03 00:00:00,新签,,,,,,是,1000.0,1000.0,王鑫,关磊,华北,王鑫,关磊,华北,1.0,1.0,杨挺,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
5,68d104502618b5ea53f4264f,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:09:52,2025-12-25 15:32:02,,2,新签超3年,,,1757245487196,2,基础版,1400,,,CHS202509070309251,车广角盘锦店,2025-09-08 00:00:00,2025-09-07 00:00:00,2025-09-08 00:00:00,新签,,,,,,是,1400.0,1400.0,韩皞,陈庆伟,东北,韩皞,陈庆伟,东北,1.0,1.0,孙旭亮,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
6,68d10471c1d4a4211d2ce420,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:10:25,2025-12-25 15:32:02,,2,新签超3年,,,1757388589517,1,进阶版,1000,,,CHS202509090309331,上海德伽汽车服务中心,2025-09-09 00:00:00,2025-09-09 00:00:00,2025-09-09 00:00:00,新签,,,,,,是,1000.0,1000.0,刘鑫烨,张凯,华南沪,刘鑫烨,张凯,华南沪,1.0,1.0,周聪,新增,,[],,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
7,68d104a081bf67abc88ce650,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:11:12,2025-12-25 15:32:02,,2,新签超3年,,,1757752521945,2,标准版,3000,,,CHS202509130309507,腾冲诚亿汽车修理有限公司,2025-09-14 00:00:00,2025-09-13 00:00:00,2025-09-14 00:00:00,新签,,,,,,是,3000.0,3000.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,崔智杰,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
8,68d104e0304ea14df52c1128,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:12:16,2025-12-25 15:32:02,,2,品牌方协作,电子目录,电子目录,,,,12000,,,,,,,2025-09-22 16:12:16,,,,,,,是,0.0,0.0,杜浩,肖军,江苏,,,,,,,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
9,68d104fce01701d04e9ba14d,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:12:44,2025-12-25 15:32:02,,2,品牌方协作,电子目录,,,,,8000,,,,,,,2025-09-22 16:12:44,,,,,,,是,0.0,0.0,韩皞,陈庆伟,东北,,,,,,,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
10,68d105177771c4e88d61d725,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-22 16:13:11,2025-12-25 15:32:02,,2,品牌方协作,电子目录,,,,,9000,,,,,,,2025-09-22 16:13:11,,,,,,,是,0.0,0.0,胡楠,景东强,西北,,,,,,,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
11,68d2342ba541a358893d61ef,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-23 13:46:19,2025-12-25 15:32:02,,2,多年补差价,,,1758459953833,2,入门版,1000,,,CHS202505070297933,上海义诚汽车服务有限公司,2025-09-22 00:00:00,2025-05-07 00:00:00,2025-09-22 00:00:00,新签,,,,,,是,1000.0,1000.0,刘鑫烨,张凯,华南沪,刘鑫烨,张凯,华南沪,1.0,1.0,周聪,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
12,68d3bc8d99f9d49450ed8b1a,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-24 17:40:29,2025-12-25 15:32:02,,2,品牌方协作,电子目录,电子目录,小六未参与,,,,9000,,,,,,,2025-09-24 17:40:29,,,,,,,是,0.0,0.0,胡楠,景东强,西北,,,,,,,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
13,68d494d8c6f070c9f68a4e94,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-25 09:03:20,2025-12-25 15:32:02,,2,新签超3年,,,1758612872486,1,基础版,799,,,CHS202509230309865,回民区青辰宝悦汽车维修中心(个体工商户),2025-09-23 00:00:00,2025-09-23 00:00:00,2025-09-23 00:00:00,新签,,,,,,是,799.0,799.0,张宏伟,关磊,华北,张宏伟,关磊,华北,1.0,1.0,武宏超,新增,,[],,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
14,68d5f2a0b3bc5add3c3d7a23,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-26 09:55:44,2025-12-25 15:32:02,,2,新签超3年,,,1758789984175,2,入门版,1000,,,CHS202509250310033,济南双江汽车服务有限公司,2025-09-26 00:00:00,2025-09-25 00:00:00,2025-09-26 00:00:00,新签,,,,,,是,1000.0,1000.0,杨旭,关磊,山东,杨旭,关磊,山东,1.0,1.0,王斌,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
15,68d5f2c3f7765d3eddb8506a,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-26 09:56:19,2025-12-25 15:32:02,,2,多年补差价,,,1758787369355,2,旗舰版,4000,,,CHS202505190299143,政德汽修,2025-09-26 00:00:00,2025-05-19 00:00:00,2025-09-26 00:00:00,新签,,,,,,是,4000.0,4000.0,杨旭,关磊,山东,杨旭,关磊,山东,1.0,1.0,王斌,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
16,68d5f2eb8eb300e7d401f7f9,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-26 09:56:59,2025-12-25 15:32:02,,2,新签超3年,,,1758786185157,2,标准版,3120,,,CHS202509250310019,红河州秀林工贸有限公司,2025-09-26 00:00:00,2025-09-25 00:00:00,2025-09-26 00:00:00,新签,,,,,,是,3120.0,3120.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,崔智杰,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
17,68d8882a40f51cce582eddb5,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-28 08:58:18,2025-12-25 15:32:02,,2,多年补差价,,,1758894296058,2,进阶版,2501,,,CHS202107030131629,灵山县车益汽车维修厂,2025-09-27 00:00:00,2025-08-09 00:00:00,2025-09-27 00:00:00,新签,,,,,,是,2501.0,2501.0,黄环宇,张凯,华南沪,黄环宇,张凯,华南沪,1.0,1.0,黄宗祥,新增,,[],125.05,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
18,68d9e2d9710265914f554379,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-29 09:37:29,2025-12-25 15:32:02,,2,跨区新签,,,1759047159482,3,旗舰版,7000,,,CHS202509280310174,荟星行汽车服务有限公司,2025-09-29 00:00:00,2025-09-28 00:00:00,2025-09-29 00:00:00,新签,,,,,,是,3500.0,3500.0,韩皞,陈庆伟,东北,张宏伟,关磊,华北,0.5,0.5,孙旭亮,拆单,,[],315.0,0.135,,,0.135,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
19,68d9f2549d0de29d680f6403,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-29 10:43:32,2025-12-25 15:32:02,,2,新签超3年,,,1759063075745,2,入门版,920,,,CHS202509280310180,宏运汽修,2025-09-29 00:00:00,2025-09-28 00:00:00,2025-09-29 00:00:00,新签,,,,,,是,920.0,920.0,柴铁峰,陈庆伟,东北,柴铁峰,陈庆伟,东北,1.0,1.0,刘立,新增,,[],46.0,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
20,68db3e73890706b7678f34ea,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-30 10:20:35,2025-12-25 15:32:02,,2,新签超3年,,,1757909706794,2,旗舰版,4400,,,CHS202509150309534,监利市壹加汽车服务有限公司,2025-09-15 00:00:00,2025-09-15 00:00:00,2025-09-15 00:00:00,新签,,,,,,是,4400.0,4400.0,陈煜,景东强,华中,陈煜,景东强,华中,1.0,1.0,刘光春,新增,,[],220.0,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
21,68db50ff8e50807e1e84e8f8,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-09-30 11:39:43,2025-12-25 15:32:02,,2,新签超3年,,,1759039606751,1,标准版,1500,,,CHS202509280310149,鄂尔多斯市心成泰汽车维修服务有限公司,2025-09-28 00:00:00,2025-09-28 00:00:00,2025-09-28 00:00:00,新签,,,,,,是,1500.0,1500.0,张宏伟,关磊,华北,张宏伟,关磊,华北,1.0,1.0,武宏超,新增,,[],0.0,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
22,68e71b9216dd2af696d3c489,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-09 10:18:58,2025-12-25 15:32:02,,2,新签超3年,,,1759573927652,1,基础版,800,,,CHS202303010210121,德系专修,2025-10-05 00:00:00,2025-10-04 00:00:00,2025-10-05 00:00:00,新签,,,,,,是,800.0,800.0,刘磊,关磊,华北,刘磊,关磊,华北,1.0,1.0,,新增,,[],0.0,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
23,68e768c27a4eb1ea616434b0,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-09 15:48:18,2025-12-25 15:32:02,,2,新签超3年,,,1759395920361,2,进阶版,2000,,,CHS202510020310341,官渡区众名汽车维修服务经营部,2025-10-03 00:00:00,2025-10-03 00:00:00,2025-10-03 00:00:00,新签,,,,,,是,2000.0,2000.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,,新增,,[],100.0,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
24,68e9ba0cd69c47d29c2e0972,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-11 09:59:40,2025-12-25 15:32:02,,2,新签超3年,,,1760012463416,1,进阶版,1000,,,CHS202510090310521,新乡骏享汽车销售服务有限公司,2025-10-10 00:00:00,2025-10-10 00:00:00,2025-10-10 00:00:00,新签,,,,,,是,1000.0,1000.0,王兵帅,张凯,河南,王兵帅,张凯,河南,1.0,1.0,邢恒岭,新增,,[],0.0,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
25,68eda452872200f9abed2d5d,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-14 09:16:02,2025-12-25 15:32:02,,2,多年补差价,,,1760180223661,2,进阶版,1999,,,CHS202507090303811,上海洗事临门汽车服务有限公司,2025-10-12 00:00:00,2025-07-09 00:00:00,2025-10-12 00:00:00,新签,,,,,,是,1999.0,1999.0,刘鑫烨,张凯,华南沪,刘鑫烨,张凯,华南沪,1.0,1.0,周聪,新增,,[],99.95,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
26,68f830d486f984a8f5d58949,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-22 09:18:12,2025-12-25 15:32:02,,2,电销业绩统计,,,1760341341750,3,基础版,2900,,,CHS202411020284735,深圳康得新KDX大膜王星级甄选店,2025-10-13 00:00:00,2025-10-13 00:00:00,2025-10-13 00:00:00,新签,,,,,,是,0.0,2900.0,严冬延,张凯,华南沪,耿渝淇,张凯,,,1.0,,新增,,[],130.5,0.135,,,0.135,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
27,68f98a2a9cdcf060fcebf670,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-23 09:51:38,2025-12-25 15:32:02,,2,新签超3年,,,1761122509436,2,进阶版,2000,,,CHS202409190281922,海口小拇指汽车服务,2025-10-23 00:00:00,2025-10-22 00:00:00,2025-10-23 00:00:00,新签,,,,,,是,2000.0,2000.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,,新增,,[],100.0,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
28,68fecdec710ccbf6abfdcf9c,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-27 09:42:04,2025-12-25 15:32:02,,2,新签超3年,,,1761365304042,2,标准版,3200,,,CHS202510250311422,博越汽修,2025-10-25 00:00:00,2025-10-25 00:00:00,2025-10-25 00:00:00,新签,,,,,,是,3200.0,3200.0,王有军,陈庆伟,浙皖,王有军,陈庆伟,浙皖,1.0,1.0,,新增,,[],160.0,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
29,6901795327c25049c38f1e2b,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-29 10:17:55,2025-12-25 15:32:02,,1,新签超3年,,,1761644690009,2,基础版,1600,,,CHS202510280311584,宜良捷驰汽车修理厂,2025-10-29 00:00:00,2025-10-29 00:00:00,2025-10-29 00:00:00,新签,,,,,,是,1600.0,1600.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,,新增,,[],80.0,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
30,690179cf34ed36233a8166fd,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-29 10:19:59,2025-12-25 15:32:02,,1,跨区新签,,,1761611436641,1,旗舰版,4499,,,CHS202510280311527,易道大咖乌鲁木齐东坪店,2025-10-28 00:00:00,2025-10-28 00:00:00,2025-10-28 00:00:00,新签,,,,,,是,2249.5,2249.5,孙振华,景东强,西北,潘志强,张凯,华南沪,0.5,0.5,,拆单,,[],0.0,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
31,69041f3e38fb7000e0ffc32e,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-10-31 10:30:22,2025-12-25 15:32:02,,1,新签超3年,,,1761816948126,2,至尊版,8000,,,CHS202510300312383,意嘉易春天里店,2025-10-31 00:00:00,2025-10-31 00:00:00,2025-10-31 00:00:00,新签,,,,,,是,8000.0,8000.0,范启超,肖军,西南,范启超,肖军,西南,1.0,1.0,,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
32,69095a58a9520f1eeaf2afcb,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-04 09:43:52,2025-12-25 15:32:02,,1,新签超3年,,,1762158897286,2,基础版,5000,续约旗舰版-门店管理系统2年,2500.0,CHS202511030312575,宣威市龙泉汽车有限公司,2025-11-04 00:00:00,2025-11-04 00:00:00,2025-11-04 00:00:00,新签,,,,,,是,5000.0,5000.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
33,690aae75afd572c006769a86,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-05 09:55:01,2025-12-25 15:32:02,,1,新签超3年,,,1762245527558,2,进阶版,2240,续约进阶版2年,1120.0,CHS202312020253454,乐山郭建军,2025-11-05 00:00:00,2025-11-05 00:00:00,2025-11-05 00:00:00,新签,,,,,,是,2240.0,2240.0,陈致欣,肖军,西南,陈致欣,肖军,西南,1.0,1.0,,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
34,690aae91ff9575eb8a758dc3,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-05 09:55:29,2025-12-25 15:32:02,,1,多年补差价,,,1761960739508,2,基础版,1101,续约基础版-门店管理系统2年,550.5,CHS202509230309853,都江堰市爱都汽车维修有限责任公司,2025-11-01 00:00:00,2025-09-23 00:00:00,2025-11-01 00:00:00,新签,,,,,,是,1101.0,1101.0,陈致欣,肖军,西南,陈致欣,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
35,690aaec3f23eb35e6e394e54,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-05 09:56:19,2025-12-25 15:32:02,,1,多年补差价,,,1761955976569,2,基础版,1101,续约基础版-门店管理系统2年,550.5,CHS202509150309531,三越汽车音响,2025-11-01 00:00:00,2025-09-12 00:00:00,2025-11-01 00:00:00,新签,,,,,,是,1101.0,1101.0,陈致欣,肖军,西南,陈致欣,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
36,690c00df267ca78f0a86da76,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-06 09:58:55,2025-12-25 15:32:02,,1,新签超3年,,,1762263761814,2,入门版,1600,续约入门版2年,800.0,CHS202105140124767,茂名市电白区华南汽车维修有限公司,2025-11-05 00:00:00,2025-11-04 00:00:00,2025-11-05 00:00:00,新签,,,,,,是,1600.0,1600.0,严冬延,张凯,华南沪,严冬延,张凯,华南沪,1.0,1.0,黄宗祥,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
37,690d4feafeb41d02c491646b,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-07 09:48:26,2025-12-25 15:32:02,,1,多年补差价,,,1762418825828,2,旗舰版,3599,续约旗舰版-门店管理系统2年,1799.5,CHS202508090305351,山海车服,2025-11-07 00:00:00,2025-08-09 00:00:00,2025-11-07 00:00:00,新签,上海山高海深汽车服务有限公司,15975930111903944708,,,,是,3599.0,3599.0,刘鑫烨,张凯,华南沪,刘鑫烨,张凯,华南沪,1.0,1.0,周聪,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
38,690d5114ebf892f67c83c3d9,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-07 09:53:24,2025-12-25 15:32:02,,1,新签超3年,,,1762401310694,2,进阶版,2240,续约进阶版2年,1120.0,CHS202003250058491,博伟汽修(新南路),2025-11-06 00:00:00,2025-11-06 00:00:00,2025-11-06 00:00:00,新签,博伟汽修,10546443563986851726,,,,是,2240.0,2240.0,胡仲远,肖军,西南,胡仲远,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
39,6915529caca516e7c6c28b71,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-13 11:38:04,2025-12-25 15:32:02,,1,多年补差价,,,1762933140216,2,基础版,1998,续约基础版-门店管理系统2年,999.0,CHS202209140188566,德奥养车,2025-11-12 00:00:00,2025-11-08 00:00:00,2025-11-12 00:00:00,新签,德奥养车,11240984669917483539,,,,是,1998.0,1998.0,陈晨,关磊,华北,陈晨,关磊,华北,1.0,1.0,杨挺,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
40,691a76c508e9a99cb7e65565,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-17 09:13:41,2025-12-25 15:32:02,,1,新签超3年,,,1763100107052,2,标准版,3120,续约标准版-门店管理系统2年,1560.0,CHS202511150313111,云南锡业集团汽车技术服务有限公司,2025-11-14 00:00:00,2025-11-14 00:00:00,2025-11-14 00:00:00,新签,云南锡业集团汽车技术服务(文山服务中心),16011444960305905673,,,,是,3120.0,3120.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,崔智杰,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
41,691c0a70d6f09daa8e0af427,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-18 13:56:00,2025-12-25 15:32:02,,1,多年补差价,,,1763366190883,2,入门版,999,续约入门版2年,499.5,CHS202509020309077,宜嘉养车,2025-11-17 00:00:00,2025-09-02 00:00:00,2025-11-17 00:00:00,新签,宜嘉养车,15984698892155383884,,,,是,999.0,999.0,刘鑫烨,张凯,华南沪,刘鑫烨,张凯,华南沪,1.0,1.0,周聪,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
42,691c0a845f80e95f659f9b26,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-18 13:56:20,2025-12-25 15:32:02,,1,新签超3年,,,1763351228539,1,进阶版,1000,续约进阶版,1000.0,CHS202511170313156,郑州欧汇汽车维修有限公司,2025-11-17 00:00:00,2025-11-17 00:00:00,2025-11-17 00:00:00,新签,郑州欧汇汽车维修有限公司,16012185257256194131,,,,是,1000.0,1000.0,王兵帅,张凯,河南,王兵帅,张凯,河南,1.0,1.0,邢恒岭,新增,,[],,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
43,691c0a9576f2783074f62089,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-18 13:56:37,2025-12-25 15:32:02,,1,新签超3年,,,1763370516248,2,标准版,3120,续约标准版-门店管理系统2年,1560.0,CHS202511170313182,文山万霆新能源科技有限责任公司,2025-11-18 00:00:00,2025-11-18 00:00:00,2025-11-18 00:00:00,新签,文山万霆新能源科技有限责任公司,16012268807246610438,,,,是,3120.0,3120.0,李壮壮,肖军,西南,李壮壮,肖军,西南,1.0,1.0,崔智杰,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
44,691d39d9de406a40ae9c1859,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-19 11:30:33,2025-12-25 15:32:02,,1,多年补差价,,,1763447333680,2,入门版,800,续约入门版2年,400.0,CHS202510070310394,盛发汽车维修保养中心,2025-11-18 00:00:00,2025-10-07 00:00:00,2025-11-18 00:00:00,新签,滨海凯盛汽修养护中心,15997329870740811799,,,,是,800.0,800.0,杜浩,肖军,江苏,杜浩,肖军,江苏,1.0,1.0,霍创业,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
45,691e88e504528289184a3d00,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-20 11:20:05,2025-12-25 15:32:02,,1,多年补差价,,,1763522175031,2,基础版,2300,续约基础版-门店管理系统2年,1150.0,CHS202511190313271,徐宝行汽车服务有限公司,2025-11-19 00:00:00,2025-11-19 00:00:00,2025-11-19 00:00:00,新签,徐州徐宝行汽车服务有限公司,16012883105031421976,,,,是,2300.0,2300.0,赵涛,肖军,江苏,赵涛,肖军,江苏,1.0,1.0,霍创业,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
46,691fdb3878dc061019100add,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-21 11:23:36,2025-12-25 15:32:02,,1,多年补差价,,,1763619899692,2,入门版,700,续约入门版2年,350.0,CHS202510230311351,梵远汽车服务,2025-11-20 00:00:00,2025-10-23 00:00:00,2025-11-20 00:00:00,新签,南京市雨花台区梵远汽车配件销售中心,16003167838416171074,,,,是,700.0,700.0,杜浩,肖军,江苏,杜浩,肖军,江苏,1.0,1.0,霍创业,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
47,6927e49b21b02c48ca7bb1f5,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-27 13:41:47,2025-12-25 15:32:02,,1,多年补差价,,,1764149126832,2,进阶版,1799,续约进阶版2年,899.5,CHS202511080312789,鼎鹏汽车服务中心,2025-11-27 00:00:00,2025-11-08 00:00:00,2025-11-27 00:00:00,新签,贵阳市云岩区鼎鹏汽车养护中心,16008912692744061003,,,,是,1799.0,1799.0,熊斌,肖军,西南,熊斌,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
48,6928ffbe82767255aabb44be,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-28 09:49:50,2025-12-25 15:32:02,,1,品牌方协作,电子目录,,,,,18000,,,,,,,2025-11-27 00:00:00,新签,,,,,,是,9000.0,18000.0,梁柱,张凯,华南沪,,,,0.5,1.0,黄宗祥,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
49,6928fff076d8fe5abdd61266,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-28 09:50:40,2025-12-25 15:32:02,,1,新签超3年,,,1762590806733,2,基础版,1600,续约基础版-门店管理系统2年,800.0,CHS202511080312804,华营昌检车线店,2025-11-09 00:00:00,2025-11-08 00:00:00,2025-11-09 00:00:00,新签,阳泉华营昌汽修连锁,10907434497378123878,,,,是,1600.0,1600.0,刘剑桥,关磊,华北,刘剑桥,关磊,华北,1.0,1.0,杨挺,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
50,692900560e8ab9d6604193f2,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-28 09:52:22,2025-12-25 15:32:02,,1,品牌方协作,电子目录,,,,,5000,,,,,,,2025-11-07 00:00:00,新签,,,,,,是,2500.0,5000.0,赵旭伟,肖军,江苏,,,,0.5,1.0,陈博,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
51,6929055f9e94d391e9cd53dd,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-11-28 10:13:51,2025-12-25 15:32:02,,1,新签超3年,,,1762739543304,2,入门版,1200,续约入门版2年,600.0,CHS202511090312841,小欢汽车维修,2025-11-10 00:00:00,2025-11-10 00:00:00,2025-11-10 00:00:00,新签,义县稍户营子镇小欢汽车维修处(个体工商户),16009461720019927075,,,,是,1200.0,1200.0,韩皞,陈庆伟,东北,韩皞,陈庆伟,东北,1.0,1.0,孙旭亮,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
52,692cfbd8da792279e268feef,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-01 10:22:16,2025-12-25 15:32:02,,1,多年补差价,,,1764382941584,2,入门版,800,续约入门版2年,400.0,CHS202510170310927,盐城市大丰区辰煜汽车服务有限公司,2025-11-29 00:00:00,2025-10-18 00:00:00,2025-11-29 00:00:00,新签,盐城市大丰区辰煜汽车服务有限公司,10546443563782178538,,,,是,800.0,800.0,杜浩,肖军,江苏,杜浩,肖军,江苏,1.0,1.0,霍创业,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
53,692cfbe7ff1b578004611ddc,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-01 10:22:31,2025-12-25 15:32:02,,1,新签超3年,,,1764333612264,2,标准版,3600,续约标准版-门店管理系统2年,1800.0,CHS202511280313670,厦门市鑫车宝汽车服务有限公司,2025-11-29 00:00:00,2025-11-28 00:00:00,2025-11-29 00:00:00,新签,厦门市鑫车宝汽车服务有限公司,16016206505594359823,,,,是,3600.0,3600.0,郭锦城,张凯,华南沪,郭锦城,张凯,华南沪,1.0,1.0,周聪,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
54,692cfc00bc219a1741c27ffc,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-01 10:22:56,2025-12-25 15:32:02,,1,多年补差价,,,1764502026835,2,进阶版,2001,续约进阶版2年,1000.5,CHS202511250313539,天门市小拇指汽车维修服务有限公司,2025-12-01 00:00:00,2025-11-26 00:00:00,2025-12-01 00:00:00,新签,天门市小拇指汽车维修服务有限公司,16015175963579027532,,,,是,2001.0,2001.0,陈煜,景东强,华中,陈煜,景东强,华中,1.0,1.0,刘光春,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
55,6938df09cc655df92928b5b3,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-10 10:46:33,2025-12-25 15:32:02,,1,品牌方协作,电子目录,,,,,8000,,,,,,,2025-12-10 00:00:00,新签,,,8000,,2025-12-01 00:00:00,是,4000.0,8000.0,孙旭亮,陈庆伟,东北,,,,0.5,1.0,孙旭亮,新增,,[],,,,,,无,电子目录,电子目录,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
56,69391bbdcdfe09bce4c98cd7,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-10 15:05:33,2025-12-25 15:32:02,,1,新签超3年,,,1764473175419,2,进阶版,2000,续约进阶版2年,1000.0,CHS202511300313735,唐山市路南新腾云汽车维修服务站,2025-11-30 00:00:00,2025-11-30 00:00:00,2025-11-30 00:00:00,新签,唐山市路南新腾云汽车维修服务站,16016880445115371607,,,,是,2000.0,2000.0,王鑫,关磊,华北,王鑫,关磊,华北,1.0,1.0,杨挺,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
57,693b7c9a25d3e9c841729ed3,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-12 10:23:22,2025-12-25 15:32:02,,1,新签超3年,,,1765420934693,2,入门版,1000,续约入门版2年,500.0,CHS202512110314232,西昌安宁美车度汽修,2025-12-11 00:00:00,2025-12-11 00:00:00,2025-12-11 00:00:00,新签,西昌安宁美车度汽车喷漆中心,16020868221515104344,,,,是,1000.0,1000.0,杨君毅,肖军,西南,杨君毅,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
58,693f6a268691ed316215299b,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-15 09:53:42,2025-12-25 15:32:02,,1,跨区新签,,,1765158079178,3,基础版,3000,基础版-门店管理系统3年,1000.0,CHS202512080314089,鹊大师(大同店),2025-12-08 00:00:00,2025-12-08 00:00:00,2025-12-08 00:00:00,新签,广之源汽车一站式服务中心,16000651004727029792,,,,是,1500.0,1500.0,张宏伟,关磊,华北,韩皞,关磊,东北,0.5,0.5,杨挺,拆单,,[],,0.135,,,0.135,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
59,693f6a41b051e5517a47eed5,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-15 09:54:09,2025-12-25 15:32:02,,1,跨区新签,,,1765358368741,3,至尊版,11997,至尊版-门店管理系统3年,3999.0,CHS202512100314217,鞍山尊荣万顺汽车销售有限公司,2025-12-10 00:00:00,2025-12-10 00:00:00,2025-12-10 00:00:00,新签,鞍山尊荣万顺汽车销售有限公司,16020612536361586766,,,,是,5998.5,5998.5,宋小涛,陈庆伟,东北,刘磊,陈庆伟,华北,0.5,0.5,孙旭亮,拆单,,[],,0.135,,,0.135,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
60,693f6a522b2bc18aef5bec96,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-15 09:54:26,2025-12-25 15:32:02,,1,多年补差价,,,1765259877744,2,进阶版,2500,续约进阶版2年,1250.0,CHS202509170309603,平湖市万里汽车大修厂,2025-12-09 00:00:00,2025-09-17 00:00:00,2025-12-09 00:00:00,新签,平湖市万里汽车大修厂,15990048390801023028,,,,是,2500.0,2500.0,王有军,陈庆伟,浙皖,王有军,陈庆伟,浙皖,1.0,1.0,魏子淇,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
61,6948aa20b9b289e0b6b0a8d1,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-22 10:17:04,2025-12-25 15:32:02,,1,新签超3年,,,1766292766298,2,入门版,800,续约入门版2年,400.0,CHS202512210314689,博晏爱玩车维修中心,2025-12-21 00:00:00,2025-12-21 00:00:00,2025-12-21 00:00:00,新签,义县稍户营子镇博晏爱玩车汽车服务维修中心,16024514498417168447,,,,是,800.0,800.0,韩皞,陈庆伟,东北,韩皞,陈庆伟,东北,1.0,1.0,孙旭亮,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
62,6948abbc111dbba0cb0d4fd9,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-22 10:23:56,2025-12-25 15:32:02,,1,多年补差价,,,1766125609196,2,至尊版,8999,续约至尊版-门店管理系统2年,4499.5,CHS202506260302952,烟台福泰汽车服务有限公司,2025-12-19 00:00:00,2025-06-26 00:00:00,2025-12-19 00:00:00,新签,福泰汽车服务有限公司,11240984669918394572,,,,是,8999.0,8999.0,宗川涵,关磊,山东,宗川涵,关磊,山东,1.0,1.0,王斌,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
63,6949f9571fc4fc20b158497f,"{'name': '陈庆伟', 'username': '025366033037741985', 'status': 1, 'type': 0, 'departments': [122311528, 122229573], 'integrate_id': '025366033037741985'}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-23 10:07:19,2025-12-30 17:35:22,,1,跨区新签,,该单是刘磊跨区浙江的客户,王蔚东培训的,关磊已经让小六把业绩和提成都给王蔚东了,100%算王蔚东的,1765263063272,1,基础版,1199,基础版-门店管理系统,1199.0,CHS202512090314165,舟山市于瑞汽车修理有限公司,2025-12-09 00:00:00,2025-12-09 00:00:00,2025-12-09 00:00:00,新签,舟山市于瑞汽车修理有限公司,16020236055869423684,1199,,2025-12-01 00:00:00,否,599.5,599.5,刘磊,关磊,华北,刘磊,陈庆伟,华北,0.5,0.5,魏子淇,拆单,,[],,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
64,694a09358daf16de8df90b12,"{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-23 11:15:01,2025-12-25 15:32:02,,1,新签超3年,,,1766389436606,1,旗舰版,2125,续约旗舰版-门店管理系统,2125.0,CHS202512220314748,PM汽车服务,2025-12-22 00:00:00,2025-12-22 00:00:00,2025-12-22 00:00:00,新签,PM汽车服务,16024929481844097081,2125,,2025-12-01 00:00:00,是,2125.0,2125.0,杨旭,关磊,山东,杨旭,关磊,山东,1.0,1.0,王斌,新增,,[],,0.0,,,0.0,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
65,694b80373f7c91e02f438916,"{'name': '张凯', 'username': '1525590028775887', 'status': 1, 'type': 0, 'departments': [122283546, 127595486, 122514491], 'integrate_id': '1525590028775887'}","{'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}",,2025-12-24 13:55:03,2025-12-25 15:32:02,,1,多年补差价,,新签一个月补1599两年,共计3198三年基础版,1766474568560,2,基础版,1599,续约基础版-门店管理系统2年,799.5,CHS202512070314056,贺州市德宝汽车养护有限公司,2025-12-23 00:00:00,2025-12-07 00:00:00,2025-12-23 00:00:00,新签,贺州市德宝汽车养护有限公司,16019465729355059235,1599,黄宗祥,2025-12-01 00:00:00,是,1599.0,1599.0,黄宗祥,张凯,华南沪,黄宗祥,张凯,华南沪,1.0,1.0,黄宗祥,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
66,694cfd1d990c750feb7392f8,"{'name': '景东强', 'username': '232229053125844557', 'status': 1, 'type': 0, 'departments': [122472424, 122503482], 'integrate_id': '232229053125844557'}","{'name': '金鹏', 'username': '02545960101197726', 'status': 1, 'type': 0, 'departments': [448339551], 'integrate_id': '02545960101197726'}",,2025-12-25 17:00:13,2025-12-26 09:45:45,,1,多年补差价,,此客户约定进阶版3年3899元;11月8号付款一年进阶版1999元,12月25日补尾款1900元。胡冰区域,1766652296888,2,进阶版,1900,续约进阶版2年,950.0,CHS202511080312811,宜春晴天汽车服务有限公司,2025-12-25 00:00:00,2025-11-08 00:00:00,2025-12-25 00:00:00,新签,宜春晴天汽车服务有限公司,16009000270293930057,1900,胡冰,2025-12-01 00:00:00,是,1900.0,1900.0,胡冰,景东强,华中,胡冰,景东强,华中,1.0,1.0,金华斌,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
67,69536f0a92ee436c9af5e238,"{'name': '肖军', 'username': '311003461041349', 'status': 1, 'type': 0, 'departments': [122314630, 122323520], 'integrate_id': '311003461041349'}","{'name': '金鹏', 'username': '02545960101197726', 'status': 1, 'type': 0, 'departments': [448339551], 'integrate_id': '02545960101197726'}",,2025-12-30 14:19:54,2025-12-31 10:05:48,,1,多年补差价,,,1767075482440,2,基础版,1101,续约基础版-门店管理系统2年,550.5,CHS202511170313151,成都鑫城南汽车,2025-12-30 00:00:00,2025-11-17 00:00:00,2025-12-30 00:00:00,新签,成都鑫城南汽车,16012170912208031813,1101,陈致欣,2025-12-01 00:00:00,是,1101.0,1101.0,陈致欣,肖军,西南,陈致欣,肖军,西南,1.0,1.0,吴间锐,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
68,6953889d92e8e1b524429a46,"{'name': '景东强', 'username': '232229053125844557', 'status': 1, 'type': 0, 'departments': [122472424, 122503482], 'integrate_id': '232229053125844557'}","{'name': '金鹏', 'username': '02545960101197726', 'status': 1, 'type': 0, 'departments': [448339551], 'integrate_id': '02545960101197726'}",,2025-12-30 16:09:01,2025-12-30 16:15:23,,1,跨区新签,,此客户在孙婷婷区域,但由于客户单店体量较大,于是让陈煜介入洽谈,故业绩陈煜一半9000元。孙婷婷一半9000元。,1765503409986,3,至尊版,17997,至尊版-门店管理系统3年,5999.0,CHS202512120314279,武汉酷卡驰汽车科技有限公司,2025-12-12 00:00:00,2025-12-12 00:00:00,2025-12-12 00:00:00,新签,武汉酷卡驰汽车科技有限公司,16021219284734738438,18000,陈煜,2025-12-01 00:00:00,是,8998.5,8998.5,孙婷婷,景东强,华中,陈煜,景东强,华中,0.5,0.5,刘光春,拆单,,[],,0.135,,,0.135,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
69,6953bd5a65e053d760df4b80,"{'name': '陈庆伟', 'username': '025366033037741985', 'status': 1, 'type': 0, 'departments': [122311528, 122229573], 'integrate_id': '025366033037741985'}","{'name': '金鹏', 'username': '02545960101197726', 'status': 1, 'type': 0, 'departments': [448339551], 'integrate_id': '02545960101197726'}",,2025-12-30 19:54:02,2025-12-31 09:04:54,,1,多年补差价,,新签3个月内补差价,多年购,1766986879209001,2,进阶版,1600,续约进阶版2年,800.0,CHS202510120310664,鹤岗市南山区鑫宏海中外汽车维修站,2025-12-29 00:00:00,2025-10-12 00:00:00,2025-12-29 00:00:00,新签,鹤岗市南山区鑫宏海中外汽车维修站,15999259585890246734,1600,韩皞,2025-12-01 00:00:00,是,1600.0,1600.0,韩皞,陈庆伟,东北,韩皞,陈庆伟,东北,1.0,1.0,刘立,新增,,[],,0.1,,,0.1,无,新签,当月新签开户,是,66b9678280b37f8a276b1d01,68886b7c0382a7249ae0b5d6
|
||||
@@ -0,0 +1,326 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pandas as pd
|
||||
import datetime
|
||||
from config import Config
|
||||
from api import API
|
||||
import pymysql # 使用 pymysql 替代 mysql.connector
|
||||
from back_ground_module import CommonModule
|
||||
import os
|
||||
import mysql.connector
|
||||
import pandas as pd
|
||||
import json
|
||||
import numpy as np
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from log_config import configure_task_logger, configure_error_task_logger
|
||||
|
||||
logger = configure_task_logger()
|
||||
error_task_logger = configure_error_task_logger()
|
||||
api_instance = API()
|
||||
common_module = CommonModule()
|
||||
output_dir = "output" # 设置输出目录
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
|
||||
class NonStandardPerformanceToBI:
|
||||
""" 非标业绩提报转BI"""
|
||||
def __init__(self):
|
||||
self.dealer_service_data = None
|
||||
self.field_mapping = {
|
||||
"报备类型": "_widget_1753770875899",
|
||||
"协作内容": "_widget_1753770875915",
|
||||
"情况说明": "_widget_1753770875944",
|
||||
"订单编号": "_widget_1753770875887",
|
||||
"实付金额": "_widget_1753770875889",
|
||||
"门店编码": "_widget_1753770875890",
|
||||
"门店名称": "_widget_1753770875888",
|
||||
"版本": "_widget_1753770875891",
|
||||
"年限": "_widget_1753948745953",
|
||||
"支付日期": "_widget_1753770875893",
|
||||
"开户/处理日期": "_widget_1753770875894",
|
||||
"小六业绩金额": "_widget_1753770875898",
|
||||
"区域业绩金额": "_widget_1753770875937",
|
||||
"报备业绩归属区域经理": "_widget_1753770875903",
|
||||
"报备业绩归属大区": "_widget_1753866196486",
|
||||
"原业绩归属人": "_widget_1753856032683",
|
||||
"原业绩归属区域经理": "_widget_1753866196485",
|
||||
"小六业绩比例": "_widget_1753770875917",
|
||||
"区域业绩比例": "_widget_1753770875921",
|
||||
"运营专家": "_widget_1753770875902",
|
||||
"提成类型": "_widget_1753778922504",
|
||||
"SaaS新签提成比例": "_widget_1753770875949",
|
||||
"服务包提成比例": "_widget_1753778922567",
|
||||
"提成金额": "_widget_1753770875948",
|
||||
"新签提成比例-首年": "_widget_1753778922503",
|
||||
"新签提成比例-非首年": "_widget_1753778922548",
|
||||
"新签阶段及提成比例": "_widget_1753778656359",
|
||||
"业绩动作":"_widget_1756708722933",
|
||||
"提成动作":"_widget_1756708722932",
|
||||
"新签阶段及提成比例.选择提成阶段": "_widget_1753778656359._widget_1753778656361",
|
||||
"新签阶段及提成比例.新签阶段": "_widget_1753778656359._widget_1753948745962",
|
||||
"新签阶段及提成比例.提成比例": "_widget_1753778656359._widget_1753778656362",
|
||||
"业绩类型":"_widget_1753770875966",
|
||||
"报备业绩归属小六":"_widget_1753770875901",
|
||||
"原业绩归属大区":"_widget_1755159216098",
|
||||
"业绩分类":"_widget_1758706882564",
|
||||
"流程是否结束":"_widget_1761633418013",
|
||||
"业绩类型-聚合":"_widget_1758706882564",
|
||||
"业绩分组":"_widget_1762417447169",
|
||||
"商品名称":"_widget_1762219744898",
|
||||
"履约金额":"_widget_1762220516367",
|
||||
"业绩归属日期":"_widget_1762417447127",
|
||||
"公司名称":"_widget_1762420723743",
|
||||
"公司ID":"_widget_1762420723744",
|
||||
"报备业绩金额-区域提交":"_widget_1766375035236",
|
||||
"业绩归属小六-区域提交":"_widget_1766461143813",
|
||||
"业绩归属月":"_widget_1766375035265",
|
||||
"是否同步衡石":"_widget_1766484337844",
|
||||
"提交人": "creator",
|
||||
"提交时间": "createTime",
|
||||
"更新时间": "updateTime"
|
||||
}
|
||||
|
||||
# 定义需要特殊处理的列表字段及其内部字段映射
|
||||
self.list_fields_config = {
|
||||
"新签阶段及提成比例": {
|
||||
"_widget_1753778656361": "选择提成阶段",
|
||||
"_widget_1753948745962": "新签阶段",
|
||||
"_widget_1753778656362": "提成比例"
|
||||
},
|
||||
# 可以在这里添加其他列表字段的配置
|
||||
# "另一个列表字段": {
|
||||
# "原始字段名1": "映射后字段名1",
|
||||
# "原始字段名2": "映射后字段名2"
|
||||
# }
|
||||
}
|
||||
|
||||
def load_all_data(self):
|
||||
# 获取非标业绩提报数据
|
||||
payload = {"api_key": "66b9678280b37f8a276b1d01",
|
||||
"entry_id": "68886b7c0382a7249ae0b5d6",
|
||||
}
|
||||
dealer_service = api_instance.entry_data_list(payload)
|
||||
self.dealer_service_data = dealer_service.get("data") # api请求格式,将数据封装在data字典里
|
||||
|
||||
def process_list_field(self, field_value, field_config):
|
||||
"""通用方法:处理列表类型的字段"""
|
||||
if not isinstance(field_value, (list, np.ndarray)):
|
||||
return field_value
|
||||
|
||||
processed_list = []
|
||||
for item in field_value:
|
||||
if not isinstance(item, dict):
|
||||
processed_list.append(item)
|
||||
continue
|
||||
|
||||
processed_item = {}
|
||||
for original_key, mapped_key in field_config.items():
|
||||
if original_key in item:
|
||||
# 处理包含id的字典字段
|
||||
if isinstance(item[original_key], dict) and "id" in item[original_key]:
|
||||
processed_item[mapped_key] = item[original_key]["id"]
|
||||
else:
|
||||
processed_item[mapped_key] = item[original_key]
|
||||
else:
|
||||
processed_item[mapped_key] = None
|
||||
processed_list.append(processed_item)
|
||||
return processed_list
|
||||
|
||||
def data_process(self):
|
||||
df = pd.DataFrame(self.dealer_service_data)
|
||||
# 反转映射字典
|
||||
reverse_mapping = {v: k for k, v in self.field_mapping.items()}
|
||||
# 1.列明替换
|
||||
df.columns = [reverse_mapping.get(col, col) for col in df.columns]
|
||||
|
||||
# 只保留流程是否结束为是的内容
|
||||
df = df[df["流程是否结束"] == "是"]
|
||||
|
||||
# 2.成员字段取值
|
||||
user_columns = ["报备业绩归属小六", "报备业绩归属区域经理", "原业绩归属人", "原业绩归属区域经理", "运营专家","业绩归属小六-区域提交"]
|
||||
|
||||
for col in user_columns:
|
||||
df[col] = df[col].map(lambda x: x.get("name", "") if isinstance(x, dict) else "")
|
||||
|
||||
# 3.日期字段转为北京时间
|
||||
time_columns = ["支付日期", "开户/处理日期", "提交时间", "更新时间", "业绩归属月", "业绩归属日期"]
|
||||
|
||||
for col in time_columns:
|
||||
# 1. 解析为 datetime,并明确指定为 UTC(即使原始字符串无时区)
|
||||
dt_utc = pd.to_datetime(df[col], errors='coerce', utc=True)
|
||||
|
||||
# 2. 转换为北京时间
|
||||
dt_beijing = dt_utc.dt.tz_convert('Asia/Shanghai')
|
||||
|
||||
# 3. 去掉时区信息(变成 naive datetime),然后格式化为字符串
|
||||
df[col] = dt_beijing.dt.tz_localize(None).dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 4.业绩动作等于拆单做复制
|
||||
|
||||
# 4.1. 定义条件
|
||||
mask = df['业绩动作'] == '拆单'
|
||||
|
||||
# 4.2. 复制满足条件的行
|
||||
new_rows = df[mask].copy() # ⚠️ 一定要用 .copy() 避免 SettingWithCopyWarning
|
||||
|
||||
# 3. 修改新行中的某些列
|
||||
new_rows['小六业绩金额'] = -new_rows['小六业绩金额']
|
||||
new_rows['区域业绩金额'] = -new_rows['区域业绩金额']
|
||||
new_rows['报备业绩归属小六'] = new_rows['原业绩归属人']
|
||||
new_rows['报备业绩归属区域经理'] = new_rows['原业绩归属区域经理']
|
||||
new_rows['报备业绩归属大区'] = new_rows['原业绩归属大区']
|
||||
|
||||
# 4. 合并回原 DataFrame
|
||||
df = pd.concat([df, new_rows], ignore_index=True)
|
||||
|
||||
# 5.处理所有配置的列表字段
|
||||
if "新签阶段及提成比例" in df.columns:
|
||||
# 先处理订单登记表字段
|
||||
df["新签阶段及提成比例"] = df["新签阶段及提成比例"].apply(
|
||||
lambda x: self.process_list_field(x, self.list_fields_config["新签阶段及提成比例"])
|
||||
if x is not None and (isinstance(x, (list, dict, np.ndarray)) or not pd.isna(x))
|
||||
else None
|
||||
)
|
||||
|
||||
# 拆分行
|
||||
df_exploded = df.explode("新签阶段及提成比例")
|
||||
|
||||
# 将订单登记表中的字段提取到主表中
|
||||
order_fields = self.list_fields_config["新签阶段及提成比例"].values()
|
||||
for field in order_fields:
|
||||
df_exploded[field] = df_exploded["新签阶段及提成比例"].apply(
|
||||
lambda x: x.get(field) if isinstance(x, dict) else None
|
||||
)
|
||||
|
||||
# 删除原始的订单登记表列
|
||||
df_exploded = df_exploded.drop(columns=["新签阶段及提成比例"])
|
||||
|
||||
# 重置索引
|
||||
df = df_exploded.reset_index(drop=True)
|
||||
|
||||
return df
|
||||
|
||||
def write_to_bi(self, df):
|
||||
# 数据库连接信息
|
||||
HS_DB_Config = Config.HS_DB_Config
|
||||
table_name = "non_standard_performance_to_BI" # 替换为你的实际表名
|
||||
|
||||
# 建立数据库连接
|
||||
connection = mysql.connector.connect(
|
||||
host=HS_DB_Config["host"],
|
||||
user=HS_DB_Config["user"],
|
||||
password=HS_DB_Config["password"],
|
||||
database=HS_DB_Config["database"]
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
# 查询表列名
|
||||
cursor.execute(f"SHOW COLUMNS FROM {table_name}")
|
||||
columns_info = cursor.fetchall()
|
||||
db_columns = [col[0] for col in columns_info] # 提取列名
|
||||
df = df.replace([None, np.nan, pd.NA, 'nan', 'NaN', 'NAN', ''], None)
|
||||
# 保留 DataFrame 中与数据库列名匹配的列
|
||||
filtered_df = df[df.columns.intersection(db_columns)]
|
||||
|
||||
# 如果没有匹配的列,直接返回
|
||||
if filtered_df.empty:
|
||||
print("DataFrame 中没有与数据库表结构匹配的列。")
|
||||
return
|
||||
|
||||
# 筛选列之后,插入前处理 dict 类型
|
||||
filtered_df = filtered_df.copy()
|
||||
for col in filtered_df.columns:
|
||||
if filtered_df[col].apply(lambda x: isinstance(x, (dict, list)) if x is not None else False).any():
|
||||
filtered_df.loc[:, col] = filtered_df[col].apply(
|
||||
lambda x: json.dumps(x, ensure_ascii=False) if x is not None else x
|
||||
)
|
||||
|
||||
# 构建插入语句
|
||||
placeholders = ', '.join(['%s'] * len(filtered_df.columns))
|
||||
# 使用反引号避免特殊列明
|
||||
columns = ', '.join([f"`{col}`" for col in filtered_df.columns])
|
||||
insert_sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
|
||||
|
||||
# 将 DataFrame 写入数据库
|
||||
for _, row in filtered_df.iterrows():
|
||||
cursor.execute(insert_sql, tuple(row))
|
||||
|
||||
connection.commit()
|
||||
logger.info(f"成功写入 {len(filtered_df)} 条记录到 {table_name} 表中。")
|
||||
|
||||
except Exception as e:
|
||||
error_task_logger.error(f"写入数据库时发生错误: {e}")
|
||||
connection.rollback()
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
def clear_table_data(self):
|
||||
"""
|
||||
清空指定 MySQL 表的数据。
|
||||
参数已写死在函数内部,直接调用即可。
|
||||
"""
|
||||
# 数据库连接信息
|
||||
HS_DB_Config = {
|
||||
'host': "f6-public.rwlb.rds.aliyuncs.com",
|
||||
'user': "rw_operation_data_relay",
|
||||
'password': "m+q5Z4%IVuF9bf",
|
||||
'database': "f6operation_data_relay"
|
||||
}
|
||||
table_name = "non_standard_performance_to_BI" # 要清空的表名
|
||||
|
||||
connection = None
|
||||
try:
|
||||
# 建立数据库连接
|
||||
connection = mysql.connector.connect(
|
||||
host=HS_DB_Config["host"],
|
||||
user=HS_DB_Config["user"],
|
||||
password=HS_DB_Config["password"],
|
||||
database=HS_DB_Config["database"]
|
||||
)
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor()
|
||||
|
||||
# 使用TRUNCATE清空表数据
|
||||
cursor.execute(f"TRUNCATE TABLE {table_name}")
|
||||
connection.commit()
|
||||
|
||||
logger.info(f"成功清空表 {table_name} 中的所有数据")
|
||||
|
||||
except Error as e:
|
||||
error_task_logger.error(f"清空表时发生错误: {e}")
|
||||
if connection and connection.is_connected():
|
||||
connection.rollback()
|
||||
finally:
|
||||
if connection and connection.is_connected():
|
||||
cursor.close()
|
||||
connection.close()
|
||||
logger.info("数据库连接已关闭")
|
||||
|
||||
def main(self):
|
||||
task_start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
try:
|
||||
logger.info("任务开始")
|
||||
# step1: 获取数据
|
||||
self.load_all_data()
|
||||
logger.info("加载数据完成")
|
||||
# step2:数据处理
|
||||
df = self.data_process()
|
||||
# df.to_csv(os.path.join(output_dir, "new_dealer_service_order_to_bi.csv"))
|
||||
logger.info("数据处理完成")
|
||||
# step3:数据库删除
|
||||
self.clear_table_data()
|
||||
logger.info("目标数据库已清空")
|
||||
# step4:数据写入BI
|
||||
self.write_to_bi(df)
|
||||
logger.info("数据已写入数据库中")
|
||||
common_module.send_task_status(task_start_time, "非标业绩提报转BI")
|
||||
except Exception as e:
|
||||
error_task_logger.error(f"非标业绩提报转BI发生错误{e}")
|
||||
common_module.send_task_error(task_start_time,"非标业绩提报转BI", str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start = NonStandardPerformanceToBI()
|
||||
start.main()
|
||||
+3
-3
@@ -421,8 +421,8 @@
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-03-05T06:03:19.996979900Z",
|
||||
"start_time": "2026-03-05T06:03:19.335172700Z"
|
||||
"end_time": "2026-04-03T09:13:22.881255Z",
|
||||
"start_time": "2026-04-03T09:13:20.171270200Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
@@ -454,7 +454,7 @@
|
||||
"\n",
|
||||
" # 使用DELETE删除ID大于等于127821的数据\n",
|
||||
" # cursor.execute(f\"DELETE FROM {table_name} WHERE id >= {min_id_to_delete}\")\n",
|
||||
" cursor.execute(f\"DELETE FROM GP_annual_renewal_rate_new WHERE 月分区(仅用于存储每月最后一天截至数据) = '202602';\")\n",
|
||||
" cursor.execute(f\"DELETE FROM GP_monthly_renewal_rate_new WHERE 月分区(仅用于存储每月最后一天截至数据) = '202603';\")\n",
|
||||
"\n",
|
||||
" connection.commit()\n",
|
||||
"\n",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
Reference in New Issue
Block a user