P2 Foundry
块式报表与多渠道分发
把"经营看板"变成"主动推送的经营简报":chart/text/table 块编排成报表定义,立即或按 cron 定时生成渲染快照,再经 email / internal 渠道分发;块引用 metric 时自动打点报表血缘。看完这 4 个故事,你就能自己搭一张定时送报的报表。
业务分析师
数据工程师
管理层晨报
定时分发
站内通知
血缘
共 4 个故事
能 / 不能速览
✅ 这个主题能做
- chart / text / table 三类块编排:chart 绑图表、text 写正文、table 三选一取数(ref_chart_id / OOL 对象查询 / metric_name 指标)
- 立即生成快照(POST /reports/:id/run),或按 cron 定时生成 + 分发(调度任务 report:<rid>)
- 多渠道分发:email 走 SMTP、internal 落 fr_notifications 站内通知 + 审计,feishu 未配时诚实降级标注
- 块引用 metric 自动打点血缘:RecordMetricToReport 生成 metric → report 血缘边
- 历史快照预览:markdown / HTML / 各块取数结果 / 分发结果可回溯
⛔ 这个主题做不了
- feishu 渠道当前恒降级标注(foundry 无飞书引擎),要飞书需接线 aip 侧回调
- 单块取数失败不中断整体,该块以 note 标注,快照照常生成
- 无模板引擎(无 Jinja 类模板/自定义渲染器),blocks 为固定结构
- 渠道失败仅标注,不自动重发;定时与手动触发的 run 不可区分
- 报表无定义级版本,快照是唯一"版本"证据
适用角色
本主题面向四个角色:
- 业务分析师:搭报表定义(块编排 + 渠道 + cron),是核心使用者。
- 数据工程师:配置 OOL 对象查询块、核对指标口径、排障取数失败。
- 管理者:作为收件人接收晨报/周报,消费快照而不直接操作。
- 合规审计:通过 internal 渠道的 fr_notifications 与 report_dispatch 审计回放"谁何时收到了哪版报表"。
能力速览(能做什么)
三类块编排
chart 块绑 dashboard 图表取数;text 块直取 markdown 正文;table 块三选一:图表数据 / OOL 对象查询 / 指标查询。
立即 / 定时生成
POST /reports/:id/run 立即生成快照;带 schedule 的报表注册到统一调度器(report:<rid>),cron 触发"生成 + 分发"。
多渠道分发
email 走 SMTP、internal 落站内通知表 + 审计,feishu 未注入时降级标注(degraded)而非假装送达。
指标血缘打点
块引用 metric_name 时生成快照自动调 RecordMetricToReport,数据血缘图出现 metric → report 节点,口径可反查。
快照历史预览
fr_report_runs 保存每次生成的 markdown / HTML / 各块取数结果 / 分发结果,历史可回溯、可复盘。
调整指南(怎么调整)
- 改取数源:table 块取数优先级 ref_chart_id → object_query(OOL)→ metric_name,三选一,改块字段即改取数。
- 改分发渠道:channels 填 email / internal / feishu,recipients 配邮箱或 user_id;email 未配置时渠道降级标注,配置 SMTP 后下次生成自动 delivered。
- 改调度:schedule 填 5 段/6 段 cron(如 0 9 1 * *);更新报表后调度自动重注册,配置变更即时生效;enabled=false 停用定时但手动 run 不受限。
- 改血缘:块引用 metric_name 才有血缘打点;不引指标的块不产生 metric → report 边。
- 改容错预期:单块失败只在该块 note 标注,不阻断其他块;先预览快照再决定是否分发。
做得好的场景
报表引擎把"人去查看板"变成"报表主动找人",特别适合以下场景:
- 管理层晨报:每月 1 号 09:00 自动把上月 GMV / 订单量 / AOV 发到管理层邮箱,替代手工导 Excel 拼 PPT。
- 定时站内通知:internal 渠道落 fr_notifications + 审计,合规场景"谁何时收到哪版报表"全程可回放。
- 指标口径反查:报表里某个数字有疑问,沿 metric → report 血缘边反查指标定义与上游,判断口径可信度。
- 跨数据源拼装:table 块用 OOL 对象查询按对象所属数据源取数,一张报表可拼装不同数据源的数据。
限制与不足
以下是明确的边界,使用前先知道:
- feishu 恒降级:foundry 无飞书引擎,FeishuSender 未注入,feishu 渠道运行时 degraded 标注;需 aip 侧回调接线后解除。
- 单块失败不阻断:该块 note="取数失败: ..."写入快照,其余块照常,快照照常落库。
- 无模板引擎:blocks 为固定结构,无参数化 / 条件渲染 / 自定义渲染器。
- 无渠道重试:失败渠道仅标注,不自动重发;无投递回执。
- run 无触发来源:定时 / 手动不可区分;报表无定义级版本,快照是唯一版本证据。
- 权限不分权:owner 仅记录语义,任意登录用户可读可写全部报表;取数数据权限取决于报表 owner 的 RLS/CLS。
场景故事
故事 1
管理层晨报:块编排 + cron 定时生成 + 邮件分发
场景:定时分发
角色:业务分析师
耗时:约 15 分钟
- 背景
- 王姐要搭"月度经营报表":每月 1 号 09:00 自动把上个月的 GMV / 订单量 / AOV 发到管理层邮箱。她编排了四个块:text 块写月报导语、chart 块绑定已有的"区域营收"图表、table 块引 order_total 指标、table 块用 OOL 查月度汇总,配 schedule=0 9 1 * *、channels=email。
- 传统做法对比
- 以前每月手工导 Excel、拼 PPT,再群发邮件,动辄小半天,口径还容易各导各的。现在口径钉死在块定义上,调度器到点自动生成快照并群发,快照留档可回溯。
- 角色
- 业务分析师(建报表 + 配调度与渠道);管理层作为收件人消费邮件快照。
- 操作步骤
-
- 侧边栏"智能报表"新建报表"月度经营报表"
- 添加 text 块写"本月销售概览"导语
- 添加 chart 块,ref_chart_id 绑定"区域营收"图表(id=7)
- 添加 table 块,metric_name 填 order_total
- 添加 table 块,object_query 配 sum(amount) 按 month 分组
- schedule 填 0 9 1 * *,channels 填 email,recipients 填管理层邮箱
- 先点"立即生成"预览快照,再点"生成并分发"验证
- 系统响应
- 创建返回
{"code":0,"data":{"id":"<rep_id>","rid":"<rid>","name":"月度经营报表","blocks":[{...}],"schedule":"0 9 1 * *","channels":["email"],"enabled":true,"owner":"u1"}};快照预览返回:{"code":0,"data":{"snapshot":{
"markdown": "## 区域营收\n| region | revenue |\n| --- | --- |\n| 华东 | 100 |",
"blocks": [
{"title": "摘要", "type": "text", "content": "本月销售概览"},
{"title": "区域营收", "type": "chart", "columns": ["region", "revenue"],
"rows": [["华东", 100]], "note": "图表 区域营收(bar)"}]}}}
- 结果洞察
- 报表定义落库后自动注册调度任务 report:<rid>,到点触发"生成快照 + 分发"。email 已配置 SMTP 时该渠道 delivered,运行记录 status=success;快照以 markdown + HTML 两种形态留档,管理层收到的邮件正文就是 HTML 渲染的经营简报。手工小半天的工作变成一次配置。
- 调整建议
- 先"立即生成"验证快照内容,再放心等定时触发;email 未配置时渠道会 degraded 标注(运行 status=partial),配好 SMTP 后下次生成自动 delivered 无需改报表;调度回调每次重读报表最新配置,改块/改渠道即时生效。
- 动手试一试
- 登录:admin / admin1。页面路径:Foundry 侧边栏"智能报表"。输入内容:新建"月度经营报表",text 块写导语、chart 块绑"区域营收"图表、table 块引 order_total 指标,schedule=0 9 1 * *、channels=email、recipients=管理层邮箱。预期结果:立即生成返回快照;调度任务 report:<rid> 出现在调度列表;生成并分发返回 dispatch 数组。
- 限制提示
- chart 块取数数据权限取决于报表 owner 的 RLS/CLS,owner 看不到的行/列快照里不会有;run 无触发来源字段,定时与手动不可区分;快照体积随块数/行数累积,暂无自动清理策略。
故事 2
合规场景:internal 站内通知 + report_dispatch 审计留痕
场景:站内通知
角色:合规审计
耗时:约 10 分钟
- 背景
- 合规部要求"月结数字以哪版为准"可追溯。王姐把报表渠道改成 internal,收件人填财务三个 user_id:每次生成快照,fr_notifications 落站内通知,审计写 report_dispatch(含 channel/recipients/run_id)哈希链,谁在什么时间收到了哪版报表,全程可审计回放。
- 传统做法对比
- 以前数字对外全靠邮件谁收到、哪版为准靠自觉,出问题互相扯皮。现在 internal 渠道每次分发都落站内通知表 + 审计日志,收件人、时间、run_id 三要素齐备。
- 角色
- 合规审计(核查审计与通知记录);业务分析师(配渠道与收件人)。
- 操作步骤
-
- 打开报表"月度经营报表",channels 改为 internal
- recipients 填财务三个 user_id
- 点"生成并分发"(POST /reports/:id/run,body {"dispatch":true})
- 打开运行历史,查看快照分发结果
- 到审计日志查 report_dispatch 记录
- 系统响应
- 生成并分发返回:
{"code":0,"data":{
"run": {"id": "run-uuid", "report_id": "rep-uuid", "status": "success",
"snapshot": "{...}", "created_at": "...", "finished_at": "..."},
"dispatch": [
{"channel": "internal", "status": "delivered",
"note": "站内通知已落表(3 收件人)"}]}}
审计落一条 report_dispatch(eventType、stepType=DISPATCH、refType=report、actionDetails 含 channel/recipients/run_id)。
- 结果洞察
- internal 渠道交付的不仅是"送达",还有"留痕":fr_notifications 落站内通知(标题/正文 markdown/收件人 JSON/channel),审计哈希链记录分发动作。合规回溯只需按 run_id 查站内通知与审计两条线即可还原"哪版报表、发给谁、何时发"。
- 调整建议
- internal 收件人为空时自动回退报表 owner,不会静默丢失;审计失败仅记日志不阻断分发;如需邮件也留痕,可同时配 email + internal 双渠道,email 走 SMTP、internal 落审计。
- 动手试一试
- 登录:admin / admin1。页面路径:智能报表 → 打开报表 → 改渠道。输入内容:channels=internal、recipients=u1,u2,u3,点"生成并分发"。预期结果:dispatch 返回 internal delivered("站内通知已落表(3 收件人)"),审计新增 report_dispatch 一条。
- 限制提示
- 收件人为空时 internal 回退报表 owner,需注意预期;审计失败仅记日志不阻断;feishu 渠道在 foundry 侧恒 degraded,合规场景暂时只有 email / internal 可用。
故事 3
指标血缘反查:报表里的"订单总额"口径从哪来
场景:血缘反查
角色:业务分析师
耗时:约 8 分钟
- 背景
- 王姐在报表里看到"订单总额"数字,想追溯口径。报表 table 块引了 order_total 指标——每次生成快照,RecordMetricToReport 自动打点 metric → report 血缘边,数据血缘图里就能看到报表节点,沿边反查该指标的定义、公式与上游。
- 传统做法对比
- 以前看到报表数字只能问"这个数哪来的",答不上来就再对一遍。现在块引用 metric 自动记血缘,指标 → 报表节点一条边,口径定义与上游一步到位,判断数字可信度有依据。
- 角色
- 业务分析师(反查口径)+ 数据工程师(维护指标定义与上游)。
- 操作步骤
-
- 打开报表定义,确认哪个块引用了 metric_name(如 order_total)
- 点"立即生成"让快照生成时打点血缘
- 到"数据血缘"页面查看报表节点
- 沿 metric → report 边反查 order_total 指标的定义与上游字段
- 系统响应
- 生成快照返回
{"code":0,"data":{"run":{"id":"<run_id>","status":"success","snapshot":{...}}}};血缘侧 RecordMetricToReport(ctx, "order_total", "月度经营报表") 打点 metric → report 边,血缘图报表节点挂到指标节点之下。血缘器未注入时打点跳过、不 panic。
- 结果洞察
- 血缘打点是 best-effort:块引用 metric 就自动记,失败仅记日志不阻断快照生成。报表节点成为血缘图的一部分后,沿 metric → report 边可以反查指标定义 / 公式 / 上游表,"报表数字可不可信"从拍脑袋变成看血缘。
- 调整建议
- 关键数字的报表块务必引 metric_name,否则不产生血缘边;dashboard 图表绑定指标的写路径同样打点(B3-5.1 补齐),图表节点也作报表节点参与血缘;血缘完整性以 lineage 服务状态为准,打点失败仅记日志。
- 动手试一试
- 登录:admin / admin1。页面路径:智能报表 → 报表定义 → 数据血缘。输入内容:table 块 metric_name 填 order_total,点"立即生成"。预期结果:运行历史 +1,血缘图出现 metric → report 边(每块引用一次打点一次)。
- 限制提示
- 只有引用 metric_name 的块才打点血缘;血缘器未注入时打点静默跳过;当前 Nexus 未含 reports scope,报表 RID 是目录类资源候选但未接入统一搜索。
故事 4
生成并分发撞上渠道降级与单块失败:诚实标注不假装送达
场景:容错降级
角色:数据工程师
耗时:约 10 分钟
- 背景
- 张工把报表渠道配成 email + feishu 双渠道做验证。foundry 侧 email 已配 SMTP、feishu 无飞书引擎,同时其中一个 chart 块引用的图表 id 写错取数失败。他要确认:单块失败会不会把整张报表拖垮?feishu 没配会不会假装送达?
- 传统做法对比
- 以前的定时发报脚本往往"整体成功/整体失败",一个块挂了整封邮件不发,或渠道没配却照样写"已发送"。现在每块每渠道独立判定:单块失败入 note、feishu 未配标 degraded,绝不假装送达。
- 角色
- 数据工程师(排障取数失败与渠道降级)。
- 操作步骤
-
- 打开报表,把 channels 配为 email + feishu,recipients 配邮箱 + user_id
- 故意把一个 chart 块的 ref_chart_id 指向不存在的图表 id
- 点"生成并分发"(dispatch=true)
- 查看返回的 dispatch 数组与运行状态
- 系统响应
- 生成并分发返回:
{"code":0,"data":{
"run": {"id": "run-uuid", "report_id": "rep-uuid", "status": "partial",
"snapshot": "{...}",
"error": "feishu: feishu 未配置(foundry 无飞书引擎,本版降级标注...)"},
"dispatch": [
{"channel": "email", "status": "delivered", "note": "邮件已发送(1 收件人)"},
{"channel": "feishu", "status": "degraded",
"note": "feishu 未配置(foundry 无飞书引擎,本版降级标注...)"}]}}
取数失败的 chart 块在快照 blocks 里以 note="取数失败: ..." 标注,快照照常生成。
- 结果洞察
- 运行状态 partial 的判定逻辑:任一渠道非 delivered(degraded/failed/skipped)即置 partial,error 聚合各渠道摘要;单块失败只在该块 note 标注,不阻断其他块。这套"诚实标注"让渠道真实状态、块真实状态一目了然——email 到了就是到了,feishu 没配就是没配。
- 调整建议
- feishu 在 foundry 侧恒 degraded,要真发飞书需 aip 侧回调接线注入 FeishuSender;chart 块取数失败先核对 ref_chart_id 与图表绑定对象权限(取数身份是报表 owner);单块失败排查后可先修块再单独"立即生成",不影响其他块。
- 动手试一试
- 登录:admin / admin1。页面路径:智能报表 → 报表编辑。输入内容:channels=email+feishu,chart 块 ref_chart_id 填一个不存在的 id,点"生成并分发"。预期结果:run.status=partial,dispatch 中 email=delivered、feishu=degraded,快照 blocks 中该块 note 标注取数失败。
- 限制提示
- email 未配置 SMTP 时该渠道 degraded(不是 failed,不会 500);收件人为空时渠道 skipped;失败渠道不自动重发;调度定时回调同步执行生成+分发,长报表会占用调度 goroutine(有 SkipIfStillRunning 防重入)。
常见问题
email 渠道为什么显示 degraded?
foundry 未配置 platform/mailer(SMTP)时,email 渠道降级标注(degraded),运行状态 partial。配置好 SMTP 后下次生成自动 delivered,无需改报表。
feishu 渠道能用吗?
当前 foundry 无飞书引擎,FeishuSender 未注入,feishu 渠道运行时恒 degraded(诚实标注,不假装送达)。需接线 aip 侧飞书回调才能解除降级。
table 块的取数优先级是什么?
ref_chart_id → object_query(OOL)→ metric_name,三选一;同时提供多个时按优先级取第一个命中。全无时创建报表会被 422 拒绝。
生成快照时某个块失败会怎样?
该块 BlockResult.note="取数失败: ..." 写入快照,其余块照常取数,快照照常落库(前端可预览部分结果)。不会因为一块失败导致整张报表不生成。
定时生成与手动生成有差别吗?
逻辑相同(GenerateSnapshot + Dispatch);调度回调每次重读报表最新配置,手动 run 也走同一路径。当前 run 记录无触发来源字段,定时/手动不可区分。
删掉报表会怎样?
级联删除全部运行记录与站内通知,并反注册调度任务 report:<rid>;调度历史(scheduler_runs)保留可查。
主题小结
一句话:报表引擎把"看板"变成"主动推送的简报"——chart/text/table 块编排定义,立即或 cron 定时生成快照(调度任务 report:<rid>),email / internal / feishu 多渠道分发且诚实标注降级,块引用 metric 自动记血缘。记住几个边界:feishu 恒降级、单块失败不阻断、无模板引擎、无渠道重试。