Prefect 同步跳过缺失表并推送飞书提醒

This commit is contained in:
BXY
2026-09-11 11:13:16 +08:00
parent 0d1ff763d7
commit a71dd66504
4 changed files with 90 additions and 13 deletions
+66 -3
View File
@@ -2,7 +2,7 @@ import json
import os
import re
from collections import defaultdict
from dataclasses import dataclass
from dataclasses import dataclass, field
from datetime import date, datetime, timedelta, timezone
from decimal import Decimal
from pathlib import Path
@@ -380,6 +380,17 @@ class MetricSyncArtifacts:
created_fields: set[str]
@dataclass(frozen=True, order=True)
class MissingTable:
project: str
year: int
month: int
class TargetTableNotFoundError(ValueError):
"""Raised when a project's monthly Feishu table does not exist."""
@dataclass(frozen=True)
class DailySyncConfig:
base_url: str
@@ -400,6 +411,7 @@ class DailySyncContext:
mapping: dict[str, str]
table_overrides: dict[str, str]
config: DailySyncConfig
missing_tables: set[MissingTable] = field(default_factory=set)
def _log_to_prefect(level_name: str, message: str) -> None:
@@ -938,6 +950,7 @@ def send_feishu_sync_card(
base_url: str,
start_date: date,
end_date: date,
extra_content: str = "",
) -> None:
if not webhook_url:
log_step("未配置飞书群机器人 webhook,跳过群消息推送")
@@ -958,6 +971,7 @@ def send_feishu_sync_card(
"content": (
f"**同步范围**\n{start_date.isoformat()} 至 {end_date.isoformat()}\n\n"
f"**多维表格**\n[点击打开多维表格]({base_url})"
+ (f"\n\n{extra_content}" if extra_content else "")
),
},
}
@@ -1005,6 +1019,7 @@ def send_feishu_app_card(
end_date: date,
image_path: str = "",
token: str | None = None,
extra_content: str = "",
) -> None:
if not chat_id:
log_step("未配置 FEISHU_CHAT_ID,跳过应用机器人卡片推送")
@@ -1019,6 +1034,7 @@ def send_feishu_app_card(
"content": (
f"**同步范围**\n{start_date.isoformat()} 至 {end_date.isoformat()}\n\n"
f"**多维表格**\n[{title}]({base_url})"
+ (f"\n\n{extra_content}" if extra_content else "")
),
},
}
@@ -1118,7 +1134,7 @@ def find_target_table(
return candidates[0]
if not candidates:
all_table_names = [table.name for table in tables]
raise ValueError(
raise TargetTableNotFoundError(
f"未找到匹配表: 项目={project}, 月份={year:04d}-{month:02d}, 项目相关表={project_related_tables or '[]'}, 全部表={all_table_names}"
)
raise ValueError(
@@ -1715,8 +1731,15 @@ def run_daily_sync_for_date(context: DailySyncContext, stat_date: date) -> None:
}
try:
table = find_target_table(context.tables, project, stat_date.year, stat_date.month, context.table_overrides)
except ValueError as exc:
except TargetTableNotFoundError as exc:
if context.config.skip_missing_tables:
missing_table = MissingTable(project=project, year=stat_date.year, month=stat_date.month)
if missing_table not in context.missing_tables:
context.missing_tables.add(missing_table)
log_step(
"记录缺失表: "
f"项目={project}, 月份={stat_date.year:04d}-{stat_date.month:02d}"
)
log_step(f"跳过: {exc}")
continue
raise
@@ -1815,3 +1838,43 @@ def send_sync_notification(
start_date=start_date,
end_date=end_date,
)
def send_missing_table_notification(
context: DailySyncContext,
missing_tables: set[MissingTable],
start_date: date,
end_date: date,
) -> None:
"""Notify Feishu once after a sync run about tables skipped because they do not exist."""
if context.config.dry_run or not missing_tables:
return
table_lines = "\n".join(
f"- {item.project}:{item.year:04d}-{item.month:02d}" for item in sorted(missing_tables)
)
extra_content = (
"**以下月份表不存在,已跳过写入**\n"
f"{table_lines}\n\n"
"请创建对应月份表后重新执行补数。"
)
title = "GMV 数据同步:缺失表提醒"
if context.config.chat_id:
send_feishu_app_card(
chat_id=context.config.chat_id,
title=title,
base_url=context.config.base_url,
start_date=start_date,
end_date=end_date,
token=context.client.token,
extra_content=extra_content,
)
return
send_feishu_sync_card(
webhook_url=context.config.group_webhook,
title=title,
base_url=context.config.base_url,
start_date=start_date,
end_date=end_date,
extra_content=extra_content,
)