← 全部示例
认清你的未知 · 实施前

可微调的方案

不要按执行顺序排列方案,而是按最可能修改每部分的概率排序——值得关注的决策优先浮现,机械性工作沉到底部。

提示词
以 HTML 形式写一份批注导出的实施方案,但把我最可能微调的决定放在前面:数据模型变更、新类型接口,以及所有面向用户的内容。把机械性重构埋在底部——那部分我信任你。
Claude 的输出

实施方案:批注导出

Acme · 将审阅的批注导出为可分享的 PDF 或 CSV · 分支 feat/annotation-export

工作量约 2.5 人天
涉及文件13(3 个新建)
风险低–中
迁移1 个,增量
↑ 微调 ↓ 信任
本方案按微调概率排序,而非执行顺序。自上而下阅读 = 最值得关注的在前。A 节包含所有判断点;B 节是构建顺序;C 节是机械性工作,可完全跳过。
A

你可能想修改的决定

高微调概率

三处我做了你可能不同意的判断。每个标记的选择都展示了我考虑的替代方案——切换以对比。

A1 · 数据模型 — 新建 annotation_exports

annotation_exports 新建
iduuid pk
review_iduuid → reviews
requested_byuuid → users
statusenum(queued, rendering, ready, failed)
optionsjsonb
snapshot ①jsonb
① 无 snapshot 列——渲染器在渲染时实时 JOIN annotations
artifact_url ②text null
rendered_attimestamptz null
② 无存储产物——GET /exports/:id/download 按需渲染
created_attimestamptz
选择 ① 快照 vs. 实时 JOIN
方案选择 — 反规范化快照
在请求导出时将批注载荷复制到 snapshot jsonb。导出是审阅在那一刻的记录:后续编辑、解决和删除不会改写历史。
  • 即使审阅已归档,导出仍然有效。
  • 渲染器只读一行——无需跨 annotations + annotation_replies 的 N+1 查询。

代价:繁重审阅约 40 KB/行;快照可能相对于实时线程变得陈旧。

替代方案 — 渲染时实时 JOIN
跳过快照;渲染器在生成文件时 JOIN annotations。导出始终反映当前状态。
  • 无重复数据,无陈旧性问题。
  • 重新下载同一导出可能产生不同的文件——对审计用途令人意外。

选这个如果:导出是工作文档而非记录。一行告诉我:「使用实时 JOIN。」

选择 ② 存储产物 vs. 按需渲染
方案选择 — 渲染一次,存入 blob 存储
worker 渲染 PDF/CSV 一次并写入 artifact_url。下载是签名 URL 重定向——廉价、可缓存,可与没有 Acme 账户的审阅者分享。

代价:需管理 blob 生命周期;我会添加 30 天 TTL 清理(C 节第 6 项)。

替代方案 — 每次下载时渲染
无存储文件:GET /exports/:id/download 流式输出全新渲染。零存储,但大型审阅每次下载约 2–4 秒,且无可分享的公开链接。

选这个如果:存储/合规规则使存储副本不便。

A2 · 新类型接口

// src/types/exports.ts (new)
export interface ExportRequest {
reviewId: ReviewId;
format: 'pdf' | 'csv';1
range: 'all' | 'unresolved' | TimecodeRange;2
includeReplies: boolean; // default true
includeDrawings: boolean; // default false3
}
 
export interface AnnotationSnapshot {
id: AnnotationId;
timecode: string; // "00:04:12.08"
author: { id: UserId; name: string };
body: string;
state: 'open' | 'resolved';4
replies: ReplySnapshot[];
}
1从 PDF + CSV 开始。之后添加 'xlsx''srt' 只需一个联合成员 + 一个渲染器。
2TimecodeRange 让制作人只导出某个场景的评论。若无人需要,砍掉它可省约半天。
3绘图(帧标注)栅格化进 PDF。默认关闭,因为会使渲染时间增至三倍。
4在导出中有意将内部 5 状态机简化为 2 状态。若审阅者需要看到 'wont_fix' 请告诉我。

A3 · 用户体验流程

步骤 1
导出按钮
审阅工具栏溢出菜单中的新项,位于「复制分享链接」旁。
步骤 2
选项弹窗
格式、范围、包含回复、包含绘图。复用设置中的 ModalForm
步骤 3
方案最薄弱之处
后台任务 + 提示
导出在 worker 中渲染;用户收到「我们会通知你」提示后可离开页面。
步骤 4
就绪通知
铃铛通知 + 下载链接;也列在审阅 → 导出标签页下。

步骤 3 为何薄弱:大多数导出会在 3 秒内完成,因此即发即忘的提示可能显得过度——但含绘图的 400 条批注审阅需约 20 秒,阻塞太久。混合方案(最多等待 4 秒,然后回退到通知)更友好,但增加约半天。你来定。

B

实施顺序

执行顺序

我实际会按此顺序构建。每步在 CI 上变绿;第 5 步翻转标志前不会有用户可见内容上线。

  1. 1迁移 + 模型。 创建 annotation_exports,接入 Sequelize 模型、工厂、fixtures。0.5d
  2. 2类型 + 请求校验。 A2 中全部内容,加上 POST /api/reviews/:id/exports 的 zod schema。0.25d
  3. 3渲染器。 先做 CSV(简单),再通过现有 @acme/render 服务做 PDF。1d
  4. 4Worker 任务 + blob 上传。 exports.render 上排队;重试 3 次后 status = failed0.25d
  5. 5UI:菜单项、弹窗、提示、导出标签页。 在标志 export_annotations 后面。0.5d
  6. 6测试、文档、向 Acme 团队推出标志。0.25d
C

机械性工作(信任我)

低微调概率

功能所需的重构与管道。此处无判断点——有意折叠。

枯燥但必要 8 项任务 · 共约 0.5d,已计入上方估算
  • AnnotationSerializerapi/annotations.ts 提取到 lib/serializers/,供导出 worker 复用。纯移动,无行为变更。
  • 完成 CommentMarkerAnnotationMarker 重命名——仍有 3 个文件导入已弃用别名。
  • annotation_exports 加入 fixture 加载器和 CI 数据库重置脚本。
  • workers/index.ts 注册 exports.render,并加入死信告警列表。
  • flags.yaml 创建特性标志 export_annotations,默认关闭。
  • jobs/cleanup.ts 添加过期导出 blob 的夜间 TTL 清理(仅当选择 ② 保持不变时)。
  • 扩展 openapi.yaml 加入两个新端点并重新生成客户端。
  • 将时间码格式化辅助函数从 player/utils.ts 移至 lib/time.ts——PDF 渲染器需要它,不应从 player 包导入。

微调这三处

你可发送的最高杠杆回复。复制一条、编辑、发送——我会修订方案。

「将选择 ① 改为实时 JOIN——此处的导出是工作文档,不是记录。」
「从 ExportRequest 中移除 TimecodeRange;无人要求按场景导出。」
「步骤 3 采用混合方案:内联最多等待 4 秒,然后回退到通知。」