AI 周报自动化:同一批资料重跑,也知道该不该再发

最后更新:

先固定统计时间和来源快照,再生成待审报告。重复执行、资料修订和实际发送各有自己的记录。

先定义周报统计什么,别从定时器开始

周报可能统计本周新增任务、本周发生的更新,也可能展示截至周末的全部未完成事项。这三种口径不同,不能用同一个过滤条件替代。本页教学样例统计“本周有更新的任务及其窗口内最后状态”,不包括周内没有更新的存量任务,也不把它叫团队全部任务完成率。

固定窗口为上海时间 2026-09-07 00:00 至 2026-09-14 00:00,左闭右开。这样刚好落在 14 日零点的记录进入下一周,不会两周都算。每份报告保存窗口、时区、来源快照、规则版本和模板版本;如果只存“第 37 周”,很难解释跨时区和补跑差异。

四条虚构输入:重复投递与边界事件分开处理

下表是人工构造的任务事件,不是客户使用数据。e1 被投递两次,应只处理一次;e2 表示 T2 受阻;e3 在窗口右边界,属于下一周。当前样例最终应包含两个更新任务:T1 完成、T2 阻塞。这里没有实际发送邮件、消息或运行外部自动化项目。

真实来源可能晚到或发生修订。业务发生时间决定归属周,采集时间用于追踪延迟,不能混用。若一条历史事件修正了内容,优先保留明确版本或新的修订事件,并形成新的来源快照;不要用覆盖旧文件的方式让历史报告不可解释。

周报窗口、重复事件、来源快照和审阅状态的教学流程图
虚构周期任务教学示意,不是定时系统运行或邮件已发送截图。
事件任务发生时间 +08:00状态处理结果
e1T12026-09-08 09:00done纳入本周
e1 再次投递T12026-09-08 09:00done同事件去重
e2T22026-09-12 10:00blocked纳入本周
e3T32026-09-14 00:00done进入下一周

用三种标识区分去重、报告版本与发送

事件标识解决同一条输入被重复投递;报告标识由窗口、规范化事实、规则版本和模板版本决定,解决同一资料重跑;发送标识则关联已批准报告版本、接收范围和目标系统,解决重复通知。这三者不能共用一个文件名,否则新资料与重试容易混淆。

对同一个事件 ID 出现不同内容,应报冲突,不静默保留任意一份。对完全相同的事实快照重复生成,应返回已有待审稿;资料变化则产生新版本并重新审阅,旧版仍保留。报告已经批准后若正文变化,批准不能自动转移到新版本。

固定周窗口与来源快照 → 已去重事实 + 计算结果 + 未决事项 → 进展摘要 / 阻塞原因整理 / 下周关注草稿
教学流程示意。模型调用成功不代表报告已批准,也不代表消息已经送达。

本地复跑示例:第一次建草稿,第二次复用相同版本

以下 JavaScript 使用 Node.js 自带 crypto,不调用模型或外部服务。先按窗口过滤,再按事件 ID 去重,最后取每个任务在窗口内的最后状态。相同时间却给出不同状态时停止,避免伪造顺序。规范化后的结果生成报告键,连续调用两次应得到相同 key,created 依次为 true、false,状态均为 pending_review。

这里的 Map 只在单个进程内存在,演示的是判断逻辑;真实定时运行必须使用持久化记录及唯一约束,并测试重启和并发。不能把这段代码直接称为可靠的跨进程发送去重系统。

weekly-repeat.mjs
import { createHash } from "node:crypto";
const start = "2026-09-07T00:00:00+08:00";
const end = "2026-09-14T00:00:00+08:00";
const e1 = { id: "e1", task: "T1", at: "2026-09-08T09:00:00+08:00", state: "done" };
const input = [e1, { ...e1 },
  { id: "e2", task: "T2", at: "2026-09-12T10:00:00+08:00", state: "blocked" },
  { id: "e3", task: "T3", at: "2026-09-14T00:00:00+08:00", state: "done" }];
const reports = new Map();
function run() {
  const unique = new Map();
  for (const e of input) {
    const time = Date.parse(e.at);
    if (!Number.isFinite(time)) throw Error("INVALID_TIME");
    if (time < Date.parse(start) || time >= Date.parse(end)) continue;
    if (unique.has(e.id) && JSON.stringify(unique.get(e.id)) !== JSON.stringify(e)) throw Error("EVENT_CONFLICT");
    unique.set(e.id, e);
  }
  const latest = new Map();
  for (const e of unique.values()) {
    const prior = latest.get(e.task);
    if (prior && Date.parse(e.at) === Date.parse(prior.at) && e.state !== prior.state) throw Error("STATE_CONFLICT");
    if (!prior || Date.parse(e.at) > Date.parse(prior.at)) latest.set(e.task, e);
  }
  const facts = [...latest.values()].map(e => ({ task: e.task, state: e.state }))
    .sort((a, b) => a.task < b.task ? -1 : a.task > b.task ? 1 : 0);
  const snapshot = { start, end, rule: "v1", template: "v1", facts };
  const key = createHash("sha256").update(JSON.stringify(snapshot)).digest("hex");
  const created = !reports.has(key);
  if (created) reports.set(key, { state: "pending_review", tasks: facts.length,
    done: facts.filter(e => e.state === "done").length, blocked: facts.filter(e => e.state === "blocked").length });
  return { key, created, ...reports.get(key) };
}
console.log(JSON.stringify([run(), run()]));

