This commit is contained in:
2026-04-09 09:53:47 +08:00
parent 976753d3c0
commit 0f9971b7d2
18 changed files with 4390 additions and 121 deletions
+10 -1
View File
@@ -6,4 +6,13 @@
*.iml
out
gen
.logs
.logs
.log
.csv
.excel
.xlsx
output/
__pycache__/
.env
.vscode/
Binary file not shown.
+323
View File
@@ -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())
@@ -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 # 找到匹配的数据后退出循环
# 定义可能的日期格式(灵活应对不同格式)
@@ -392,15 +349,15 @@ class NewExceptionTask:
"%Y/%m/%d",
"%Y/%m/%d %H:%M:%S"
]
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
View File
@@ -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 = ['全国KAFMVP', '区域KAMVP', '重要客户(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
View File
@@ -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
View File
@@ -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": ""},
+138
View File
@@ -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
}
+71
View File
@@ -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
View File
@@ -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 -*-