Functions-d 1.2.5__tar.gz → 1.28__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -148,6 +148,7 @@ class DataProcessingAndMessaging:
148
148
  self.logger.info('\n' * 10)
149
149
 
150
150
  # -------------------------- 企业微信消息发送 --------------------------
151
+ # 仅展示修改后的 uxin_wx 方法,其余代码不变
151
152
  def uxin_wx(self, name, message, mentioned_list=None):
152
153
  sender = WeComMsg.WeChatWorkSender(self.corpid, self.corpsecret, self.agentid)
153
154
  try:
@@ -158,6 +159,43 @@ class DataProcessingAndMessaging:
158
159
  target_type = "群聊(Webhook)" if name.startswith("https://") else "单个用户"
159
160
  self.logger.info(f"开始向{target_type}发送消息,目标:{name}")
160
161
 
162
+ # ========== 新增:Markdown消息识别与处理 ==========
163
+ # 识别规则:message以特定标识开头(如「MD:」),或显式传入markdown类型
164
+ is_markdown = False
165
+ md_content = ""
166
+ if isinstance(message, dict) and message.get("type") == "markdown":
167
+ # 支持字典格式传参:{"type": "markdown", "content": "markdown内容"}
168
+ is_markdown = True
169
+ md_content = message.get("content", "")
170
+ elif isinstance(message, str) and message.startswith("MD:"):
171
+ # 支持字符串前缀标识:"MD:### 标题\n内容"
172
+ is_markdown = True
173
+ md_content = message[3:].strip() # 去掉前缀"MD:"
174
+
175
+ if is_markdown:
176
+ # 处理Markdown消息
177
+ if isinstance(name, str) and name.startswith("https://"):
178
+ # 群聊Webhook发送Markdown
179
+ self.logger.info(f"发送群聊Markdown消息:内容={md_content}")
180
+ result = sender.send_markdown_to_group(name, md_content)
181
+ else:
182
+ # 个人/多用户发送Markdown
183
+ receivers = name if isinstance(name, list) else [name]
184
+ self.logger.info(f"发送个人Markdown消息:内容={md_content},接收者:{receivers}")
185
+ result = sender.send_markdown(receivers, md_content)
186
+
187
+ msg_id = result.get('msgid', '未知')
188
+ self.logger.info(
189
+ f"Markdown消息发送结果:{'成功' if result.get('errcode') == 0 else '失败'},错误信息:{result.get('errmsg')},消息ID:{msg_id}")
190
+
191
+ if result.get('errcode') == 0:
192
+ print(f"给 {target_type} 的Markdown消息发送成功,消息ID:{msg_id}")
193
+ else:
194
+ print(
195
+ f"给 {target_type} 的Markdown消息发送失败,错误码:{result.get('errcode')},错误信息:{result.get('errmsg')},消息ID:{msg_id}")
196
+ return
197
+
198
+ # ========== 原有逻辑(文本/文件/图片)保持不变 ==========
161
199
  if isinstance(name, str) and name.startswith("https://"):
162
200
  if isinstance(message, str) and message.endswith(('.xlsx', '.docx', '.pdf', '.txt')) and os.path.isfile(
163
201
  message):
@@ -1028,15 +1066,55 @@ class DataProcessingAndMessaging:
1028
1066
  self._wechat_doc_log(error_msg)
1029
1067
  raise Exception(error_msg)
1030
1068
  docid = result.get("docid")