把已计算事实交给模型,只请它组织叙述

模型收到的是固定窗口、两个更新任务、一个完成、一个阻塞,以及各项来源。让它写摘要与需要关注的问题,不让它重新计算百分比或猜阻塞原因。本例没有提供 T2 为什么受阻,因此应写“原因待补充”,不能编造成资源不足或负责人拖延。

报告可分为本周变化、阻塞与待确认、下周已确认安排三个区块。没有已批准下周计划时,第三块说明待确认,不生成假承诺。把上周报告作为表达参考时,注明它不是本周新增事实,避免旧进展被模型重新算作本周成果。

周报草拟要求.txt
根据 window、facts、counts 和 source_refs 生成待审周报。
只叙述窗口内已经提供的变化,数字原样引用 counts,不另算完成率。
每条进展和阻塞附任务 ID 与来源标识。
缺少阻塞原因、负责人或计划时标注待确认。
区分本周变化与仍未完成的历史事项,不混用口径。
不把建议写成已批准的下周承诺。
输出 summary、progress、blocked、open_questions;不发送消息。

定时节点负责唤醒,持久记录负责恢复

选 n8n 等工具时,先在手动触发下检查一轮完整结果,再配置周期。n8n 官方说明 Schedule Trigger 使用工作流时区,否则使用实例时区;定时工作流需要保存并发布。查阅日期:2026-09-12。Schedule Trigger 文档。不要靠服务器默认时区解释“周一上午”。

n8n 的 Remove Duplicates 区分当前输入内去重与跨执行去重,历史还有作用范围和容量设置。查阅日期:2026-09-12。Remove Duplicates 文档。这些功能说明不等于本页已部署一个 n8n 周报项目;去重记录丢失、历史清理或任务中断后的恢复,都需在实际环境另测。

发送前批准;发送结果未知时先查证

把报告状态依次设为待审、已批准、发送中、已发送或发送结果待确认。批准绑定具体版本和接收人范围。请求超时不证明消息没送出,直接重发可能造成重复通知;保存发送请求标识,先查询目标系统记录或人工确认,再决定补发。只有实际回执支持时才标记已发送。

补跑时先判断是哪一阶段失败:数据读取失败只补读,摘要失败复用已计算事实,发送失败不必重新生成正文。晚到资料进入同一周时生成修订版,说明更新了什么,并按团队规则决定是否再通知,而不是悄悄覆盖已发送版本。读取资料、写草稿和发送通知分别配置权限。

用一次故障演练判断自动化是否值得扩大

上线前至少检查相同输入重跑、重复事件、窗口边界、资料修订、进程重启和发送超时。样例本地代码只覆盖前面几项确定性判断;持久化、并发、真实发送与恢复仍是待执行验收。记录每次运行的输入身份、耗时、异常、审阅人和最终状态,才能追踪“这周为什么没有报告”。

可交付成果是一份带来源、版本、审阅状态的周报及运行记录。衡量收益时减去复核、维护和补跑时间,没有实际计时就不承诺无人值守或节省比例。模型成本统一看实时价格,先让一个固定任务稳定重复,再增加来源和收件范围。

常见问题

重复运行时为什么不直接覆盖原周报?

覆盖会丢失审批与发送对应的版本。相同快照复用已有稿;资料或规则变化则生成新版本,再决定是否需要重新审阅和通知。

代码里的 Map 能保证重启后不重复吗?

不能。它只演示单进程判断。实际任务要持久化报告与发送记录,增加唯一约束,并做重启和并发验收。

周一零点的记录算上周还是本周?

本例用左闭右开窗口,2026-09-14 00:00 的记录进入下一周。具体时间与时区应写进报告,不凭默认设置判断。

发送请求超时可以马上重试吗?

先核对目标系统是否已收到,并保留请求标识。超时属于结果未知,盲目重发可能造成重复通知。

这里已经自动运行和发送过周报吗?

没有。内容是虚构输入与本地确定性复跑示例;n8n 部署、跨运行持久化、真实消息发送和故障恢复都需另行验证。