修复因无新增客户导致NGV数据新增异常
This commit is contained in:
@@ -10,6 +10,8 @@ from decimal import Decimal
|
|||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
os.chdir(Path(__file__).parent)
|
||||||
|
|
||||||
|
|
||||||
def replace_decimals(obj):
|
def replace_decimals(obj):
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ class UpdateNGVData:
|
|||||||
timestamp = time.time()
|
timestamp = time.time()
|
||||||
|
|
||||||
|
|
||||||
# data_NGV_j.to_csv(os.path.join(output_dir, f"{timestamp}up_NGV_j.csv"))
|
data_NGV_j.to_csv(os.path.join(output_dir, f"up_NGV_j.csv"))
|
||||||
# data_NGV_j1.to_csv(os.path.join(output_dir, f"{timestamp}up_NGV_j1.csv"))
|
data_NGV_j1.to_csv(os.path.join(output_dir, f"up_NGV_j1.csv"))
|
||||||
|
|
||||||
# 找出在 data_NGV_j 中存在但在 data_NGV_j1 中不存在的 data_id
|
# 找出在 data_NGV_j 中存在但在 data_NGV_j1 中不存在的 data_id
|
||||||
unique_data_ids = data_NGV_j[~data_NGV_j['org_code'].isin(data_NGV_j1['org_code'])]
|
unique_data_ids = data_NGV_j[~data_NGV_j['org_code'].isin(data_NGV_j1['org_code'])]
|
||||||
@@ -111,9 +111,11 @@ class UpdateNGVData:
|
|||||||
logger.info(f"人员转换完成")
|
logger.info(f"人员转换完成")
|
||||||
|
|
||||||
# 数字保留3位小数
|
# 数字保留3位小数
|
||||||
filtered_df['g_month_percentage'] = (pd.to_numeric(data_NGV_j['g_month_percentage'], errors='coerce')
|
filtered_df['g_month_percentage'] = (
|
||||||
.round(3)
|
pd.to_numeric(filtered_df['g_month_percentage'], errors='coerce')
|
||||||
.apply(lambda x: f"{x:.3f}" if pd.notna(x) else ''))
|
.round(3)
|
||||||
|
.apply(lambda x: f"{x:.3f}" if pd.notna(x) else '')
|
||||||
|
)
|
||||||
|
|
||||||
# filtered_df.to_csv(r"D:\Idea Project\SaaS_V1.3\back_ground_module\output\NGV.csv")
|
# filtered_df.to_csv(r"D:\Idea Project\SaaS_V1.3\back_ground_module\output\NGV.csv")
|
||||||
|
|
||||||
@@ -138,17 +140,18 @@ class UpdateNGVData:
|
|||||||
# print(result_str[:500])
|
# print(result_str[:500])
|
||||||
|
|
||||||
# 保存到Excel文件
|
# 保存到Excel文件
|
||||||
# output_path = r'D:\Idea Project\F6+宜搭+其它(1)\new\文件输出\ngv明细1.xlsx'
|
# output_path = r'D:\Idea Project\SaaS_V1.7\back_ground_module\output\ngv明细1.xlsx'
|
||||||
# filtered_df.to_excel(output_path, index=False)
|
# filtered_df.to_excel(output_path, index=False)
|
||||||
# data_NGV_j1.to_excel( r'D:\Idea Project\F6+宜搭+其它(1)\new\文件输出\ngv明细j1.xlsx', index=False)
|
# data_NGV_j1.to_excel( r'D:\Idea Project\SaaS_V1.7\back_ground_module\output\ngv明细j1.xlsx', index=False)
|
||||||
# data_NGV_j.to_excel( r'D:\Idea Project\F6+宜搭+其它(1)\new\文件输出\ngv明细j.xlsx', index=False)
|
# data_NGV_j.to_excel( r'D:\Idea Project\SaaS_V1.7\back_ground_module\output\ngv明细j.xlsx', index=False)
|
||||||
# new_df.to_excel(r'D:\Idea Project\F6+宜搭+其它(1)\new\文件输出\ngv明细ndf.xlsx', index=False)
|
# new_df.to_excel(r'D:\Idea Project\SaaS_V1.7\back_ground_module\output\ngv明细ndf.xlsx', index=False)
|
||||||
|
|
||||||
common_module.send_task_status(task_start_time, "NGV新增数据")
|
common_module.send_task_status(task_start_time, "NGV新增数据")
|
||||||
logger.info(f"任务完成。")
|
logger.info(f"任务完成。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_task_logger.error(f"任务执行时发生异常: {e}")
|
error_task_logger.error(f"任务执行时发生异常: {e}")
|
||||||
common_module.send_task_error(task_start_time, "NGV新增数据", str(e))
|
common_module.send_task_error(task_start_time, "NGV新增数据", str(e))
|
||||||
|
# pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def row_to_dict(row, field_mapping):
|
def row_to_dict(row, field_mapping):
|
||||||
|
|||||||
@@ -88,10 +88,6 @@ class MoleculeReportingAdjustment:
|
|||||||
if id_in_map == field_id:
|
if id_in_map == field_id:
|
||||||
transformed_data[display_name] = value
|
transformed_data[display_name] = value
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# BI上已经实现
|
# BI上已经实现
|
||||||
# if transformed_data.get("是否上传衡石") == "否" or transformed_data.get("是否上传衡石") is None:
|
# if transformed_data.get("是否上传衡石") == "否" or transformed_data.get("是否上传衡石") is None:
|
||||||
# continue
|
# continue
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import os
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
import pandas as pd
|
||||||
|
from tqdm import tqdm
|
||||||
|
import json
|
||||||
|
from yd_api import YDAPI
|
||||||
|
from api import API
|
||||||
|
import time
|
||||||
|
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
api_instance = API()
|
||||||
|
yd_api_instance = YDAPI()
|
||||||
|
|
||||||
|
def generate_monthly_ranges(start: str, end: str):
|
||||||
|
"""
|
||||||
|
生成按自然月划分的时间段列表(左闭右开)
|
||||||
|
例如: [('2025-11-01T00:00:00Z', '2025-12-01T00:00:00Z'), ...]
|
||||||
|
"""
|
||||||
|
start_dt = datetime.fromisoformat(start.replace("Z", "+00:00"))
|
||||||
|
end_dt = datetime.fromisoformat(end.replace("Z", "+00:00"))
|
||||||
|
|
||||||
|
ranges = []
|
||||||
|
current = start_dt
|
||||||
|
|
||||||
|
while current < end_dt:
|
||||||
|
# 下一个月的第一天
|
||||||
|
if current.month == 12:
|
||||||
|
next_month = current.replace(year=current.year + 1, month=1, day=1)
|
||||||
|
else:
|
||||||
|
next_month = current.replace(month=current.month + 1, day=1)
|
||||||
|
# 不超过 end_dt
|
||||||
|
segment_end = min(next_month, end_dt)
|
||||||
|
ranges.append((
|
||||||
|
current.strftime("%Y-%m-%dT00:00:00Z"),
|
||||||
|
segment_end.strftime("%Y-%m-%dT00:00:00Z")
|
||||||
|
))
|
||||||
|
current = next_month
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
|
||||||
|
class GetYDData:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.FORMID = "FORM-PE866MD1MJMU0WGLYRFLYEN5YN9L1I55Z7ZUK22"
|
||||||
|
self.appType = "APP_UYZ0KG6L0CCNV80GZ66O"
|
||||||
|
self.systemToken = "XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2"
|
||||||
|
|
||||||
|
# 第一段:2025-01-01 到 2025-11-01
|
||||||
|
first_segment = ("2025-01-01T00:00:00Z", "2025-11-01T00:00:00Z")
|
||||||
|
|
||||||
|
# 第二段:2025-11-01 到当前时间(按月拆分)
|
||||||
|
now_utc_str = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
monthly_segments = generate_monthly_ranges("2025-11-01T00:00:00Z", now_utc_str)
|
||||||
|
|
||||||
|
# 合并所有时间段
|
||||||
|
self.time_ranges = [first_segment] + monthly_segments
|
||||||
|
|
||||||
|
print("📅 计划拉取以下时间段:")
|
||||||
|
for i, (s, e) in enumerate(self.time_ranges, 1):
|
||||||
|
print(f" {i}. {s} → {e}")
|
||||||
|
|
||||||
|
def build_value_to_label_map(self, form_structure):
|
||||||
|
value_to_label_map = {}
|
||||||
|
fields = form_structure.get("result", [])
|
||||||
|
for field in fields:
|
||||||
|
field_id = field.get("fieldId")
|
||||||
|
component = field.get("componentName")
|
||||||
|
props = field.get("props", {})
|
||||||
|
data_source = props.get("dataSource", [])
|
||||||
|
|
||||||
|
if component in ["SelectField", "RadioField"] and data_source:
|
||||||
|
option_map = {}
|
||||||
|
for opt in data_source:
|
||||||
|
val = opt.get("value")
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
text_obj = opt.get("text", {})
|
||||||
|
if isinstance(text_obj, dict):
|
||||||
|
zh_text = text_obj.get("zh_CN")
|
||||||
|
if zh_text is None and "value" in text_obj:
|
||||||
|
raw = text_obj["value"]
|
||||||
|
if isinstance(raw, str) and raw.startswith('"') and raw.endswith('"'):
|
||||||
|
zh_text = raw[1:-1]
|
||||||
|
else:
|
||||||
|
zh_text = str(val)
|
||||||
|
elif zh_text is None:
|
||||||
|
zh_text = str(val)
|
||||||
|
else:
|
||||||
|
zh_text = str(text_obj)
|
||||||
|
option_map[str(val)] = zh_text
|
||||||
|
if option_map:
|
||||||
|
value_to_label_map[field_id] = option_map
|
||||||
|
return value_to_label_map
|
||||||
|
|
||||||
|
def convert_record_values(self, record, value_map):
|
||||||
|
converted = {}
|
||||||
|
for key, val in record.items():
|
||||||
|
if key in value_map and val is not None:
|
||||||
|
str_val = str(val)
|
||||||
|
converted[key] = value_map[key].get(str_val, val)
|
||||||
|
else:
|
||||||
|
converted[key] = val
|
||||||
|
return converted
|
||||||
|
|
||||||
|
def fetch_records_in_range(self, token, start_time, end_time):
|
||||||
|
"""拉取指定时间范围内的所有记录"""
|
||||||
|
try:
|
||||||
|
first_page = yd_api_instance.read_processes_instances(
|
||||||
|
token=token,
|
||||||
|
formUuid=self.FORMID,
|
||||||
|
page=1,
|
||||||
|
n=100,
|
||||||
|
appType=self.appType,
|
||||||
|
systemToken=self.systemToken,
|
||||||
|
instanceStatus="RUNNING",
|
||||||
|
modifiedFromTimeGMT=start_time,
|
||||||
|
modifiedToTimeGMT=end_time,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 首页请求失败 ({start_time} ~ {end_time}): {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
total_count = first_page.get("totalCount", 0)
|
||||||
|
total_pages = (total_count // 100) + (1 if total_count % 100 else 0)
|
||||||
|
print(f"📊 [{start_time[:10]} ~ {end_time[:10]}] 总记录数: {total_count}, 共 {total_pages} 页")
|
||||||
|
|
||||||
|
all_records = []
|
||||||
|
if total_count > 0:
|
||||||
|
all_records.extend(first_page.get("data", []))
|
||||||
|
for page in tqdm(range(2, total_pages + 1), desc=f"{start_time[:7]}"):
|
||||||
|
try:
|
||||||
|
resp = yd_api_instance.read_processes_instances(
|
||||||
|
token=token,
|
||||||
|
formUuid=self.FORMID,
|
||||||
|
page=page,
|
||||||
|
n=100,
|
||||||
|
appType=self.appType,
|
||||||
|
systemToken=self.systemToken,
|
||||||
|
instanceStatus="RUNNING",
|
||||||
|
modifiedFromTimeGMT=start_time,
|
||||||
|
modifiedToTimeGMT=end_time,
|
||||||
|
)
|
||||||
|
page_data = resp.get("data", [])
|
||||||
|
all_records.extend(page_data)
|
||||||
|
time.sleep(0.15) # 稍微增加间隔,更安全
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 第 {page} 页失败 ({start_time[:10]}): {e}")
|
||||||
|
continue
|
||||||
|
return all_records
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
# Step 1: 获取表单结构
|
||||||
|
token = yd_api_instance.generateToken()
|
||||||
|
form_struct = yd_api_instance.get_form_structures(
|
||||||
|
token=token,
|
||||||
|
formUuid=self.FORMID
|
||||||
|
)
|
||||||
|
value_map = self.build_value_to_label_map(form_struct)
|
||||||
|
print("\n✅ 表单选项映射构建完成")
|
||||||
|
|
||||||
|
# Step 2: 按时间段拉取
|
||||||
|
all_records = []
|
||||||
|
for start_time, end_time in self.time_ranges:
|
||||||
|
print(f"\n⏳ 拉取: {start_time} → {end_time}")
|
||||||
|
records = self.fetch_records_in_range(token, start_time, end_time)
|
||||||
|
all_records.extend(records)
|
||||||
|
|
||||||
|
print(f"\n📥 总共获取 {len(all_records)} 条流程实例")
|
||||||
|
|
||||||
|
# Step 3: 转换 formData
|
||||||
|
converted_records = []
|
||||||
|
for inst in all_records:
|
||||||
|
form_data = inst.get("formData", {})
|
||||||
|
converted = self.convert_record_values(form_data, value_map)
|
||||||
|
converted_records.append(converted)
|
||||||
|
|
||||||
|
# Step 4: 保存
|
||||||
|
if converted_records:
|
||||||
|
df = pd.DataFrame(converted_records)
|
||||||
|
output_path = os.path.join(output_dir, "converted_yd_data.csv")
|
||||||
|
df.to_csv(output_path, index=False, encoding="utf_8_sig")
|
||||||
|
print(f"\n✅ 成功保存 {len(converted_records)} 条记录至: {output_path}")
|
||||||
|
else:
|
||||||
|
print("\n❌ 无有效数据")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
GetYDData().main()
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"id": "initial_id",
|
||||||
|
"metadata": {
|
||||||
|
"collapsed": true,
|
||||||
|
"ExecuteTime": {
|
||||||
|
"end_time": "2026-01-07T02:17:11.661841100Z",
|
||||||
|
"start_time": "2026-01-07T02:17:11.600589500Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": [
|
||||||
|
"from back_ground_module import CommonModule\n",
|
||||||
|
"from api import API\n",
|
||||||
|
"from log_config import configure_task_logger, configure_error_task_logger\n",
|
||||||
|
"from datetime import datetime, timedelta, timezone\n",
|
||||||
|
"import pandas as pd\n",
|
||||||
|
"import os\n",
|
||||||
|
"\n",
|
||||||
|
"# 获取已经配置好的常规日志记录器\n",
|
||||||
|
"logger = configure_task_logger()\n",
|
||||||
|
"# 获取已经配置好的错误任务日志记录器\n",
|
||||||
|
"error_task_logger = configure_error_task_logger()\n",
|
||||||
|
"# 保存为CSV文件\n",
|
||||||
|
"output_dir = \"output\" # 设置输出目录\n",
|
||||||
|
"# 创建输出目录(如果不存在)\n",
|
||||||
|
"import os\n",
|
||||||
|
"\n",
|
||||||
|
"os.makedirs(output_dir, exist_ok=True)\n",
|
||||||
|
"common_module = CommonModule()\n",
|
||||||
|
"api_instance = API()\n",
|
||||||
|
"\n",
|
||||||
|
"data_JCB = common_module.get_jcb_details()\n",
|
||||||
|
"current_local = datetime.now() + timedelta(days=-1) # tz-naive,代表本地时间\n",
|
||||||
|
"current_date_str = current_local.strftime(\"%Y-%m-%d\")\n",
|
||||||
|
"# 计算30天前的本地日期(用于开户日判断)\n",
|
||||||
|
"thirty_days_ago_local = (current_local - timedelta(days=30)).date()\n",
|
||||||
|
"payload = {\"api_key\": \"6717470a0b3975ef583c6df1\",\n",
|
||||||
|
" \"entry_id\": \"67174710da507490d8ac12c1\",\n",
|
||||||
|
" }\n",
|
||||||
|
"daily_revisit = api_instance.entry_data_list(payload)\n",
|
||||||
|
"daily_revisit_list = daily_revisit.get(\"data\") # api请求格式,将数据封装在data字典里\n",
|
||||||
|
"abnormal_data = []\n",
|
||||||
|
"for index, row in data_JCB.iterrows():\n",
|
||||||
|
" try:\n",
|
||||||
|
" # 开户日是本地日期字符串,解析为 date 对象\n",
|
||||||
|
" open_date = datetime.strptime(str(row['开户日']), \"%Y-%m-%d\").date()\n",
|
||||||
|
" except (ValueError, TypeError):\n",
|
||||||
|
" continue # 跳过无效日期\n",
|
||||||
|
"\n",
|
||||||
|
" if (\n",
|
||||||
|
" open_date < thirty_days_ago_local\n",
|
||||||
|
" and row['近30天开单天数'] == 0\n",
|
||||||
|
" and row['客户状态'] == \"留存\"\n",
|
||||||
|
" ):\n",
|
||||||
|
" new_row = row.copy()\n",
|
||||||
|
" new_row[\"日期\"] = open_date.strftime(\"%Y-%m-%d\")\n",
|
||||||
|
" abnormal_data.append(new_row)\n",
|
||||||
|
"\n",
|
||||||
|
"abnormal_data = pd.DataFrame(abnormal_data) if abnormal_data else pd.DataFrame()\n",
|
||||||
|
"\n",
|
||||||
|
"if not abnormal_data.empty:\n",
|
||||||
|
" abnormal_data[\"表单类型\"] = \"异常待办\"\n",
|
||||||
|
" abnormal_data[\"派发日期\"] = current_date_str\n",
|
||||||
|
"\n",
|
||||||
|
" # 清洗手机号(仅去除浮点型 .0)\n",
|
||||||
|
" def clean_phone(x):\n",
|
||||||
|
" if pd.isna(x) or x == \"\" or x == \"None\":\n",
|
||||||
|
" return \"\"\n",
|
||||||
|
" s = str(x)\n",
|
||||||
|
" if s.endswith('.0') and s[:-2].isdigit():\n",
|
||||||
|
" return s[:-2]\n",
|
||||||
|
" return s\n",
|
||||||
|
"\n",
|
||||||
|
" abnormal_data['联系手机号'] = abnormal_data['联系手机号'].apply(clean_phone)\n",
|
||||||
|
"\n",
|
||||||
|
"# 构建云端已派发记录 DataFrame\n",
|
||||||
|
"df_cloud = pd.DataFrame([\n",
|
||||||
|
" {\n",
|
||||||
|
" \"数据id\": item.get(\"_id\", \"\"),\n",
|
||||||
|
" \"账号\": item.get(\"_widget_1739258942667\", \"\"),\n",
|
||||||
|
" \"提交时间\": item.get(\"createTime\", \"\"),\n",
|
||||||
|
" \"表单类型\": item.get(\"_widget_1739951204545\", \"\")\n",
|
||||||
|
" }\n",
|
||||||
|
" for item in daily_revisit_list\n",
|
||||||
|
"])\n",
|
||||||
|
"\n",
|
||||||
|
"recent_accounts = set()\n",
|
||||||
|
"if not df_cloud.empty and not abnormal_data.empty:\n",
|
||||||
|
" # 将 createTime 转为 UTC 时间(强制统一时区)\n",
|
||||||
|
" df_cloud[\"提交时间\"] = pd.to_datetime(df_cloud[\"提交时间\"], utc=True, errors=\"coerce\")\n",
|
||||||
|
" df_cloud = df_cloud.dropna(subset=[\"提交时间\"])\n",
|
||||||
|
"\n",
|
||||||
|
" # 筛选“异常待办”\n",
|
||||||
|
" df_abnormal_cloud = df_cloud[df_cloud[\"表单类型\"] == \"异常待办\"]\n",
|
||||||
|
"\n",
|
||||||
|
" if not df_abnormal_cloud.empty:\n",
|
||||||
|
" # 每个账号保留最新一条\n",
|
||||||
|
" df_recent = df_abnormal_cloud.sort_values(\"提交时间\").groupby(\"账号\", as_index=False).tail(1)\n",
|
||||||
|
"\n",
|
||||||
|
" current_utc = datetime.now(timezone.utc)\n",
|
||||||
|
" cutoff_utc = pd.Timestamp(current_utc) - pd.Timedelta(days=30)\n",
|
||||||
|
"\n",
|
||||||
|
" # 安全比较:两边都是 UTC\n",
|
||||||
|
" recent_accounts = set(df_recent[df_recent[\"提交时间\"] > cutoff_utc][\"账号\"])\n",
|
||||||
|
"\n",
|
||||||
|
"# 剔除已派发账号 + 过滤有效手机号\n",
|
||||||
|
"if not abnormal_data.empty:\n",
|
||||||
|
" abnormal_data = abnormal_data[\n",
|
||||||
|
" (~abnormal_data[\"账号\"].isin(recent_accounts)) &\n",
|
||||||
|
" (abnormal_data[\"联系手机号\"].notna()) &\n",
|
||||||
|
" (abnormal_data[\"联系手机号\"] != \"\") &\n",
|
||||||
|
" (abnormal_data[\"联系手机号\"] != \"None\")\n",
|
||||||
|
" ]\n",
|
||||||
|
"\n",
|
||||||
|
"# # 保存结果\n",
|
||||||
|
"output_path = os.path.join(output_dir, \"异常待办1.csv\")\n",
|
||||||
|
"abnormal_data.to_csv(output_path, index=False)"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"ename": "ModuleNotFoundError",
|
||||||
|
"evalue": "No module named 'back_ground_module'",
|
||||||
|
"output_type": "error",
|
||||||
|
"traceback": [
|
||||||
|
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
|
||||||
|
"\u001B[31mModuleNotFoundError\u001B[39m Traceback (most recent call last)",
|
||||||
|
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[3]\u001B[39m\u001B[32m, line 1\u001B[39m\n\u001B[32m----> \u001B[39m\u001B[32m1\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mback_ground_module\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m CommonModule\n\u001B[32m 2\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mapi\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m API\n\u001B[32m 3\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mlog_config\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m configure_task_logger, configure_error_task_logger\n",
|
||||||
|
"\u001B[31mModuleNotFoundError\u001B[39m: No module named 'back_ground_module'"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"execution_count": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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,112 @@
|
|||||||
|
from back_ground_module import CommonModule
|
||||||
|
from api import API
|
||||||
|
from log_config import configure_task_logger, configure_error_task_logger
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 获取已经配置好的常规日志记录器
|
||||||
|
logger = configure_task_logger()
|
||||||
|
# 获取已经配置好的错误任务日志记录器
|
||||||
|
error_task_logger = configure_error_task_logger()
|
||||||
|
# 保存为CSV文件
|
||||||
|
output_dir = "output" # 设置输出目录
|
||||||
|
# 创建输出目录(如果不存在)
|
||||||
|
import os
|
||||||
|
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
common_module = CommonModule()
|
||||||
|
api_instance = API()
|
||||||
|
|
||||||
|
data_JCB = common_module.get_jcb_details()
|
||||||
|
output_path = os.path.join(output_dir, "借车包明细.csv")
|
||||||
|
data_JCB.to_csv(output_path, index=False)
|
||||||
|
|
||||||
|
current_local = datetime.now() + timedelta(days=-1) # tz-naive,代表本地时间
|
||||||
|
current_date_str = current_local.strftime("%Y-%m-%d")
|
||||||
|
# 计算30天前的本地日期(用于开户日判断)
|
||||||
|
thirty_days_ago_local = (current_local - timedelta(days=30)).date()
|
||||||
|
payload = {"api_key": "6717470a0b3975ef583c6df1",
|
||||||
|
"entry_id": "67174710da507490d8ac12c1",
|
||||||
|
}
|
||||||
|
daily_revisit = api_instance.entry_data_list(payload)
|
||||||
|
daily_revisit_list = daily_revisit.get("data") # api请求格式,将数据封装在data字典里
|
||||||
|
abnormal_data = []
|
||||||
|
for index, row in data_JCB.iterrows():
|
||||||
|
try:
|
||||||
|
# 开户日是本地日期字符串,解析为 date 对象
|
||||||
|
open_date = datetime.strptime(str(row['开户日']), "%Y-%m-%d").date()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
continue # 跳过无效日期
|
||||||
|
|
||||||
|
if (
|
||||||
|
open_date < thirty_days_ago_local
|
||||||
|
and row['近30天开单天数'] == 0
|
||||||
|
and row['客户状态'] == "留存"
|
||||||
|
):
|
||||||
|
new_row = row.copy()
|
||||||
|
new_row["日期"] = open_date.strftime("%Y-%m-%d")
|
||||||
|
abnormal_data.append(new_row)
|
||||||
|
|
||||||
|
abnormal_data = pd.DataFrame(abnormal_data) if abnormal_data else pd.DataFrame()
|
||||||
|
output_path = os.path.join(output_dir, "异常待办.csv")
|
||||||
|
abnormal_data.to_csv(output_path, index=False)
|
||||||
|
|
||||||
|
if not abnormal_data.empty:
|
||||||
|
abnormal_data["表单类型"] = "异常待办"
|
||||||
|
abnormal_data["派发日期"] = current_date_str
|
||||||
|
|
||||||
|
# 清洗手机号(仅去除浮点型 .0)
|
||||||
|
def clean_phone(x):
|
||||||
|
if pd.isna(x) or x == "" or x == "None":
|
||||||
|
return ""
|
||||||
|
s = str(x)
|
||||||
|
if s.endswith('.0') and s[:-2].isdigit():
|
||||||
|
return s[:-2]
|
||||||
|
return s
|
||||||
|
|
||||||
|
abnormal_data['联系手机号'] = abnormal_data['联系手机号'].apply(clean_phone)
|
||||||
|
|
||||||
|
# 构建云端已派发记录 DataFrame
|
||||||
|
df_cloud = pd.DataFrame([
|
||||||
|
{
|
||||||
|
"数据id": item.get("_id", ""),
|
||||||
|
"账号": item.get("_widget_1739258942667", ""),
|
||||||
|
"提交时间": item.get("createTime", ""),
|
||||||
|
"表单类型": item.get("_widget_1739951204545", "")
|
||||||
|
}
|
||||||
|
for item in daily_revisit_list
|
||||||
|
])
|
||||||
|
output_path = os.path.join(output_dir, "异常待办云端.csv")
|
||||||
|
df_cloud.to_csv(output_path, index=False)
|
||||||
|
recent_accounts = set()
|
||||||
|
if not df_cloud.empty and not abnormal_data.empty:
|
||||||
|
# 将 createTime 转为 UTC 时间(强制统一时区)
|
||||||
|
df_cloud["提交时间"] = pd.to_datetime(df_cloud["提交时间"], utc=True, errors="coerce")
|
||||||
|
df_cloud = df_cloud.dropna(subset=["提交时间"])
|
||||||
|
|
||||||
|
# 筛选“异常待办”
|
||||||
|
df_abnormal_cloud = df_cloud[df_cloud["表单类型"] == "异常待办"]
|
||||||
|
|
||||||
|
if not df_abnormal_cloud.empty:
|
||||||
|
# 每个账号保留最新一条
|
||||||
|
df_recent = df_abnormal_cloud.sort_values("提交时间").groupby("账号", as_index=False).tail(1)
|
||||||
|
|
||||||
|
current_utc = datetime.now(timezone.utc)
|
||||||
|
cutoff_utc = pd.Timestamp(current_utc) - pd.Timedelta(days=30)
|
||||||
|
|
||||||
|
# 安全比较:两边都是 UTC
|
||||||
|
recent_accounts = set(df_recent[df_recent["提交时间"] > cutoff_utc]["账号"])
|
||||||
|
|
||||||
|
# 剔除已派发账号 + 过滤有效手机号
|
||||||
|
if not abnormal_data.empty:
|
||||||
|
abnormal_data = abnormal_data[
|
||||||
|
(~abnormal_data["账号"].isin(recent_accounts)) &
|
||||||
|
(abnormal_data["联系手机号"].notna()) &
|
||||||
|
(abnormal_data["联系手机号"] != "") &
|
||||||
|
(abnormal_data["联系手机号"] != "None")
|
||||||
|
]
|
||||||
|
|
||||||
|
# # 保存结果
|
||||||
|
output_path = os.path.join(output_dir, "异常待办1.csv")
|
||||||
|
abnormal_data.to_csv(output_path, index=False)
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
import os
|
|
||||||
from config import Config
|
|
||||||
import pandas as pd
|
|
||||||
from back_ground_module import CommonModule
|
|
||||||
from api import API
|
|
||||||
from log_config import configure_task_logger, configure_error_task_logger
|
|
||||||
|
|
||||||
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 YdToJDYRenewalToDo:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def load_all_data(self):
|
|
||||||
"""
|
|
||||||
从各类来源加载数据上加载数据
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
def main(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
yd_to_jd_renewal_to_do = YdToJDYRenewalToDo()
|
|
||||||
yd_to_jd_renewal_to_do.main()
|
|
||||||
+4
-4
@@ -421,8 +421,8 @@
|
|||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ExecuteTime": {
|
"ExecuteTime": {
|
||||||
"end_time": "2026-01-06T08:21:20.943512600Z",
|
"end_time": "2026-01-06T08:49:37.955377100Z",
|
||||||
"start_time": "2026-01-06T08:21:19.361422Z"
|
"start_time": "2026-01-06T08:49:37.747330400Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
@@ -454,7 +454,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" # 使用DELETE删除ID大于等于127821的数据\n",
|
" # 使用DELETE删除ID大于等于127821的数据\n",
|
||||||
" # cursor.execute(f\"DELETE FROM {table_name} WHERE id >= {min_id_to_delete}\")\n",
|
" # cursor.execute(f\"DELETE FROM {table_name} WHERE id >= {min_id_to_delete}\")\n",
|
||||||
" cursor.execute(f\"DELETE FROM GP_monthly_renewal_rate_new WHERE 月分区(仅用于存储每月最后一天截至数据) = '202512';\")\n",
|
" cursor.execute(f\"DELETE FROM GP_annual_renewal_rate_new WHERE 月分区(仅用于存储每月最后一天截至数据) = '202512';\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" connection.commit()\n",
|
" connection.commit()\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -478,7 +478,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"execution_count": 2
|
"execution_count": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class YDAPI:
|
|||||||
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", instanceStatus="RUNNING",
|
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", instanceStatus="RUNNING",
|
||||||
max_retries=10, delay=2, createFromTimeGMT=None, createToTimeGMT=None,
|
max_retries=10, delay=2, createFromTimeGMT=None, createToTimeGMT=None,
|
||||||
modifiedFromTimeGMT=None,
|
modifiedFromTimeGMT=None,
|
||||||
modifiedToTimeGMT=None, searchFieldJson={}):
|
modifiedToTimeGMT=None, searchFieldJson={},useAlias=False):
|
||||||
"""
|
"""
|
||||||
函数功能:读取流程表单的所有数据,并加入重试机制。
|
函数功能:读取流程表单的所有数据,并加入重试机制。
|
||||||
|
|
||||||
@@ -196,7 +196,8 @@ class YDAPI:
|
|||||||
"modifiedToTimeGMT": modifiedToTimeGMT,
|
"modifiedToTimeGMT": modifiedToTimeGMT,
|
||||||
"searchFieldJson": json.dumps(
|
"searchFieldJson": json.dumps(
|
||||||
searchFieldJson
|
searchFieldJson
|
||||||
)
|
),
|
||||||
|
"useAlias": useAlias,
|
||||||
}
|
}
|
||||||
# print(formData)
|
# print(formData)
|
||||||
|
|
||||||
@@ -460,39 +461,40 @@ class YDAPI:
|
|||||||
def get_form_structures(self, token, formUuid, appType="APP_UYZ0KG6L0CCNV80GZ66O",
|
def get_form_structures(self, token, formUuid, appType="APP_UYZ0KG6L0CCNV80GZ66O",
|
||||||
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", max_retries=20):
|
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", max_retries=20):
|
||||||
"""
|
"""
|
||||||
函数功能:获取表单结构 # 宜搭废弃
|
获取表单结构(宜搭)
|
||||||
|
注意:该接口必须使用 GET 方法,参数通过 query string 传递
|
||||||
Args:
|
|
||||||
token (str): 登录验证token,用于API调用的身份验证。
|
|
||||||
formUuid (str): 表单的UUID,标识要获取结构的表单。
|
|
||||||
appType
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
响应值: 如果请求成功,则返回服务器的JSON响应;如果请求失败或无响应,则返回一个表示获取成功信息的字符串。
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
api = f'https://api.dingtalk.com/v1.0/yida/forms/formFields'
|
api_base = 'https://api.dingtalk.com/v1.0/yida/forms/formFields'
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
# 构造查询参数
|
||||||
"x-acs-dingtalk-access-token": token
|
params = {
|
||||||
}
|
|
||||||
retries = 0
|
|
||||||
data_get = {}
|
|
||||||
payload = {
|
|
||||||
"formUuid": formUuid,
|
|
||||||
"appType": appType,
|
"appType": appType,
|
||||||
"systemToken": systemToken,
|
"systemToken": systemToken,
|
||||||
|
"formUuid": formUuid,
|
||||||
"userId": "2268275546837446",
|
"userId": "2268275546837446",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"x-acs-dingtalk-access-token": token
|
||||||
|
}
|
||||||
|
|
||||||
|
retries = 0
|
||||||
while retries < max_retries:
|
while retries < max_retries:
|
||||||
res = requests.post(api, headers=headers, json=payload)
|
try:
|
||||||
if res.status_code == 200:
|
# 使用 GET,并传入 params(自动编码为 query string)
|
||||||
data_get = res.json()
|
res = requests.get(api_base, headers=headers, params=params, timeout=10)
|
||||||
break
|
if res.status_code == 200:
|
||||||
else:
|
return res.json()
|
||||||
retries += 1
|
else:
|
||||||
time.sleep(0.1)
|
print(f"请求失败,状态码: {res.status_code}, 响应: {res.text}")
|
||||||
return data_get
|
except Exception as e:
|
||||||
|
print(f"请求异常: {e}")
|
||||||
|
|
||||||
|
retries += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
return {} # 或抛出异常
|
||||||
|
|
||||||
|
|
||||||
class NpEncoder(json.JSONEncoder):
|
class NpEncoder(json.JSONEncoder):
|
||||||
|
|||||||
Reference in New Issue
Block a user