1031
- self._wechat_doc_log(f"智能表格:{sheet_name} 创建成功,docid={docid}")
1032
- print(f"智能表格:{sheet_name} 创建成功,docid={docid}")
1033
- return {"智能表格": sheet_name, "docid": docid, "url": result.get("url")}
1069
+ # ========== 新增:添加创建时间 ==========
1070
+ # 获取当前时间(北京时间,格式:YYYY-MM-DD HH:MM:SS)
1071
+ create_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1072
+
1073
+ # ========== 核心新增:创建成功后自动查询子sheet ==========
1074
+ self._wechat_doc_log(f"开始查询新创建表格[{docid}]的子表信息")
1075
+ # 然后修改wx_create_table中调用wx_get_sheets的代码,传入send_notice=False:
1076
+ sheet_list = self.wx_get_sheets(docid, send_notice=False)
1077
+
1078
+ # ========== 构造包含子sheet的完整消息内容 ==========
1079
+ # 基础信息
1080
+ msg_content = f"✅ 智能表格创建成功\n表格名称:{sheet_name}\n创建时间:{create_time}\ndocid:{docid}\n\n子表信息:"
1081
+ # 子sheet信息
1082
+ if sheet_list:
1083
+ for idx, sheet in enumerate(sheet_list, 1):
1084
+ sheet_visibility = '可见' if sheet['is_visible'] else '不可见'
1085
+ msg_content += f"\n{idx}. ID: {sheet['sheet_id']} | 标题: {sheet['title']} | 类型: {sheet['type']} | 可见性: {sheet_visibility}"
1086
+ else:
1087
+ msg_content += "\n暂无子表信息"
1088
+
1089
+ self._wechat_doc_log(
1090
+ f"智能表格:{sheet_name} 创建成功,docid={docid},创建时间={create_time},子表数量:{len(sheet_list)}")
1091
+ print(f"智能表格:{sheet_name} 创建成功,docid={docid},创建时间={create_time}")
1092
+ print(f"该表格的子表信息:")
1093
+ if sheet_list:
1094
+ for idx, sheet in enumerate(sheet_list, 1):
1095
+ sheet_visibility = '可见' if sheet['is_visible'] else '不可见'
1096
+ print(
1097
+ f" {idx}. ID: {sheet['sheet_id']} | 标题: {sheet['title']} | 类型: {sheet['type']} | 可见性: {sheet_visibility}")
1098
+ else:
1099
+ print(" 暂无子表信息")
1100
+
1101
+ # 调用uxin_wx发送包含子sheet的完整消息
1102
+ self.uxin_wx('dongyang', msg_content)
1103
+
1104
+ return {
1105
+ "智能表格": sheet_name,
1106
+ "docid": docid,
1107
+ "url": result.get("url"),
1108
+ "创建时间": create_time,
1109
+ "子表列表": sheet_list # 返回值新增子sheet列表
1110
+ }
1034
1111
  except Exception as e:
1035
1112
  self._wechat_doc_log(f"创建异常: {str(e)}")
1036
1113
  raise
1037
1114
 
1038
1115
  # -------------------------- 企微文档:查询子表 --------------------------
1039
- def wx_get_sheets(self, docid):
1116
+ # 修改wx_get_sheets方法的定义,新增send_notice参数
1117
+ def wx_get_sheets(self, docid, send_notice=True):
1040
1118
  self._wechat_doc_log(f"查询文档[{docid}]的子表信息")
1041
1119
  self._wechat_doc_refresh_token_if_needed()
1042
1120
 
@@ -1052,10 +1130,30 @@ class DataProcessingAndMessaging:
1052
1130
  if result.get("errcode") != 0:
1053
1131
  error_msg = f"查询子表失败: {result.get('errmsg')}"
1054
1132
  self._wechat_doc_log(error_msg)
1133
+ # 失败时仍发送通知
1134
+ fail_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1135
+ fail_content = f"⚠️ 企微文档子表查询失败\n查询时间:{fail_time}\n文档ID:{docid}\n错误信息:{result.get('errmsg')}"
1136
+ self.uxin_wx('dongyang', fail_content)
1055
1137
  raise Exception(error_msg)
1056
1138
 
1057
1139
  sheet_list = result.get("sheet_list", [])
1140
+ query_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1058
1141
  self._wechat_doc_log(f"查询文档[{docid}]子表完成,共找到{len(sheet_list)}个子表")
