feat: 2026-04-15~05-02 累积变更基线 — AI 重构 + Runtime Context + DWS 修复
涵盖(每条对应已存的审计记录): - AI 模块拆分:apps/backend/app/ai/apps -> prompts/(8 个 APP + app2a 派生) audit: 2026-04-20__ai-module-complete.md - admin-web AI 管理套件:AIDashboard / AIOperations / AIRunLogs / AITriggers / TriggerManager audit: 2026-04-21__admin-web-ai-management-suite.md - App2 财务洞察 prompt v3 -> v5.1 + 小程序 AI 接入(chat / board-finance) audit: 2026-04-22__app2_prompt_v5_1_and_miniprogram_ai_insight.md - App2 prewarm 全过滤器 + AI 触发器 cron reschedule audit: 2026-04-21__app2-finance-prewarm-all-filters.md migration: 20260420_ai_trigger_jobs_and_app2_prewarm.sql / 20260421_app2_prewarm_cron_reschedule.sql - AppType 联合类型对齐 + adminAiAppTypes.test.ts audit: 2026-04-30__admin_web_ai_app_type_alignment.md - DashScope tokens_used 提取修复 audit: 2026-04-30__backend_dashscope_tokens_used_extraction.md - App3 线索完整详情 prompt audit: 2026-05-01__backend_app3_full_detail_prompt.md - Runtime Context 沙箱(5-1~5-2 主线): - 后端 schema/service + admin_runtime_context / xcx_runtime_clock 两个 router - admin-web RuntimeContext.tsx + miniprogram runtime-clock.ts - migration: 20260501__runtime_context_sandbox.sql - tools/db/verify_admin_web_sandbox.py + verify_sandbox_end_to_end.py - database/changes: 7 份 sandbox_* 验证报告 - 飞球 DWS 修复:finance_area_daily 区域汇总 + task_engine 调整 + RLS 视图业务日上界(migration 20260502 + scripts/ops/gen_rls_business_date_migration.py) 合规: - .gitignore 启用 tmp/ 排除 - 不入仓:apps/etl/connectors/feiqiu/.env(API_TOKEN secret,本地修改保留) 待验证清单: - docs/audit/changes/2026-05-04__cumulative_baseline_pending_verification.md 每个主题的功能完整性 / 上线验证几乎都未收口,按优先级 P0~P3 逐一处理
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
- 非 all 行现金流/卡消费/充值字段 = 0
|
||||
- hall 行 = 各具体区域之和(历史兼容)
|
||||
- all 行 = 各具体区域之和(收入/优惠),现金流/充值/卡消费来自 dws_finance_daily_summary
|
||||
无台桌订单和补时长等 all-only 台区只合入 all,不合入具体区域
|
||||
- settle_type IN (1, 3) 过滤
|
||||
- discount_gift_card 使用赠送卡消费金额口径
|
||||
|
||||
@@ -107,6 +108,9 @@ _COUNT_FIELDS = {"order_count", "member_order_count"}
|
||||
|
||||
_ZERO = Decimal("0")
|
||||
|
||||
# 已知不属于看板 7 个具体区域、但应合入 all 的物理台区。
|
||||
_ALL_ONLY_AREA_NAMES = {"补时长", "虚拟台"}
|
||||
|
||||
|
||||
class FinanceAreaDailyTask(FinanceBaseTask):
|
||||
"""
|
||||
@@ -177,6 +181,7 @@ class FinanceAreaDailyTask(FinanceBaseTask):
|
||||
sql = f"""
|
||||
SELECT
|
||||
{biz_expr} AS stat_date,
|
||||
sh.table_id AS table_id,
|
||||
dt.site_table_area_name AS area_name,
|
||||
sh.settle_type,
|
||||
-- 收入
|
||||
@@ -378,8 +383,9 @@ def transform_area_daily(
|
||||
)
|
||||
# 收集所有涉及的日期
|
||||
all_dates: set[date] = set()
|
||||
# 未知区域名称计数(汇总后一次性输出,避免逐行 warning 产生大量日志噪音)
|
||||
# 未知/无具体区域计数(汇总后一次性输出,避免逐行日志噪音)
|
||||
_unknown_area_counts: Dict[str, int] = defaultdict(int)
|
||||
_all_only_area_counts: Dict[str, int] = defaultdict(int)
|
||||
|
||||
for row in settlement_rows:
|
||||
sd = row.get("stat_date")
|
||||
@@ -393,10 +399,15 @@ def transform_area_daily(
|
||||
continue
|
||||
|
||||
area_name = row.get("area_name")
|
||||
table_id = row.get("table_id")
|
||||
area_code = resolve_area_code(area_name)
|
||||
|
||||
if area_code is None:
|
||||
_unknown_area_counts[str(area_name)] += 1
|
||||
unmatched_label = _format_unmatched_area_label(area_name, table_id)
|
||||
if _is_all_only_area(area_name, table_id):
|
||||
_all_only_area_counts[unmatched_label] += 1
|
||||
else:
|
||||
_unknown_area_counts[unmatched_label] += 1
|
||||
|
||||
# 提取金额
|
||||
table_fee = safe_decimal_fn(row.get("table_fee_amount", 0))
|
||||
@@ -479,11 +490,20 @@ def transform_area_daily(
|
||||
for k, v in fields.items():
|
||||
bucket[k] = bucket[k] + v
|
||||
|
||||
# 汇总输出未知区域名称(避免逐行 warning 刷屏)
|
||||
# 汇总输出 all-only 区域(无台桌订单、补时长等),这些记录合入 all 属正常口径。
|
||||
if _all_only_area_counts:
|
||||
summary = ", ".join(f"'{k}': {v}次" for k, v in _all_only_area_counts.items())
|
||||
logger.info(
|
||||
"DWS_FINANCE_AREA_DAILY: 共 %d 条结算单无具体区域(已计入 all,不计入任何具体区域): %s",
|
||||
sum(_all_only_area_counts.values()),
|
||||
summary,
|
||||
)
|
||||
|
||||
# 汇总输出真正未知区域名称(避免逐行 warning 刷屏)
|
||||
if _unknown_area_counts:
|
||||
summary = ", ".join(f"'{k}': {v}次" for k, v in _unknown_area_counts.items())
|
||||
logger.warning(
|
||||
"DWS_FINANCE_AREA_DAILY: 共 %d 条结算单区域未匹配(已计入 all 但不计入任何具体区域): %s",
|
||||
"DWS_FINANCE_AREA_DAILY: 共 %d 条结算单区域未匹配(已计入 all 但不计入任何具体区域,请检查 dim_table/AREA_LABEL_MAP): %s",
|
||||
sum(_unknown_area_counts.values()),
|
||||
summary,
|
||||
)
|
||||
@@ -618,4 +638,42 @@ def _safe_decimal(value: Any, default: Decimal = _ZERO) -> Decimal:
|
||||
return default
|
||||
|
||||
|
||||
def _is_all_only_area(area_name: Any, table_id: Any) -> bool:
|
||||
"""判断结算单是否属于无具体区域但应合入 all 的正常口径。
|
||||
|
||||
CHANGE 2026-05-02 | 扩大豁免规则,避免噪音 WARNING:
|
||||
- "补时长" / "虚拟台" 的带数字/空格变体(如 "补时长2"、"虚拟台 1")也算 all-only。
|
||||
- 维表 site_table_area_name 为空(NULL)但有 table_id 的脏数据,归入 all-only INFO,
|
||||
因为这通常是 dim_table SCD2 缺区域名而非真正映射缺口;金额仍合入 all 不丢失。
|
||||
真正的「未知非空区域名」(如新店自定义命名未在 AREA_LABEL_MAP 中)才进 WARNING。
|
||||
"""
|
||||
if area_name is None:
|
||||
# 无 table_id:本来就没台桌,正常 all-only
|
||||
# 有 table_id:维表区域名缺失,作为 dim_table 数据问题,仍归 all-only 但保留可观测性(INFO 行会带 'table_id=… None' 标签)
|
||||
return True
|
||||
if not isinstance(area_name, str):
|
||||
return False
|
||||
name = area_name.strip()
|
||||
if not name:
|
||||
return True
|
||||
if name in _ALL_ONLY_AREA_NAMES:
|
||||
return True
|
||||
# 形如 "补时长2"、"补时长 3"、"虚拟台4" 等编号变体
|
||||
for prefix in _ALL_ONLY_AREA_NAMES:
|
||||
if name.startswith(prefix):
|
||||
tail = name[len(prefix):].strip()
|
||||
if not tail or tail.isdigit():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _format_unmatched_area_label(area_name: Any, table_id: Any) -> str:
|
||||
"""格式化未匹配区域日志标签,区分无台桌订单和维表缺口。"""
|
||||
if area_name is None and not table_id:
|
||||
return "无台桌"
|
||||
if area_name is None:
|
||||
return f"table_id={table_id}: None"
|
||||
return str(area_name)
|
||||
|
||||
|
||||
__all__ = ["FinanceAreaDailyTask", "transform_area_daily"]
|
||||
|
||||
@@ -42,7 +42,10 @@ load_dotenv(_REPO_ROOT / ".env", override=False)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_TIMEOUT = (5, 30)
|
||||
# CHANGE 2026-05-02 | 旧值 (5, 30) 在 recall_completion_check / task_generator 这种长任务下
|
||||
# 经常 30s 读超时(实际处理 ~33s 以上)。临时止血提到 600s 与 flow_runner 对齐;
|
||||
# 长期方案是后端 /api/internal/run-job 改异步入队(参见 docs/database/changes/2026-05-02__sandbox_complete_refactor.md 已知未覆盖)
|
||||
_TIMEOUT = (10, 600)
|
||||
|
||||
# HTTP 模式<E6A8A1><E5BC8F><EFBFBD>按顺序执行的后端任务
|
||||
_JOB_SEQUENCE = [
|
||||
|
||||
Reference in New Issue
Block a user