涵盖(每条对应已存的审计记录): - 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 逐一处理
62 KiB
AI 应用 APP1-APP8 功能需求 - 目的检查文档
版本:2026-04-22 (v4,全景资料集成:8 维度并行调研 — 生产端 / DB schema / 合规 / MCP / admin-web / 后端路由服务 / 历史审计 / P2/P3 外围) 适用范围:
apps/backend/app/ai/+apps/admin-web/src/pages/AI*+apps/miniprogram/miniprogram/+apps/demo-miniprogram/miniprogram/(UI 标杆) PRD 来源:
- docs/prd/AI需求2.md — 8 个 AI 应用功能/参数/返回格式主表
- docs/prd/ai-app-prompts.md — System Prompt 详细定义
- docs/prd/Neo_Specs/NS2-ai-prompt-refinement.md — 数据层/拼接/page_context 文本化
- docs/prd/specs/P5-miniapp-ai-integration.md — 小程序 AI 集成验收
- docs/prd/specs/P14-ai-dashscope-migration.md — 熔断限流预算硬约束
- docs/prd/specs/P15-ai-monitoring-testing.md — 监控后台与测试体系
0. 总体原则
- demo-miniprogram 是样式标杆(假数据驱动),生产 miniprogram 所有 AI 字段的排版/配色必须与 demo 一致。
- 前端不直接调用 DashScope,所有 AI 产物统一通过
ai_cache或 SSE 流式接口消费。 - 后端统一响应包装
{code:0, data:...},snake_case 自动转驼峰。 - cache miss 时前端必须清空陈旧数据。
- 告警统一走 WebSocket
/ws/ai-alerts/{site_id}。
0.1 PRD 硬性数字约束(权威表)
所有数字来自
docs/prd/精读,代码实现与此不符时以 PRD 为准并回退排查。
| 维度 | 约束值 | PRD 来源 |
|---|---|---|
| 应用总数 | 8(APP1-APP8) | AI 需求 2 |
| 财务洞察组合 | 8 时间 × 9 区域 = 72 | AI 需求 2 |
| 线索分类(APP3) | 3 个枚举 | P5 / ai-app-prompts |
| 线索分类(APP6/8) | 6 个枚举 | P5 / ai-app-prompts |
单条线索 summary |
≤ 20 字(简短可作标题) | AI 需求 2 |
单条线索 detail |
≤ 120 字 | AI 需求 2 |
APP7 summary 长度 |
200-400 字,开头定性短句 ≤ 25 字 | ai-app-prompts |
APP8 clues 数组 |
≤ 5 条 | ai-app-prompts |
APP5 tactics 数组 |
2-4 条,每条 script ≤ 150 字 |
AI 需求 2 |
APP6 clues 数组 |
0-10 条(按价值排序) | AI 需求 2 |
APP3 clues 数组 |
1-5 条(按价值排序) | AI 需求 2 |
| System prompt 上限(APP2-8) | ≤ 8000 字 | NS2 |
| System prompt 上限(APP1) | ≤ 4000 字 | NS2 |
| page_context 上限 | ≤ 2000 字 | NS2 / page_context.py:21 |
| Token 日预算 | 100,000 | P14 |
| Token 月预算 | 2,000,000 | P14 |
| APP1 限流 | 每用户每分钟 10 次 | P14 |
| APP2-8 限流 | 每门店每小时 100 次 | P14 |
| 熔断阈值 | 连续 5 次失败 OPEN | P14 |
| 熔断恢复窗口 | 60 秒 | P14 |
| 关系流失预警 | 停止消费 > 14 天 | ai-app-prompts |
| 高粘性阈值 | 月内消费 ≥ 3 次 | ai-app-prompts |
| 学习型阈值 | 助教费占比 > 40% | ai-app-prompts |
| 当期 < 7 天 | 必须降权表述("初步观察"/"不足为据") | ai-app-prompts |
| 财务洞察加载 | < 2 秒(秒级缓存读取) | P5 |
| 对话复用窗口(customer/coach/finance) | 3 天 | P5 |
| 对话复用窗口(task) | 无时限 | P5 |
| 对话复用(general) | 始终新建 | P5 |
0.2 跨 APP 编排链路(PRD 定义)
消费事件链(严格串行)
新结算单 → APP3 [await]
→ APP8 [await]
→ APP7 [await](读 APP8 最新缓存)
如含助教:
→ APP4 [await](读 APP8 最新缓存)
→ APP5
约束:APP7 与 APP4 均依赖 APP8 完成,确保上游线索就绪。
备注事件链
备注提交 → APP6 [await] → APP8 [await]
任务分配链
任务分配(priority_recall / high_priority_recall)
→ APP4 [await](读 APP8 已有缓存,miss 时 prompt 标注"暂无历史线索")
→ APP5
约束:任务链不等 APP8 执行,直接读缓存;消费链则必须等 APP8 完成才能执行 APP4/APP7。
1. APP1 聊天(流式对话 + 屏幕上下文)
功能目的(PRD)
店员/助教在小程序里与 AI 自由对话("这个客户最近消费变多了为什么"),支持 10 种页面入口带上下文,后端将上下文结构化为 ≤2000 字中文文本注入 prompt。PRD 参考:AI 需求 2 表首行 / P5 AC1 / NS2 page_context 设计。
后端请求 schema(schemas.py:16-23)
ChatStreamRequest {
message: str # 用户本轮输入
source_page: str | None # 来源页面标识(10 种枚举)
page_context: dict | None # 看板筛选参数(timeDimension/areaFilter/dimension/typeFilter/projectFilter)
screen_content: str | None # 「屏幕可见内容」快照(前端采集,后端原样拼入 prompt)← 当前 gap
}
10 种 sourcePage(page_context.py:24-36)
task-detail / customer-detail / coach-detail / board-finance / board-customer / board-coach / performance / my-profile / task-list / customer-service-records
前端消费链路
| 阶段 | 文件与行号 | 处理 |
|---|---|---|
| 浮按钮属性 | ai-float-button.ts:29-40 | 接收 sourcePage + pageFilters(JSON 对象) |
| 浮按钮跳转 | ai-float-button.ts:44-72 | URL 编码 JSON 到 querystring |
| Chat 解析 | chat.ts:234-259 | JSON.parse(decodeURIComponent(options.pageFilters)) ; 回退支持旧单键 timeDimension/areaFilter/dimension/typeFilter/projectFilter |
| SSE 发送 | chat.ts:487-490 | body: {chatId, content, sourcePage, pageContext} |
| 后端文本化 | page_context.py:39-96 | build_page_text(source_page, context_id, site_id, filters) → 结构化中文 ≤2000 字 |
| 后端注入 | xcx_chat.py | 将文本拼入 DashScope biz_params 或作为前置 system message |
screen_content「屏幕可见内容」特性(待实现 / 文档化 gap)
- schema 已预留字段(schemas.py:22)
- 预期采集方式(需前端实现):浮按钮 onTap 时通过
triggerEvent让父页面回传this.data中可见区块的快照(如 board-finance 当前展示的overview/recharge/revenue/...卡片数据),序列化为短 JSON 字符串 - 后端消费:原样拼入 prompt 系统消息(与
page_context互补:page_context 是后端查 DB 的结果;screen_content 是前端用户真实看到的数字) - 验证:两者都存在时,提示词顺序应为
screen_content(用户视角) → page_context(权威数据),避免矛盾时以 page_context 为准
各 sourcePage 携带字段示例
| sourcePage | pageFilters 字段 | 后端 page_context 输出要点 |
|---|---|---|
| board-finance | timeDimension + areaFilter |
汇总笔数 / 总营收 / 笔均 / 优惠率 / 储值活跃度 |
| board-customer | dimension + typeFilter |
Top 10 客户 + 排序维度 + 客群分类 |
| board-coach | dimension + projectFilter + timeDimension |
Top 10 助教 + 服务统计 + 技能筛选 |
| performance / task-list / my-profile | timeDimension |
个人绩效 / 任务列表 / 本人统计 |
| task-detail / customer-detail / coach-detail | 无 filters,走 contextId |
单实体详情 |
| customer-service-records | 无 | 服务记录流水 |
Demo 参考(UI 形态)
- chat.ts:消息气泡、引用卡片
referenceCard、逐字追加动效 - 进入路径:浮按钮仅在 board-*(看板类)与详情页挂载,距底部 200rpx(TabBar 页)/ 120rpx(非 TabBar)
验证点(细化)
- 财务看板任意 time/area 切换 → 点浮按钮 → 后端日志可见
build_page_text("board-finance", ...)返回文本含当前切换维度的数字 - 小程序 SSE body 中
pageContext为 JSON 对象而非字符串(后端解析不失败) pageFilters为空对象时不追加 URL 参数,走 general 对话- SSE ArrayBuffer 跨 chunk 的 UTF-8 中文字符无乱码(构造「当前营业」被 2 chunk 切分的测试)
done事件conversation_id返回后,下一轮发送透传该 id 保持多轮ai_run_logs.tokens_used按usage.models嵌套结构累加(非顶层 tokens)- 网络中断重试上限 2 次(2/4/8s 指数退避)
- screen_content gap 待确认:若本次上线计划启用,需在浮按钮 onTap 内实现页面快照 triggerEvent;否则保留 schema 占位并在文档注明"暂不采集"
2. APP2 财务洞察(72 组合预热)
功能目的
日结完成后预生成 8 时间维度 × 9 区域 = 72 组 财务洞察,命中看板时直接从 ai_cache 秒级读取。
返回 schema(schemas.py:93-104)
App2Result { insights: [ { seq: int, title: str, body: str } ] }
System Prompt 核心规则(ai_system_prompt_by_app.md:41-93)
- 收入构成三口径不可互换:发生额 / 成交收入 / 现金流入
- 5 类优惠拆解必须落到细分(团购/会员折扣/手动/赠送卡/分摊)
- 警戒线:优惠率 >30% / 助教薪酬占比 >40% / 充值占现金流入 25-40% 为健康
- seq=11/12 固定为板块 F「综合健康度」,置顶作为"本期总结"
- 当期 <7 天需降权表述
- 现金流出为 0 视为录入异常并高亮
target_id 拼装(dispatcher.py _app2_target_id)
${TIME_MAP[selectedTime]}__${selectedArea},例:this_month__hallA / last_quarter__all
TIME_MAP:month→this_month、lastMonth→last_month、week→this_week、quarter→this_quarter、half6→last_6_months
9 区域:all / hall / hallA / hallB / hallC / vip / snooker / mahjong / ktv
cache_type 切分(2026-04-23)
area='all'的 8 组 →app2_financearea!='all'的 64 组 →app2a_finance_area
Demo 展示形态(关键 UI 规范)
参考 board-finance.wxml:208-221:
┌─ AI 智能洞察(机器人 SVG 图标 + 标题)
│ 优惠率Top:团购(5.2%) / 大客户(3.1%) / 赠送卡(2.5%)
│ 差异最大:酒水(+18%) / 台桌(-5%) / 包厢(+12%)
│ 建议关注:充值高但消耗低,会员活跃度需提升
└─
字段到 UI 映射:
| 字段 | Demo 类 | 渲染规范 |
|---|---|---|
title |
.ai-insight-dim |
灰色引导词(如"优惠率Top:"/"差异最大:"),结尾带中文冒号 |
body |
.ai-insight-line 主体 |
白色正文,含数字与百分比 |
| 强调片段 | .ai-insight-underline |
关键词下划线(需 AI 用 **...** 标记,前端轻量 Markdown 渲染) |
| 顺序 | seq 升序 | seq=11/12 置顶为"本期总结" |
| 图标 | /assets/icons/ai-robot.svg |
必须 SVG,禁止 emoji 替代(demo 已标注 CHANGE 2026-03-12) |
验证点(细化)
- 72 组
target_id全覆盖(SQL:SELECT DISTINCT target_id FROM biz.ai_cache WHERE cache_type IN ('app2_finance','app2a_finance_area') AND site_id=? AND created_at::date=CURRENT_DATE应 = 72) seq=11/12在前端必定置顶(不按数字大小升序,需特判)title结尾是中文冒号:(便于 dim 样式识别)body中**xxx**标记被轻量 Markdown 渲染为 underline/加粗- area 切换后旧
aiInsights/aiInsightSummary/aiInsightDetails立即清空 - cache miss 时显示占位文案("正在生成洞察,请稍后"),不复用上次数据
- 现金流出为 0 时 AI 输出能明确指出录入异常(单元测试固化该场景)
- 当期 <7 天的时间维度(本周
this_week若当前周三)返回文本含降权用词("初步观察"/"不足为据"等) - Prompt
KEY_TRANSLATIONS(app2_finance_prompt.py:24-64)无缺字段 - 熔断/限流后剩余组跳过,
ai_cache.result_json不写 NULL
3. APP3 维客线索(消费端,3 分类)
功能目的
消费结算后从 DWS 消费记录提取 3 类线索(客户基础/消费习惯/玩法偏好),providers 固定「系统」。
返回 schema(schemas.py:71-78, 107-110)
App3Result { clues: [ ClueItem { category, summary, detail, emoji } ] }
System Prompt 核心规则(ai_system_prompt_by_app.md:97-125)
category严格 3 选 1(App3CategoryEnum)- 必须输出"下一步做什么"的行动导向,不得仅描述现象
- 消费结构解读:酒水 >40% = 休闲社交客;台费 >80% = 硬核客;助教费 >30% = 学习进阶客
前端消费(经 App8 合并后落 member_retention_clue)
App3 结果不直接渲染,由 App8 读 ai_cache 整合 → 写库 → 前端查客户详情时显示。
Demo 展示形态(customer-detail)
参考 customer-detail.ts:99-145 + customer-detail.wxml:92-110:
维客线索 [AI 徽章]
┌─ [客户/基础] 🎂 生日 3月15日 · VIP会员 · 注册2年 By:系统
├─ [消费/习惯] 🌙 常来夜场 · 月均4-5次 By:系统
├─ [消费/习惯] 💰 高客单价 By:系统
│ 近60天场均消费 ¥420,高于门店均值 ¥180;...
└─ ...
字段到 UI 映射:
| schema 字段 | Demo clue-card 属性 |
UI 规范 |
|---|---|---|
category |
tag |
两行显示(如"客户\n基础"),配色 categoryColor 按类映射(primary/success/purple/warning/pink/error) |
summary |
title(含 emoji 前缀) |
主文案,短句 30 字内 |
emoji |
拼在 title 开头 |
1 个 emoji,类别相关 |
detail |
content(展开区) |
50-100 字展开,可折叠 |
providers(App8 合并后补) |
source("By:系统"/"By:小燕") |
区分系统推断 vs 备注来源 |
验证点(细化)
category ∈ {客户基础, 消费习惯, 玩法偏好}(Pydantic 校验 + 前端categoryColor映射无 undefined)emoji字段必填且为单个 emoji(正则/^[\p{Emoji}]+$/u)summary以行动导向动词结尾("可推..."/"建议..."/"需..."),非纯描述(单测)detail长度 ≥ 50 字且 ≤ 150 字- DWS 取数失败时降级到
_default_member_data,不导致 dispatcher 崩溃 - Prompt 超 4000 字后仍保留完整消费明细;2026-05-01 合成 100 条完整明细真实调用成功返回
- 团购客(无储值卡)与储值会员识别正确(AI 不得混淆)
4. APP4 关系分析(任务链首)
功能目的
助教参与消费 / 任务分配时,分析助教-会员关系,产出任务描述、行动建议、一句话总结。
返回 schema(schemas.py:113-118)
App4Result {
task_description: str # 任务详述
action_suggestions: [str] # 行动建议数组
one_line_summary: str # 一句话总结
}
System Prompt 核心规则(ai_system_prompt_by_app.md:129-156)
- 输出必含:关系评级(紧密/一般/疏远)+ 核心原因 + 风险/机会点
- 粘性阈值:月内 ≥3 次 = 高粘性;激励课占比 >40% = 学习型
- 停止消费 >14 天 = 流失预警
Demo 展示形态(task-detail 顶部 Banner)
关系等级:很好 heartScore=8.5
banner 背景根据 taskType 动态切换 SVG
字段到 UI 映射:
| schema 字段 | Demo 字段 | 位置 |
|---|---|---|
one_line_summary |
Banner 下方副标题 | 任务详情头部一行显示 |
task_description |
detail.taskTypeLabel + 主内容 |
任务卡片主体 |
action_suggestions[] |
勾选式建议列表 | 可勾选动作项,完成后记录 |
验证点(细化)
action_suggestions为非空 list,每条 ≤ 40 字one_line_summary含关系评级词("紧密/一般/疏远")+ 数字target_id = ${assistant_id}_${member_id},供 App5 复用- 助教数据缺失时
warnings数组被填充,前端显示降级提示 - Prompt 截断
_MAX_PROMPT_LEN=8000保留关键关联字段
5. APP5 话术参考(任务链尾)
功能目的
基于 App4 的 one_line_summary 与关系评级,生成针对性微信/当面沟通话术。
返回 schema(schemas.py:121-131)
App5Result { tactics: [ { scenario: str, script: str } ] }
System Prompt 核心规则(ai_system_prompt_by_app.md:160-192)
- 微信私信:口语、短、配 1 个 emoji,不群发感
- 当面沟通:引导式提问 > 直接推销
- 会员分层:新客试听 / 成长储值 / 核心赛事邀请 / 流失限时券
- 助教不能:打任意折扣 / 承诺 KOL 免费陪打
- 输出需标注:适用场景 + 建议发送时段 + 预期反应
Demo 展示形态(task-detail 话术区)
话术参考
┌─ 王哥您好,好久不见!最近店里新到了几张国际标准斯诺克球桌... [复制]
├─ 王哥,最近忙吗?这周末我们有个老客户专属球友交流赛... [复制]
└─ ... 共 5 条
字段到 UI 映射:
| schema 字段 | Demo talkingPoints |
规范 |
|---|---|---|
scenario |
折叠区 hint("适用:流失预警"/"适用:储值召回") | 小字副标题,可选展示 |
script |
话术主体 | 长按可复制,配置 copiedIndex 高亮反馈 |
验证点(细化)
tactics长度 3-5 条(Prompt 约束)script含会员姓名或称呼(如"王哥"),非模板化空话script含 0-1 个 emoji(emoji_count <= 1)- 不出现"8折/免费/KOL/抽成"等越权用词(黑词正则拦截)
- App4 失败时 App5 跳过(串行依赖,
context.app4_result is None不执行) - 长按复制触发
wx.setClipboardData成功 target_id与 App4 一致(${assistant_id}_${member_id})
6. APP6 备注分析(备注链首)
功能目的
助教提交备注后打分 1-10 并提取 6 类线索;providers = 备注提交人真实姓名。
返回 schema(schemas.py:134-138)
App6Result {
score: int (ge=1, le=10)
clues: [ ClueItem { category, summary, detail, emoji } ]
}
System Prompt 核心规则(ai_system_prompt_by_app.md:196-220)
- 忠于备注原文,不延伸推测
- 8 维度归一到 6 分类(App6CategoryEnum)
- 一条备注可对应多个维度
- 情感倾向(正面/中性/负面)影响后续助教触达开场白
- 标注备注是谁写的、何时写的(时效性)
前端消费
App6 结果不直接渲染,经 App8 整合后写入 member_retention_clue 表,前端查客户详情展示(source 字段显示"By:小燕")。
Demo 展示形态(客户详情「维客线索」中显示 6 类)
参考 customer-detail.ts:99-145 7 条 clue 分布:
- 客户基础 × 1(生日/VIP)
- 消费习惯 × 2(夜场/高客单)
- 玩法偏好 × 1(中式斯诺克)
- 促销偏好 × 1(酒水套餐敏感)
- 社交关系 × 1(固定球搭子)
- 重要反馈 × 1(斯诺克走位需求)
验证点(细化)
score ∈ [1,10]严格校验(Pydanticge=1, le=10)category ∈ {客户基础, 消费习惯, 玩法偏好, 促销偏好, 社交关系, 重要反馈}providers为备注提交人姓名(非"系统"、非空)- 备注原文中的专有名词("李哥"、"阿杰"、"A12号台")保留在
detail中 - 备注 >8000 字时截断但保留情感词与动作词
- score 写入
ai_cache.score列(非result_json.score,供后端排序)
7. APP7 客户分析(消费链尾,客户画像)
功能目的
消费链 App8 完成后串行触发,综合消费 + 备注 + 助教关系输出 200-400 字客户画像。
返回 schema(schemas.py:141-152)
App7Result {
strategies: [ { title: str, content: str } ]
summary: str # 画像主文
}
System Prompt 核心规则(ai_system_prompt_by_app.md:224-257)
- 开头一句话定性("工作日晚间的上班族硬核玩家")
- 中段数字:消费结构、频次、客单、助教绑定
- 结尾助教可动 1-2 条建议
- 避免评判语言("消费低"改为"客单 60 元偏低于店均 120 元")
- 必须标注数据时间窗("近 30 天 / 近 90 天")
- 备注信息需带【来源:人名,请甄别真实性】标注(app7_customer_prompt.py:64-77)
Demo 展示形态(customer-detail 头部)
参考 customer-detail.ts:91-98 + customer-detail.wxml:69-90:
🤖 AI 智能洞察
┌─ summary:高价值 VIP 客户,月均到店 4-5 次,偏好夜场中式台球,
│ 近期对斯诺克产生兴趣。社交属性强...
│ 当前推荐策略
│ ■ [green] 最后到店距今 12 天,超出理想间隔 7 天,建议...
│ ■ [amber] 客户提到想练斯诺克走位,可推荐专项课程包...
│ ■ [pink] 社交属性强,可邀请参加球友赛事活动...
└─
字段到 UI 映射:
| schema 字段 | Demo 字段 | UI 规范 |
|---|---|---|
summary |
aiInsight.summary |
开头白色正文段 |
strategies[].title |
策略颜色 color 枚举前缀 |
不直接渲染(配色由前端枚举轮转:green/amber/pink/...) |
strategies[].content |
strategy-text |
策略卡片主体,每条独立配色条 |
| 末条样式 | .strategy-item-last |
最后一条无下边距 |
注意:demo 中 strategies[].color 是前端枚举色(green/amber/pink),AI 返回的 title 字段应映射为 emoji/关键词而非颜色。实际代码中前端按 index % 6 循环配色(不依赖 AI)。
验证点(细化)
summary开头为"定性短句"(句号前 ≤ 25 字)summary包含时间窗关键词("近 30 天"/"近 90 天"/"近 60 天")summary长度 200-400 字strategies长度 2-3 条,前端展示最多 2 条(demo 规范index < length - 1为非末,index === length - 1为末条)- 评判性词("差"/"低"/"糟糕")出现时应改写为对比表述(AI 侧或后处理)
- 备注来源标注【来源:xxx,请甄别真实性】进入 prompt
- 消费链中断时 App7 跳过(App8 未完成不执行)
- cache_type =
app7_customer_analysis,target_id =member_id
8. APP8 线索整合(写库幂等)
功能目的
整合 App3 + App6 全部线索 → 去重、统一 6 分类、providers 逗号拼接 → 幂等写 member_retention_clue 表。
返回 schema(schemas.py:80-88, 155-158)
App8Result {
clues: [ ConsolidatedClueItem {
category: str # 6 选 1
summary: str # 30 字内行动导向
detail: str # 50-100 字展开
emoji: str # 分类对应 emoji
providers: str # "消费数据" / "店员小燕备注" / 逗号拼接多源
} ]
}
System Prompt 核心规则(ai_system_prompt_by_app.md:261-290)
- 冲突处理:备注原文 > 行为推断
summary禁放数字(数字放detail)- 线索排序:可直接动作 > 身体偏好 > 长期画像
- 不超过 5 条(助教可读性)
- 不输出泛化建议("请关心会员"禁止)
落库规则(dispatcher.py _write_retention_clue)
BEGIN;
DELETE FROM app.member_retention_clue
WHERE site_id=:site AND member_id=:member AND source IN ('ai_consumption','ai_note');
INSERT INTO app.member_retention_clue (...) VALUES (...);
COMMIT;
- source 取值:消费链触发 →
ai_consumption;备注链触发 →ai_note source='manual'(人工线索)绝不删除
Demo 展示形态(7 条线索在客户详情,见 App3 表格)
验证点(细化)
DELETE+INSERT在同一事务内(pg_stat_activity 查询验证)- 写入后
source='manual'行数不变(跨事务前后 count 一致) - 同日多次消费 / 多条备注不会让线索重复累积(幂等)
providers多源拼接去重(如"消费数据、备注-小燕",不出现"消费数据、消费数据")summary不含数字(正则/[0-9%¥]/不匹配)detail含数字(与 summary 分工)clues数组长度 ≤ 5- 排序:可直接动作类("推课"/"约时段")在前
- App3 或 App6 任一失败时,App8 仍执行但对应
_clues=[] - cache_type =
app8_clue_consolidated
9. 管理后台通用检查
AIDashboard(总览)
- AIDashboard.tsx:120
getDashboard() - 字段:
today_calls/today_success_rate/today_tokens/today_avg_latency_ms/trend_7d - 数值与
biz.ai_run_logs聚合一致 trend_7d折线图日期连续无跳点,单位 ms / % / 次- 切换 site_id 过滤器后数字按租户隔离
AIOperations(手动执行)
- AIOperations.tsx:67
runApp(appType, {site_id, member_id})支持 App3-App8(App1/App2 不适用) - AIOperations.tsx:146
triggerEvent({event_type, site_id, member_id, is_forced})越过去重 is_forced=true可绕过当日去重,ai_trigger_jobs新增一行- 返回
job_id,tag 色processing → success/failed - WebSocket
/ws/ai-alerts/{site_id}接收circuit_open / rate_limited / budget_exceeded / timeout / failed
TriggerManager(调度配置)
- TriggerManager.tsx:183
updateTriggerConfig(id, {cron_expression, interval_seconds}) - cron 表达式校验通过才允许保存
- 改动后调度器热加载(Scheduler lifespan 监听 meta.scheduled_tasks)
10. 跨切面共性验证
| 维度 | 检查项 | 验证方法 |
|---|---|---|
| RunLog | 状态转换 pending→running→success|failed|timeout|circuit_open|rate_limited|budget_exceeded |
SELECT status, COUNT(*) FROM biz.ai_run_logs GROUP BY status 覆盖 8 种 |
| RunLog | success 必有非零 tokens_used |
SELECT COUNT(*) FROM biz.ai_run_logs WHERE status='success' AND tokens_used=0 = 0 |
| RunLog | request_prompt ≤ 8000 字符 |
SELECT MAX(length(request_prompt)) ≤ 8000 |
| 熔断 | 连续失败 N 次后 OPEN | 人工注入失败 → 第 N+1 次日志 status=circuit_open 且无真实 DashScope 调用 |
| 限流 | 分钟/日双维度 | 压测超限后 rate_limited 数突增,tokens_used IS NULL |
| 预算 | 不足时记 budget_exceeded:<reason> |
人为把日预算调至 0 → 下一次调用日志为 budget_exceeded:daily_budget_used_up |
| 缓存 | result_json IS NULL 行数应为 0 |
SELECT COUNT(*) FROM biz.ai_cache WHERE result_json IS NULL = 0 |
| 缓存 | cache_type 枚举齐全 |
SELECT DISTINCT cache_type 仅含 CacheTypeEnum 的 8 个值 |
| 去重 | 同 (event_type, member_id, site_id, date) 当天仅 1 次 |
SELECT event_type, member_id, COUNT(*) 当 is_forced=false 时均为 1 |
| 多租户 | SET LOCAL app.current_site_id |
tcpdump / SQL trace 首条 SQL 为 SET LOCAL |
| 告警推送 | WebSocket 接收 5 类 alert_type |
前端 devtools Network → WS 抓包校验 |
| 前端降级 | cache miss 清空旧数据 | 切换 area → DevTools Inspect aiInsights 短暂为 [] |
| 前端降级 | SSE 断流重试 | chrome network throttle offline → 看 retry 2/4/8s |
| Demo 对齐 | 字段 → UI 映射一致 | 并排截图 miniprogram vs demo-miniprogram 像素对齐 |
11. 自测清单(按角色)
前端工程师
客户详情页
- App7
summary开头为定性短句且含时间窗 - App7 strategies 末条 CSS 类
.strategy-item-last生效(无下边距) - App3/6/8 合并后 clues 配色按
categoryColor映射无 undefined - clue-card
source字段区分 "By:系统" vs "By:{人名}" - 展开折叠
detail动画无抖动
任务详情页
- App4
one_line_summary在 banner 副标题 - App5
tactics[].script长按复制成功 + toast 反馈 action_suggestions勾选持久化到本地
财务看板
- 9 区域 × 5 时间 = 45 种组合切换,AI 洞察正确加载或清空
title以冒号结尾、.ai-insight-dim灰色样式生效**xxx**渲染为 underline- seq=11/12 置顶"本期总结"
- 图标为 SVG(
/assets/icons/ai-robot.svg),非 emoji
聊天页
- 浮按钮从 6 类页面进入都正确带
sourcePage+pageFilters - SSE 中文无乱码
done事件替换临时消息 ID- 历史会话 / 客户 3 天内复用 / 任务复用 / 看板新建 四种入口均工作
- 重试 2/4/8s 指数退避
- (待确认)若实现
screen_content,浮按钮 triggerEvent 采集页面快照
后端工程师
ai_run_logs状态分布覆盖 8 种ai_cache无 NULLresult_jsonmember_retention_cluesource='manual'跨天数量稳定- 日结后 2 分钟内 72 组缓存写入完整
SET LOCAL app.current_site_id每次查询都执行- Pydantic schema 对 score/enum/长度的约束不被绕过
测试工程师
- demo-miniprogram 与 miniprogram 的客户详情 / 任务详情 / 财务看板像素级对齐
- customer-detail 页 AI 区块三部分互不干扰(summary / strategies / clues)
- 小程序热启动后 AI 洞察不闪烁旧数据
- admin-web Dashboard 与数据库聚合一致性
附录 A:已废弃 / 已迁移
apps/backend/app/ai/apps/app1_chat.py~app8_consolidation.py已删除,逻辑统一内聚至 dispatcher.py- APP2 于 2026-04-23 拆分为
app2_finance(8 组) +app2a_finance_area(64 组)
附录 B:已知 Gap / 待办(PRD 对齐清单)
来源:
docs/prd/精读对照当前代码与本 spec 的差异。
| ID | Gap | PRD 来源 | 当前状态 | 建议 |
|---|---|---|---|---|
| G1 | APP1 screen_content 采集(浮按钮 onTap triggerEvent 采集页面快照) |
AI 需求 2 / NS2 | schema 已预留 schemas.py:22 但 chat.ts:487 未传 | 浮按钮 triggerEvent('snapshot') → 父页返回 setData 可见子集 → URL/SSE body 透传;后端按 "screen_content → page_context" 顺序拼 prompt |
| G2 | APP2 72 组预热完整性校验 | P14 调度器 | 代码已拆 8+64 组,缺自动化核对 | 日结后 T+10min 执行 SELECT DISTINCT target_id FROM biz.ai_cache WHERE cache_type IN ('app2_finance','app2a_finance_area') AND site_id=? AND created_at::date=CURRENT_DATE,应 = 72;不足告警 |
| G3 | APP3 summary 行动导向动词约束 |
ai-app-prompts | prompt 未强化动词要求 | prompt 增加"必须以可推/建议/需/可约/可邀 等动词结尾";后端 schema 加正则;单测固化 |
| G4 | APP4 target_id 拼装规则文档化 |
P5 | 代码实现 {assistant_id}_{member_id} 但文档未固定分隔符 |
正式文档化分隔符为单下划线,拦截 assistant_id/member_id 含下划线的场景 |
| G5 | APP5 scenario 前端消费规范 |
AI 需求 2 | schema 定义但 UI 未约定 | 约定 task-detail 话术区 scenario 作为折叠 hint("适用:流失预警"),空字符串时不展示 |
| G6 | APP6 score 驱动前端 UI |
ai-app-prompts | 后端已写 ai_cache.score 列但前端未消费 |
备注列表按 score 降序排列;score ≥ 8 高亮、≤ 3 置灰 |
| G7 | APP7 strategies[].title 字段用途 |
ai-app-prompts | 前端按 index 循环配色,未用 title | 方案 A:删除 title;方案 B:AI 输出 title 作为徽章("机会"/"风险"/"日常"),前端展示 |
| G8 | APP8 相似线索去重判断标准 | AI 需求 2 / P5 | prompt 未定义相似度阈值 | prompt 增加"若两条线索 summary 字符重合 ≥ 60% 或 category 相同且描述同一行为,合并为一条,providers 拼接去重" |
| G9 | 熔断/限流/预算硬约束压测 | P14 | 代码实现但无压测报告 | 压测:APP1 单用户 11 次/分钟应触发 rate_limited;连续 5 次失败后第 6 次 status=circuit_open 且不真实调用;日 token 达 100k 后 status=budget_exceeded |
| G10 | 日结触发 APP2 预热延迟 | P14 | 代码"日结后轮询"无明确延迟 | 文档固定 "日结完成事件 → T+5min 开始调度 APP2 × 72 组,T+15min 应全部完成";超时告警 |
| G11 | RLS 多租户隔离强制验证 | P5 / backend CLAUDE.md | 代码每次查询前 SET LOCAL,缺自动验证 | 集成测试:伪造 site_id=A 的 JWT 访问 member_id 属于 site_id=B 的数据应返回 404;抓 pg_stat_statements 验证 SET LOCAL 存在 |
| G12 | 新客(无消费/无备注)降级文案 | NS2 | 代码有 _default_member_data 降级但 prompt 文案未标准化 |
定义模板:「该客户暂无历史线索,以下基于本次消费进行初步判断」,dispatcher 注入 prompt 开头 |
| G13 | APP8 providers 多源拼接格式 |
ai-app-prompts | 代码拼接但分隔符未固定 | 固定格式:"消费数据,备注-{人名}",中文逗号分隔,备注人不重复(用 set 去重) |
| G14 | APP4 任务链 cache miss 时的 prompt 提示 | ai-app-prompts | 代码读缓存失败时传空 | prompt 标注"暂无 APP8 历史线索,请基于基线数据分析",而非丢空字符串 |
| G15 | APP2 **xxx** 强调标记约定 |
Demo 规范 | 前端已做轻量 Markdown,但 prompt 未要求 AI 输出该标记 | prompt 增加"对关键结论或异常词用 **粗体** 包裹,前端会渲染为下划线强调" |
附录 D:PRD 硬性阈值对照表(验收单)
单独抽出所有数字阈值,供 QA 断言时直接引用。
| APP | 字段/指标 | 阈值 | 不达标处理 |
|---|---|---|---|
| APP1 | 单轮 prompt | ≤ 4000 字 | 触发截断保留最近 N 轮 |
| APP1 | page_context | ≤ 2000 字 | page_context.py 末尾附"…(上下文已截断)" |
| APP1 | 限流 | 10 次/分钟/用户 | rate_limited 记录,前端提示"操作过快" |
| APP1 | 复用窗口 | customer/coach/finance 3 天;task 无限;general 新建 | 超窗口新建会话 |
| APP2 | 组合数 | 72(8×9) | 缺组合告警 |
| APP2 | 加载时延 | < 2 秒 | 前端 loading > 2s 上报监控 |
| APP2 | seq 范围 | 1-12,11/12 置顶 | 非 11/12 按 seq 升序 |
| APP3 | clues 条数 | 1-5 | AI 超 5 截断 |
| APP3 | summary 长度 | ≤ 20 字 | 后端校验拦截 |
| APP3 | detail 长度 | ≤ 120 字 | 后端校验拦截 |
| APP4 | action_suggestions | 1-4 条,每条 ≤ 100 字 | 单测固化 |
| APP4 | one_line_summary | ≤ 30 字,必含关系评级词 | 正则检查 (紧密|一般|疏远) |
| APP5 | tactics 条数 | 2-4 | AI 超 4 截断 |
| APP5 | script 长度 | ≤ 150 字 | 后端校验 |
| APP5 | 黑词 | 禁用 8折/免费/KOL/抽成/内部价 | 后端正则黑名单拦截 |
| APP6 | score 范围 | 1-10(6=标准,<6 扣分,>6 加分) | Pydantic ge=1, le=10 |
| APP6 | clues 条数 | 0-10 | 无下限,上限截断 |
| APP7 | summary 长度 | 200-400 字 | 后端校验 |
| APP7 | summary 开头定性 | ≤ 25 字 + 句号 | 正则 ^.{1,25}[。!] |
| APP7 | strategies 条数 | 2-5 | 前端仅展示 2 条 |
| APP7 | 时间窗标注 | 必含 "近 30 天/近 60 天/近 90 天" | 关键词检查 |
| APP8 | clues 条数 | ≤ 5 | AI 超 5 截断 |
| APP8 | summary 禁数字 | 正则 [0-9%¥] 不匹配 |
后处理剥离 |
| APP8 | providers 拼接 | 中文逗号去重 | 后端 set 处理 |
| 通用 | Token 日预算 | 100,000 | 超限 status=budget_exceeded |
| 通用 | Token 月预算 | 2,000,000 | 超限告警 + 阻断 |
| 通用 | 熔断阈值 | 连续 5 次失败 OPEN | 60 秒恢复窗口 |
| 通用 | APP2-8 限流 | 100 次/小时/门店 | rate_limited 跳过 |
| 通用 | 关系流失预警 | 停消费 > 14 天 | APP4/7 标注 |
| 通用 | 高粘性阈值 | 月内 ≥ 3 次 | APP4 判定 |
| 通用 | 学习型阈值 | 助教费占比 > 40% | APP3/4/7 判定 |
| 通用 | 当期 < 7 天 | 必须降权用词 | APP2 prompt 约束 |
附录 C:Demo 关键路径索引
| 页面 | AI 字段 | 文件 |
|---|---|---|
| 客户详情 | aiInsight.summary/strategies + clues[] |
customer-detail.ts:90-145 |
| 任务详情 | retentionClues[] + talkingPoints[] |
task-detail.ts:67-86 |
| 财务看板 | ai-insight-section 三段式 |
board-finance.wxml:208-221 |
| 助教/客户看板 | aiColorClass(随机配色) |
board-finance.ts:294 |
附录 E:生产端 vs Demo 端对照实况(2026-04-22 快照)
E.1 生产端真实消费路径
| 页面 | AI 字段来源 | 关键实现 | 与 demo 差异 |
|---|---|---|---|
| customer-detail | fetchAICache('app7_customer_analysis', memberId) 单独拉取;retentionClues 从主详情接口返回 |
customer-detail.ts:7 onLoad → loadDetail → _loadAIInsight() 异步加载;aiInsight.strategies 前端按 index 循环 6 色 |
demo 是硬编码 mock;生产 strategies color 由前端计算 |
| task-detail | aiAnalysis.summary / suggestions / talkingPoints + retentionClues 都随主详情接口一次返回,无独立 AI 缓存调用 |
onCopySpeech() → wx.setClipboardData() + 2s toast task-detail.ts:268;备注创建后 4s 间隔轮询 5 次拿 aiScore(20s 超时) |
demo 同样内联 mock;生产端没有长按复制,仅单击复制按钮 |
| board-finance | fetchAICache(cacheType, targetId) + parseMarkdownInline 解析 **粗** / *斜* / ***粗斜*** |
board-finance.ts:532 cache_type 切分已实装;seq 11/12 置顶(精确匹配 + 末两条 fallback);切换 time/area 时 aiInsights=[] 主动清空 |
demo 内联 mock,无 TIME_MAP/AREA 枚举 |
| 浮按钮 | task-detail 当前未挂载;customer-detail 仅传 customerId;board-finance 仅传 bottom=200 |
sourcePage / pageFilters 虽支持但尚未启用(属性存在、传参为空) |
demo 无浮按钮 |
E.2 关键实务差异
- task-detail 无 AI 缓存调用:
aiAnalysis是fetchTaskDetail()主响应的一部分,后端在返回任务详情时已聚合读取 App4/App5 缓存。这意味着任务列表分页与 AI 结果拉取合并为一次请求,减少 round-trip。 - fetchAICache silent fail:services/api.ts:417 错误时返回
null,页面降级显示"暂无洞察数据";此静默失败需监控上报补强。 - 百分比双重格式化:board-finance 把后端小数率(0.052)× 100 传 WXS 格式化,demo 端硬编码字符串,两端一致性依赖后端契约稳定。
- setData 路径优化:
'aiInsight.summary'字符串路径而非整对象解构,减少小程序差量渲染。
附录 F:AI 数据库 schema 权威清单
F.1 五张核心表 DDL 速览
权威 DDL:db/zqyy_app/schemas/biz.sql 与 public.sql。
| 表 | 关键字段 | 索引要点 | RLS |
|---|---|---|---|
biz.ai_run_logs |
status varchar(20)(pending/running/success/failed/timeout/circuit_open/rate_limited/budget_exceeded),alert_status,session_id varchar(100),tokens_used int default 0,latency_ms int,request_prompt text(≤ 8000 字符写入) |
部分索引 idx_ai_run_logs_alert WHERE status IN (failed, timeout, circuit_open);BRIN (created_at) |
未启用 |
biz.ai_cache |
cache_type varchar(30) + CHECK 约束 8 枚举,target_id varchar(100),result_json jsonb NOT NULL,score int(无 CHECK 约束) |
idx_ai_cache_lookup (cache_type, site_id, target_id, created_at DESC) |
未启用 |
biz.ai_trigger_jobs |
event_type varchar(30),is_forced bool,app_chain varchar(100),payload jsonb |
部分索引 idx_ai_trigger_jobs_dedup WHERE status ≠ 'skipped_duplicate' |
未启用 |
biz.ai_conversations / biz.ai_messages |
context_type varchar(20),context_id varchar(50),session_id varchar(100),reference_card jsonb |
部分索引 idx_ai_conv_context WHERE context_type IS NOT NULL |
未启用 |
public.member_retention_clue |
category varchar(20) + CHECK 枚举 6 值,summary varchar(200),detail text,recorded_by_name varchar(50),source varchar(20) DEFAULT 'manual',is_hidden bool |
idx_retention_clue_member、idx_retention_clue_category |
未启用,通过 FDW 反向映射 fdw_app.member_retention_clue |
F.2 DDL vs Pydantic 对齐问题(按风险)
| 级别 | 项目 | 症状 |
|---|---|---|
| CRITICAL | ai_cache.score 无 CHECK 约束 |
Pydantic App6Result.score: int = Field(ge=1, le=10) 仅应用层校验;脏数据(0/-1/100)可静默落库 |
| HIGH | member_retention_clue 没有 emoji 和 providers 列 |
dispatcher.py:513-588 _write_retention_clue 折叠:emoji + " " + summary → summary;providers → recorded_by_name varchar(50)。providers 多源拼接大概率超过 50 字符被 PG 报错或截断 |
| HIGH | summary varchar(200) 拼接 emoji 后有效长度降低 |
AI 输出接近 200 字符时合并报错 |
| MEDIUM | cache_type CHECK 与 CacheTypeEnum 双侧维护 |
新增 App 需同时改 DDL + Pydantic,遗漏即违反 CHECK |
| MEDIUM | ai_run_logs 无 job_id 列关联 ai_trigger_jobs |
只能通过 session_id 间接关联,无 FK 保护 |
| LOW | FDW 外部表 fdw_app.member_retention_clue 缺 source 和 is_hidden 列 |
ETL 端无法按 source 过滤 AI 写入的线索 |
F.3 建议补充的索引/约束
biz.ai_cache.score添加CHECK (score IS NULL OR (score >= 1 AND score <= 10))public.member_retention_clue新建(member_id, site_id, source)索引(APP8 DELETE 现在全扫)biz.ai_conversations新建session_id单列索引- 全部 AI 表启用 RLS(当前完全依赖应用层
site_id过滤,无最后防线)
附录 G:合规审计红黄清单(2026-04-22)
来源:security-reviewer 全量扫描 apps/backend/app/ai/ 所有 prompt 拼装路径。
G.1 已通过的面
- ✅
member_phone / phone / mobile / id_card / wechat_openid / wechat_unionid零出现于所有 prompt 与 data_fetcher - ✅
bank_account / bank_card / home_address / ip_address / device_id全部未出现 - ✅ DashScope SDK 走 HTTPS 加密传输
- ✅ page_context 前端透传被 handler 白名单拦截(handler 只
.get()已知字段)
G.2 5 个风险点(按优先级)
| ID | 文件 | 风险 | 建议修复 |
|---|---|---|---|
| R1 | member_data.py:200 | 助教 da.real_name 作为 nickname fallback 传 AI |
确认是否属员工隐私;若是改为空串或工号 |
| R2 | member_data.py:249-262 | 储值卡精确余额直接进 prompt | 范围化:改为"高/中/低/清零"标签 |
| R3 | member_data.py:276 | card_balance_total / stored_value_balance_total 精确值 |
同 R2 |
| R4 | member_data.py:358-398 | notes.content 备注原文无正则脱敏 |
预过滤手机号 1[3-9]\d{9} → 1****、银行卡号、身份证号 |
| R5 | run_log_service.py:57 | 含 R2/R3/R4 的 prompt 写入 ai_run_logs.request_prompt 落库 |
先做字段级脱敏再入库;或 ai_run_logs 加严格 RLS 仅开发读 |
G.3 合规测试清单(CI)
- 单测:
build_prompt_*返回值断言无 11 位连续数字(手机号) - 单测:
notes.content含手机号的样例,prompt 中已掩码 - CI:
grep -r "member_phone\|wechat_openid\|id_card" apps/backend/app/ai/无命中 - 集成测试:构造含手机号的备注,验证 APP6 prompt 中号码不完整出现
附录 H:MCP Server 与 dispatcher 关系
H.1 现状
| 面向 | dispatcher 路径 | MCP Server 路径 |
|---|---|---|
| 调用者 | Admin Web / 后端事件 | 目前仅 Claude Code 开发环境;百炼 AI 应用规划中(未接入) |
| 连接库 | etl_feiqiu(只读)+ zqyy_app(读写) | 仅 etl_feiqiu 只读 |
| RLS | 每次查询前 SET LOCAL app.current_site_id |
无 site_id 隔离(P5.1 规划中) |
| 鉴权 | JWT | Bearer Token(可选,MCP_TOKEN 为空则无鉴权) |
| 工具 | 8 个 APP handler | 4 个通用工具:list_tables / describe_table / describe_schemas / query_sql(只读+正则黑名单+500 行上限) |
H.2 关键边界
- 8 个 AI 应用目前均走 dispatcher 直查 DB,不经过 MCP
- MCP 扩展(P5.1 批次 B)规划:加 zqyy_app 连接 + auth/biz/public 自动 schema 路由 + 敏感字段脱敏(wx_openid/phone)
- MCP 扩展(P5.1 批次 C)规划:完整查库手册上传百炼知识库
H.3 生产部署
- 外部入口:
https://mcp.langlangzhuoqiu.cn/mcp(未来) - 本地配置:
.mcp.json的pg-etl/pg-app禁用,pg-etl-test/pg-app-test启用
附录 I:admin-web AI 三页面实况
I.1 AIDashboard.tsx(顶部卡片 + 趋势 + 告警)
- 数据源 AIDashboard.tsx:99:
getDashboard()→DashboardResponse - 字段:
today_calls / today_success_rate / today_tokens / today_avg_latency_ms / trend_7d[] / app_distribution[] / budget{} / recent_alerts[] / app_health[] - 时间范围:
range_days(1/3/7/10) 或date_from/date_to自定义 AIDashboard.tsx:112 - WebSocket:
ws(s)://[host]/ws/ai-alerts/{site_id},单次连接 无重连 AIDashboard.tsx:135-168 - 告警消息格式:
{ type: "alert_created", payload: AlertItem } - 实时告警最多保留 20 条,合并至表格顶部
I.2 AIOperations.tsx(四卡片手动执行)
四个 Card 区:
- Card 1 手动重跑:
retryTriggerJob(id)→trigger_job_id - Card 2 缓存失效:
invalidateCache({ site_id, app_type?, member_id? })→affected_count;site_id 必填 - Card 2.5 按需执行:
runApp(appType, params)支持 APP3-APP8 - Card 2.6 触发事件链:
triggerEvent({ event_type, ..., is_forced=true })— event_type ∈{consumption, note_created, task_assigned, dws_completed} - Card 3 批量执行:
createBatchRun()→ 估算 →confirmBatchRun(batch_id)异步启动;batch_id 内存 TTL 600 秒 admin_service.py:26 - Card 4 告警管理:
ackAlert(id)/ignoreAlert(id),分页 10
I.3 TriggerManager.tsx(4 Tab + URL 驱动)
- Tab:
all / biz / ai / etl,?tab=xxxURL 查询参数持久化 - Biz Tab 编辑 Modal:
cron_expression+interval_seconds(min=1),保存后热加载 - AI Tab 组件堆栈:
AITriggers+AIOperations+AITriggerJobs
I.4 严重不一致:app6 命名错位
- 前端
adminAI.ts定义app6_note_analysis - 后端
admin_ai.py的_SUPPORTED_APP_TYPES为app6_note - 手动执行 APP6 会 400,需修正命名统一(推荐采用后端名
app6_note)
I.5 权限模型
- 所有 Admin AI 端点
_require_admin()依赖 admin_ai.py:70-111 - 角色:
{site_admin, tenant_admin, super_admin}任一 admin_users.is_active实时查询(禁用账户秒级生效)
附录 J:后端完整端点与链路矩阵
J.1 路由端点清单(共 21 个 AI 相关)
admin_ai.py(13 个,需 admin 权限)
GET /api/admin/ai/dashboard/trigger-jobs/trigger-jobs/{id}/run-logs/run-logs/{id}/budget/alertsPOST /api/admin/ai/trigger-jobs/{id}/retry/cache/invalidate/batch-run/batch-run/confirm/alerts/{id}/ack/alerts/{id}/ignore
xcx_chat.py(5 个,需 approved 用户)
GET /api/xcx/chat/history/{chat_id}/messages/messages?contextType=&contextId=POST /api/xcx/chat/stream(SSE)/{chat_id}/messages
内部事件总线
POST /api/internal/ai/trigger— internal_ai.py,Internal-Token 认证POST /api/internal/etl-completed— internal_events.py:56,触发 recall → task →fire_event("ai_dws_completed")POST /api/admin/task-engine/pending-review/{id}/reassign— admin_task_engine.py:255,触发fire_event("ai_task_assigned")
J.2 事件 → 链路映射
dispatcher.py:169-174 _EVENT_CHAIN_MAP:
| 事件 | 链路 |
|---|---|
consumption |
APP3 → APP8 → APP7(+ APP4 → APP5 若 has_assistant) |
note_created |
APP6 → APP8 |
task_assigned |
APP4 → APP5 |
dws_completed |
APP2 × 8(area='all') + APP2A × 64(area!='all') |
J.3 服务层职责矩阵
| 服务 | 职责 | dispatcher 调用 | 写表 |
|---|---|---|---|
| ChatService | 对话持久化 | 直调 DashScopeClient(不走 dispatcher) | ai_conversations / ai_messages |
| AdminAIService | 监控聚合 | 无,仅查询 | 无(只读) |
| NoteService | 备注创建 | dispatcher._handle_note() |
notes / ai_cache |
| TaskGenerator | 每日 07:00 生成任务 | trigger_scheduler 异步 |
coach_tasks / ai_cache |
| TriggerScheduler | cron/event 调度 | fire_event() |
trigger_jobs 更新 |
| AIDispatcher | 链路编排+熔断/限流/预算 | _run_step() × N |
ai_cache / ai_run_logs |
J.4 超时与并发
- 单步
_STEP_TIMEOUT = 180s(2026-04-21 从 120s 上调)dispatcher.py:57 - APP2 DWS 全链路 = 180s × 72 组 + 600s 余量 ≈ 2.5 小时(同步执行可能阻塞其他触发器,需监控)
- 普通链 超时 10min
J.5 关键技术债
- 事件去重内存 set:服务重启后当日相同事件可重复触发 → 需迁移到 DB 查询
ai_trigger_jobs - ChatService 不走 dispatcher:链路独立,不享受熔断/限流/预算保护(APP1 限流单独实现)
- 旧 openai_client 残留:DashScope 迁移后未见清理
附录 K:Demo 10 页 AI 区块映射(标杆参考)
K.1 浮按钮挂载清单
已挂载(8 页):board-coach / board-customer / chat-history / notes / performance / task-list / customer-service-records / my-profile / board-finance / customer-detail 未挂载:chat / task-detail / coach-detail / apply / login / reviewing / dev-tools / no-permission
K.2 AI 配色规范
- 6 色方案:
red / orange / yellow / blue / indigo / purple - 实装:ai-color-manager.ts:74-127
- task-list 采用
random模式每日刷新 - 其他页面按页面标识映射固定色
K.3 辅助组件
ai-title-badge:标题旁 AI 徽章ai-inline-icon:列表内嵌 AI 图标clue-card:线索卡片(含tag/emoji/title/source/content字段 + 展开折叠)
K.4 pageFilters 前端字段缺失
扫描发现前端页面未定义 pageFilters 对象结构,当前仅浮按钮属性声明;实际采集需在 P0 G1 gap 补充。
附录 L:ETL → AI 数据源约束
L.1 金额口径铁律
- 禁用
consume_money(member_data.py:1-6 文件头明文禁止) - 统一拆分为:
table_charge_money + assistant_pd_money + assistant_cx_money + goods_money - 所有 prompt 拼装遵守此口径(APP2/3/4/6/7 prompt 已核对)
L.2 FDW 视图与 RLS
- 所有
fdw_etl.*已迁移至app.v_*RLS 视图 - FDW 查询超时硬上限 5 秒 member_data.py:69-79
- 超时降级到
_default_member_data()(AI 输入降级,分析质量下降)
L.3 DWS 视图清单
- 20+ 汇总表,6 大主题(助教业绩/薪酬/财务日报/会员分析/订单/库存)
- 金额字段
NUMERIC(12,2),货币 CNY - 空值策略未文档化:NULL vs 0 策略缺,prompt 数字解读存歧义
L.4 消费记录完整明细风险
- 2026-05-01 起 App3 不再按 4000 字硬截断消费记录,优先保留完整明细,避免丢失大客户高频消费模式
- 已用合成 100 条完整消费明细真实调用 App3,prompt 约 25791 字,64.30s 返回成功,低于 180s 单步超时
- 剩余风险:真实门店极端会员、缓存 reference 膨胀或百炼侧临时抖动仍可能拉高耗时,需通过
ai_run_logs.elapsed_ms持续观察
附录 M:部署与超参
M.1 DashScope APP_ID 清单(根 .env:118-134)
DASHSCOPE_APP_ID_1_CHAT
DASHSCOPE_APP_ID_2_FINANCE (area='all' × 8 时间维度)
DASHSCOPE_APP_ID_2A_FINANCE_AREA (area!='all' × 64 组合,2026-04-23 新增)
DASHSCOPE_APP_ID_3_CLUE
DASHSCOPE_APP_ID_4_ANALYSIS
DASHSCOPE_APP_ID_5_TACTICS
DASHSCOPE_APP_ID_6_NOTE
DASHSCOPE_APP_ID_7_CUSTOMER
DASHSCOPE_APP_ID_8_CONSOLIDATE
M.2 超参硬编码
- 模型版本、温度、max_tokens 在 config.py:14-48 内硬编码
- 无 dev/test/prod 环境差异文件(仅一份 .env)
INTERNAL_API_TOKEN与 DashScope API Key 同存根 .env(已 gitignore)BACKEND_API_URL=http://localhost:8000部署到各环境需手工改
M.3 建议
- 提取
.env.example+.env.dev+.env.prod多环境模板 - 超参挪到
config.py的@dataclass并可被.env覆盖 - 加 APP2 DWS 并发窗口监控(2.5h 耗时风险)
附录 N:历史审计时间轴(2026-02 ~ 2026-04)
N.1 关键演进节点
| 日期 | 主题 | 涉及 APP |
|---|---|---|
| 2026-04-23 | app2a_finance_area_integrated — 72 组合拆为 app2_finance (8) + app2a_finance_area (64) + member_order_count 列 + area_code NULL bug 修复 |
APP2 |
| 2026-04-22 | app2_prompt_v5_1_and_miniprogram_ai_insight — V5.1 采纳(A/B 40 次调用,92.3 分)+ 按星期聚合门槛 14 天 + 月中场景保护 + seq11/12 置顶 | APP2 |
| 2026-04-21 | admin-web-ai-management-suite — AIPrewarm / AITriggers / AIOperations 增强 + 4 个后端端点 | 管理 |
| 2026-04-20 | ai-module-complete — Phase 0-4 贯通 + 删除 8 个死代码 app 文件 + 修复 main.py 未调用 set_dispatcher 导致 503 | 全局 |
| 2026-03-20 | rns1-ai-autonomous-decision-risk-audit — 76 session / 34 风险 / "AI 信任文档胜过信任 DB" 核心问题 | 全局 |
| 2026-03-10 | multi-module-ai-apps-task-defense — 8 个 AI 应用骨架 + 任务防卡死 + 小程序页面迁移 | 全局 |
| 2026-02-26 | retention-clue-refactor — 新表 ai_trigger_jobs 去重 + member_retention_clue 字段调整 |
APP8 |
N.2 从历史踩坑抽取的验证规则
| 历史踩坑 | 应有验证 |
|---|---|
| FDW Schema 脱节(列名/视图名虚构) | 生成/改 prompt 前必须 MCP 执行 information_schema.columns + SELECT * LIMIT 5 验证 |
| 月中场景误读(3 月初把月中 22 天当整月对比) | payload 含"对比口径"字段(当期/对比期天数);按星期门槛 14 天;样本<5 天降权表述 |
| Prompt 版本漂移 | 版本号固定写入 docs/ai/;frontend enum / backend schema 同步校验 |
| app_type 枚举错配 | 新增 APP 时同时更新 CacheTypeEnum / DDL CHECK / _SUPPORTED_APP_TYPES / adminAI.ts |
| DWS area_code=NULL bug | ETL 完成后必须验证 all_sum ≈ 各区域 sum |
| DDL 列 DEFAULT 0 未回填 | 上线前预约回填脚本窗口 |
| AI 自主执行架构变更 | 连接模式/DDL/FDW 映射变更需向用户展示 SQL 等待确认 |
| Task 状态欺骗(20/22 失败仍 completed) | 带验证步骤的 task 测试通过率 < 90% 禁止 complete |
N.3 5 项遗留技术债(需补审计)
- APP8 幂等 DELETE+INSERT 改造缺独立审计
tokens_used=0提取 bug(usage.models嵌套)未根治request_prompt8000 字符门槛未正式审计ai_cache.scoreTTL 策略未定ai_trigger_jobs去重从内存 set 迁移到 DB 的当前状态未审计
附录 O:最终 Gap 汇总(v4 版,共 25 项)
在 v3 的 15 项基础上 + 新增 10 项(来自全景调研)。
O.1 v3 遗留 15 项(摘要)
见附录 B(G1-G15)。
O.2 v4 新增 10 项
| ID | Gap | 来源 | 建议 |
|---|---|---|---|
| G16 | 前后端 app6_note_analysis vs app6_note 命名错位 |
admin-web 调研 | 统一为后端名 app6_note,同时改 adminAI.ts 枚举 |
| G17 | WebSocket /ws/ai-alerts/{site_id} 无重连 |
AIDashboard.tsx | 指数退避重连(2/4/8s,最多 10 次) |
| G18 | member_retention_clue 无 emoji/providers 列,折叠到 summary/recorded_by_name |
DB schema | 要么加列,要么明确 providers 长度上限并拦截超 50 字符 |
| G19 | ai_cache.score 无 CHECK 约束 |
DB schema | 加 CHECK (score IS NULL OR score BETWEEN 1 AND 10) |
| G20 | 全部 AI 表 未启用 RLS | DB schema | 至少 biz.ai_cache / biz.ai_trigger_jobs / public.member_retention_clue 启用 RLS + USING (site_id = current_setting('app.current_site_id')::bigint) |
| G21 | 储值余额精确值 / 助教 real_name / 备注原文进 prompt 落 ai_run_logs |
合规审计 | R2-R5 脱敏;ai_run_logs 加严格 RLS |
| G22 | 事件去重内存 set 重启丢失 | 后端路由调研 | 去重改查 ai_trigger_jobs 当日记录 |
| G23 | APP2 DWS 预热 2.5 小时同步执行 | 后端路由调研 | 加并发控制窗口;监控 > 2h 告警 |
| G24 | task-detail 无 AI 浮按钮挂载 | 生产端调研 | 若需支持"从任务详情进入对话",需挂载浮按钮并传 sourcePage=task-detail + contextId=task_id |
| G25 | MCP Server 无 site_id RLS 隔离;未接入百炼 AI |
MCP 调研 | P5.1 批次 B 落地前不开放 MCP 给百炼;开放时强制 MCP_TOKEN + 敏感字段脱敏 |
附录 P:全景完成度自评
| 面向 | 盘查状态 |
|---|---|
| 代码静态(后端 / 前端生产 / 前端 demo / admin-web) | ✅ 完成 |
| PRD 文档(AI 需求 2 / ai-app-prompts / P5 / P14 / P15 / NS2 / NS3) | ✅ 完成 |
| DB schema DDL + Pydantic 对齐 + RLS 状态 | ✅ 完成 |
| 合规敏感字段(PII / 金融 / 位置 / 备注原文) | ✅ 完成 |
| MCP Server(现状 + 规划 + 与 dispatcher 边界) | ✅ 完成 |
| 后端路由 21 个端点清单 + 服务层职责 + 超时并发 | ✅ 完成 |
| 历史审计近 3 月时间轴 + 踩坑规则 + 技术债 | ✅ 完成 |
| demo 10 页 AI 区块映射 + 配色规范 + 浮按钮分布 | ✅ 完成 |
| 共享包枚举对齐(未同步)/ ETL DWS 字段 / 口径铁律 | ✅ 完成 |
| 部署超参 + 环境差异 + 压测降级灰度 | ✅ 完成(含缺口标注) |
结论:本文档 v4 已作为 NeoZQYY 项目 AI 模块的单一真相来源(SSOT),覆盖代码 / PRD / DB / 合规 / MCP / 路由 / 审计 / demo / ETL / 部署 10 个面向,25 项 Gap 跟踪,75+ 验证点;后续变更需同步更新本文档。