1142
+
1143
+ # 构造通知内容
1144
+ msg_content = f"✅ 企微文档子表查询完成\n查询时间:{query_time}\n文档ID:{docid}\n子表总数:{len(sheet_list)}\n\n子表详细信息:"
1145
+ if sheet_list:
1146
+ for idx, sheet in enumerate(sheet_list, 1):
1147
+ sheet_visibility = '可见' if sheet['is_visible'] else '不可见'
1148
+ msg_content += f"\n{idx}. ID: {sheet['sheet_id']} | 标题: {sheet['title']} | 类型: {sheet['type']} | 可见性: {sheet_visibility}"
1149
+ else:
1150
+ msg_content += "\n暂无子表信息"
1151
+
1152
+ # 仅当send_notice为True时发送通知
1153
+ if send_notice:
1154
+ self.uxin_wx('dongyang', msg_content)
1155
+
1156
+ # 原有日志和打印逻辑
1059
1157
  for sheet in sheet_list:
1060
1158
  sheet_info = (f"子表信息 - ID: {sheet['sheet_id']}, 标题: {sheet['title']}, "
1061
1159
  f"类型: {sheet['type']}, 可见性: {'可见' if sheet['is_visible'] else '不可见'}")
@@ -1098,6 +1196,40 @@ class DataProcessingAndMessaging:
1098
1196
  print(f"[企业微信文档] {msg}")
1099
1197
  return pd.DataFrame()
1100
1198
 
1199
+ # 新增:解析公式列的核心函数
1200
+ def _parse_formula_field(field_value):
1201
+ """
1202
+ 解析公式列数据,提取公式计算后的数值结果
1203
+ 公式列典型格式:{"type":"FormulaFieldProperty","value": 100, "expression": "A1+B1"}
1204
+ """
1205
+ if not field_value:
1206
+ return ""
1207
+
1208
+ # 处理公式列的嵌套字典格式
1209
+ if isinstance(field_value, dict) and field_value.get("type") == "FormulaFieldProperty":
1210
+ formula_value = field_value.get("value")
1211
+ # 如果公式计算结果是数字,直接返回;否则返回空字符串
1212
+ if isinstance(formula_value, (int, float)):
1213
+ self._wechat_doc_log(f"解析公式列值:{field_value} → 提取数值{formula_value}")
1214
+ return formula_value
1215
+ else:
1216
+ self._wechat_doc_log(f"公式列无有效数值:{field_value}")
1217
+ return ""
1218
+
1219
+ # 兼容列表嵌套公式字典的情况
1220
+ elif isinstance(field_value, list) and len(field_value) > 0:
1221
+ formula_values = []
1222
+ for item in field_value:
1223
+ if isinstance(item, dict) and item.get("type") == "FormulaFieldProperty":
1224
+ val = item.get("value")
1225
+ if isinstance(val, (int, float)):
1226
+ formula_values.append(val)
1227
+ return formula_values[0] if formula_values else ""
1228
+
1229
+ # 非公式格式直接返回原值
1230
+ else:
1231
+ return field_value
1232
+
1101
1233
  # 时间转换工具
1102
1234
  def _unified_time_convert(value):
1103
1235
  if value is None or str(value).strip() in ["", "None", "nan"]:
@@ -1149,6 +1281,118 @@ class DataProcessingAndMessaging:
1149
1281
  else:
1150
1282
  return raw_text
1151
1283
 
1284
+ # 判断是否为包含user_id的有效人员字段格式
1285
+ def _is_valid_person_format(field_value):
1286
+ """
1287
+ 判断字段值是否为包含user_id的有效人员格式(字典/列表嵌套字典且有user_id)
1288
+ """
1289
+ # 空值直接返回False
1290
+ if not field_value:
1291
+ return False
1292
+
1293
+ # 列表类型:检查是否有元素包含user_id字段
1294
+ if isinstance(field_value, list):
1295
+ for item in field_value:
1296
+ if isinstance(item, dict) and item.get("user_id") is not None:
1297
+ return True
1298
+ return False
1299
+
1300
+ # 字典类型:检查是否有user_id字段
1301
+ elif isinstance(field_value, dict):
1302
+ return field_value.get("user_id") is not None
1303
+
1304
+ # 其他类型返回False
1305
+ else:
1306
+ return False
1307
+
1308
+ # 解析人员列数据的函数
1309
+ def _parse_person_field(field_value):
1310
+ """
1311
+ 解析人员列数据,提取所有人员名称/user_id,用逗号分隔
1312
+ """
1313
+ if not field_value:
1314
+ return ""
1315
+
1316
+ # 如果是列表,遍历所有元素
1317
+ if isinstance(field_value, list):
1318
+ person_names = []
1319
+ for item in field_value:
1320
+ if isinstance(item, dict):
1321
+ # 优先获取名称相关字段,没有则用user_id
1322
+ name = item.get("name") or item.get("user_id") or item.get("text") or item.get("title")
1323
+ if name:
1324
+ person_names.append(str(name))
1325
+ else:
1326
+ # 如果没有名称字段,拼接有用的信息
1327
+ person_info = []
1328
+ if item.get("user_id"):
1329
+ person_info.append(item["user_id"])
1330
+ if item.get("id_type"):
1331
+ person_info.append(f"类型{item['id_type']}")
1332
+ if person_info:
1333
+ person_names.append("-".join(person_info))
1334
+ else:
1335
+ person_names.append(str(item))
1336
+ else:
1337
+ person_names.append(str(item))
1338
+ # 去重并拼接
1339
+ unique_names = list(set(person_names)) # 去重
1340
+ return ", ".join(unique_names)
1341
+
1342
+ # 如果是字典
1343
+ elif isinstance(field_value, dict):
1344
+ name = field_value.get("name") or field_value.get("user_id") or field_value.get(
1345
+ "text") or field_value.get("title")
1346
+ if name:
1347
+ return str(name)
1348
+ else:
1349
+ # 返回格式化的字典信息
1350
+ return f"{field_value.get('user_id', '')}"
1351
+
1352
+ # 其他类型直接转字符串
1353
+ else:
1354
+ return str(field_value)
1355
+
1356
+ # 判断字符串是否为纯数字(支持整数/小数)
1357
+ def _is_pure_number(s):
1358
+ """
1359
+ 判断字符串是否为纯数字(包含整数、小数、负数)
1360
+ """
1361
+ if not s or str(s).strip() in ["", "None", "nan"]:
1362
+ return True # 空值不影响列的数字判断
1363
+ try:
1364
+ float(str(s).strip())
1365
+ return True
1366
+ except (ValueError, TypeError):
1367
+ return False
1368
+
1369
+ # 转换列为数值格式
1370
+ def _convert_numeric_columns(df):
1371
+ """
1372
+ 遍历DataFrame列,将全为数字的列转换为数值格式(int/float自动适配)
1373
+ """
1374
+ numeric_columns = []
1375
+ for col in df.columns:
1376
+ # 跳过时间/人员相关列(避免误转换)
1377
+ if any(kw in str(col) for kw in
1378
+ ["日期", "时间", "记录ID", "创建", "更新", "编辑者", "人员", "负责人", "经办人", "对接人",
1379
+ "联系人", "使用人", "成交人", "交付人", "品牌专家", "姓名", "销售", "维护人", "操作人",
1380
+ "转移人", "执行人", "人", "编辑"]):
1381
+ continue
1382
+
1383
+ # 检查列中所有非空值是否为纯数字
1384
+ all_numeric = df[col].apply(lambda x: _is_pure_number(x)).all()
1385
+ if all_numeric:
1386
+ # 先转换为float,再判断是否能转int(保留小数的自动适配)
1387
+ df[col] = pd.to_numeric(df[col], errors="coerce")
1388
+ # 若所有值都是整数,转int类型;否则保留float
1389
+ if (df[col].dropna() % 1 == 0).all():
1390
+ df[col] = df[col].astype('Int64') # Int64支持空值
1391
+ numeric_columns.append(col)
1392
+ self._wechat_doc_log(f"列[{col}]全为数字,已转换为{df[col].dtype}格式")
1393
+
1394
+ return df, numeric_columns
1395
+
1152
1396
  # 处理记录
1153
1397
  rows = []
1154
1398
  custom_fields = set()
@@ -1158,7 +1402,18 @@ class DataProcessingAndMessaging:
1158
1402
  break
1159
1403
  all_fields = ["记录ID", "创建时间", "更新时间", "最后编辑者"] + list(custom_fields)
1160
1404
  date_related_columns = [col for col in all_fields if any(kw in str(col) for kw in ["日期", "时间"])]
1405
+ # 识别人员相关列(包含"人员"、"负责人"、"经办人"等关键词)
1406
+ person_related_columns = [col for col in all_fields if
1407
+ any(kw in str(col) for kw in
1408
+ ["人员", "负责人", "经办人", "对接人", "联系人", "使用人", "成交人", "交付人",
1409
+ "品牌专家", "姓名", "销售", "维护人", "操作人", "更新人", "转移人", "执行人",
1410
+ "人"])]
1411
+ # 识别公式相关列(可根据实际列名关键词调整)
1412
+ formula_related_columns = [col for col in all_fields if
1413
+ any(kw in str(col) for kw in ["公式", "计算", "合计", "总和", "金额", "数量", "天数"])]
1161
1414
  self._wechat_doc_log(f"识别到需转换的日期相关列:{date_related_columns}")
1415
+ self._wechat_doc_log(f"识别到人员相关列:{person_related_columns}")
1416
+ self._wechat_doc_log(f"识别到公式相关列:{formula_related_columns}")
1162
1417
 
1163
1418
  for record in all_records:
1164
1419
  row = {
@@ -1170,15 +1425,49 @@ class DataProcessingAndMessaging:
1170
1425
 
1171
1426
  values = record.get("values", {})
1172
1427
  for field_name, field_value in values.items():
1173
- if field_name in date_related_columns:
1428
+ # 1. 公式列优先解析
1429
+ if field_name in formula_related_columns:
1430
+ row[field_name] = _parse_formula_field(field_value)
1431
+ # 2. 日期相关列 - 时间转换
1432
+ elif field_name in date_related_columns:
1174
1433
  row[field_name] = _unified_time_convert(field_value)
1434
+ # 3. 人员相关列 - 先判断是否为有效格式,再解析
1435
+ elif field_name in person_related_columns:
1436
+ # 核心修改:先判断是否包含user_id的有效人员格式
1437
+ if _is_valid_person_format(field_value):
1438
+ row[field_name] = _parse_person_field(field_value)
1439
+ self._wechat_doc_log(f"列[{field_name}]值[{field_value}]符合人员格式,已解析")
1440
+ else:
1441
+ # 非有效格式,按普通字段处理
1442
+ if isinstance(field_value, list) and len(field_value) > 0:
1443
+ if isinstance(field_value[0], dict):
1444
+ # 处理普通列表字典,提取所有text/title并用逗号分隔
1445
+ text_values = []
1446
+ for item in field_value:
1447
+ text_val = item.get("text", item.get("title", str(item)))
1448
+ text_values.append(str(text_val))
1449
+ row[field_name] = ", ".join(text_values)
1450
+ else:
1451
+ row[field_name] = ", ".join([str(item) for item in field_value])
1452
+ else:
1453
+ row[field_name] = str(field_value) if field_value is not None else ""
1454
+ self._wechat_doc_log(f"列[{field_name}]值[{field_value}]非有效人员格式,按普通字段处理")
1455
+ # 4. 其他列 - 常规处理
1175
1456
  else:
1176
- if isinstance(field_value, list) and len(field_value) > 0:
1457
+ # 兼容普通列中嵌套公式格式的情况
1458
+ parsed_val = _parse_formula_field(field_value)
1459
+ if parsed_val != "":
1460
+ row[field_name] = parsed_val
1461
+ elif isinstance(field_value, list) and len(field_value) > 0:
1177
1462
  if isinstance(field_value[0], dict):
1178
- row[field_name] = field_value[0].get("text",
1179
- field_value[0].get("title", str(field_value[0])))
1463
+ # 处理普通列表字典,提取所有text/title并用逗号分隔
1464
+ text_values = []
1465
+ for item in field_value:
1466
+ text_val = item.get("text", item.get("title", str(item)))
1467
+ text_values.append(str(text_val))
1468
+ row[field_name] = ", ".join(text_values)
1180
1469
  else:
1181
- row[field_name] = str(field_value)
1470
+ row[field_name] = ", ".join([str(item) for item in field_value])
1182
1471
  else:
1183
1472
  row[field_name] = str(field_value) if field_value is not None else ""
1184
1473
 
@@ -1187,12 +1476,17 @@ class DataProcessingAndMessaging:
1187
1476
  df = pd.DataFrame(rows)
1188
1477
  df = df[df['更新时间'] != '0']
1189
1478
 
1190
- msg = f"成功读取{len(rows)}条记录到dataframe({len(date_related_columns)}个日期相关列已转为北京时间)"
1479
+ # 核心新增:转换全数字列为数值格式(包含公式列)
1480
+ df, numeric_columns = _convert_numeric_columns(df)
1481
+
1482
+ # 优化日志信息,包含数值列转换结果
1483
+ msg = (f"成功读取{len(rows)}条记录到dataframe({len(date_related_columns)}个日期相关列已转为北京时间,"
1484
+ f"{len(person_related_columns)}个人员相关列已检查格式并处理,{len(formula_related_columns)}个公式列已解析,"
1485
+ f"{len(numeric_columns)}个全数字列已转为数值格式:{numeric_columns})")
1191
1486
  self._wechat_doc_log(msg)
1192
1487
  print(f"[企业微信文档] {msg}")
1193
1488
 
1194
1489
  return df
1195
-
1196
1490
  # -------------------------- 企微文档:删除表格 --------------------------
1197
1491
  def wx_delete_table(self, docid):
1198
1492
  self._wechat_doc_log(f"开始删除文档:docid={docid}")
@@ -1209,8 +1503,12 @@ class DataProcessingAndMessaging:
1209
1503
  self._wechat_doc_log(error_msg)
1210
1504
  raise Exception(error_msg)
1211
1505
 
1212
- self._wechat_doc_log(f"文档删除成功:docid={docid}")
1213
- print(f"文档删除成功:docid={docid}")
1506
+ # 获取当前时间(北京时间,格式:YYYY-MM-DD HH:MM:SS)
1507
+ create_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1508
+ msg_content = f"企微文档删除成功:docid={docid} \n 删除时间:{create_time}"
1509
+ self._wechat_doc_log(msg_content)
1510
+ print(msg_content)
1511
+ self.uxin_wx('dongyang', msg_content)
1214
1512
  return True
1215
1513
 
1216
1514
  # -------------------------- 辅助方法:检查文件是否被占用 --------------------------
@@ -1,4 +1,4 @@
1
- # WeComMsg/__init__.py
1
+ # Functions_d/__init__.py
2
2
 
3
3
  # 从核心代码文件中导入需要对外暴露的类/函数
4
4
  from .Functions_d import DataProcessingAndMessaging
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Functions_d
3
- Version: 1.2.5
3
+ Version: 1.28
4
4
  Summary: 包含数据处理、Hive交互、企业微信消息发送、Excel操作等功能的工具类库
5
5
  Author: DongYang
6
6
  Author-email: 649898871@qq.com
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Functions_d
3
- Version: 1.2.5
3
+ Version: 1.28
4
4
  Summary: 包含数据处理、Hive交互、企业微信消息发送、Excel操作等功能的工具类库
5
5
  Author: DongYang
6
6
  Author-email: 649898871@qq.com
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = Functions_d
3
- version = 1.2.5
3
+ version = 1.28
4
4
  author = DongYang
5
5
  author_email = 649898871@qq.com
6
6
  description = 包含数据处理、Hive交互、企业微信消息发送、Excel操作等功能的工具类库
File without changes
File without changes
File without changes