Skip to content

docs(forecasting): 达成率有专门字段,并对齐 #702 后当前季度的唯一写入方 (#627) - #735

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-627-forecasting-attainment-docs
Aug 5, 2026
Merged

yinlianghui merged 1 commit into
mainfrom
claude/issue-627-forecasting-attainment-docs

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #627

仅改文档。content/docs/sales/forecasting.mdx 及其两个中文译本,外加一个 changeset。未触碰 src/**、测试、其它文档页,也未触碰 content/docs/releases/;平台依赖保持 @objectstack/* 17.0.0-rc.2 不变。

前提复核(先验证再动手)

issue 是 8/2 提的,而预测这块今天刚被 #702 / PR #717 改过,所以先对着 origin/main 逐条核实:

  • 那条 bullet 仍然一字不差地是 issue 引用的样子 —— - **Attainment** — closed_amount / quota is computed in the report layer; no dedicated field.
  • attainment_pct 仍然存在,表达式与 issue 引用完全一致(coalesce(record.quota, 0) > 0 ? (coalesce(record.closed_amount, 0) * 100.0) / record.quota : 0.0)。只有 description 文案被改写过(现为 "Closed Won ÷ Quota × 100. Reads 0% until a positive quota is set."),语义不变。
  • coverage_ratio 形状不变;attainment_pct 仍是 all_forecasts、this_quarter_forecasts、my_forecast 三个列表视图的列,也在记录表单的 Amounts 分区。
  • forecast_metrics 的 attainment 度量仍是 derived: { op: 'ratio', of: ['closed_sum', 'quota_sum'] },配额加权的理由仍写在 src/datasets/forecast.dataset.ts 里。

前提成立,按 issue 的建议改写。

一处措辞没有照抄 issue: issue 的 suggested fix 写的是 "attainment is a stored formula field"。公式字段不是存储列 —— src/views/forecast.view.ts 里的注释就是证据(按 attainment_pct 排序失效,因为"引擎在查询之后用 JS 算它,数据引擎从没见过这个排序键")。所以文中写的是"读取时按行从该行自己的两个列算出",没有写 stored。

一处结论来自实测而非推断: 我原本顺手写了"没设配额的销售代表达成率显示 0%"。查了 @objectstack/service-analytics 的 computeDerived:case "ratio": if (nums.length < 2 || nums[1] === 0) return null; —— 分母为 0 或任一侧为 null 时返回 null,不是 0。所以字段的兜底(0.00)和数据集度量的行为(不给值)在这个边界上是不一样的,文档现在把这个区别写出来了。

改了什么

达成率那条 bullet(issue 本体) —— 改成:有专门字段、不需要在报表层重建;attainment_pct 是每条快照上的公式字段,三个列表视图和记录表单上都有;零配额有保护(配额为 0 或没配额读作 0.00 而不是报错);coverage_ratio 同形状。随后新增一段说明"团队达成率不是各人达成率的平均值":字段是逐行 0–100 的百分数(106.00 表示 106%),数据集度量是先分别求和再相除的 0–1 比值,用意是按配额大小加权。

字段表 —— 新增 Derived 一行(预期金额 / 达成率(%)/ 覆盖倍数),否则读者查表仍然看不到这些字段存在。

同页一致性清扫(#702 / PR #717 之后已不成立的说法)

按派发要求,同一页里凡是与"当前季度的快照从哪来、行何时出现、谁写配额"相冲突的都在本 PR 一并修正:

  1. 《How forecasts get created》新增一段"当前季度只有一个自动写入方" —— 种子只播已结束的季度 + 当前月份,当前季度的窗口只归 forecast_snapshot;并写明两个后果:刚播种的组织在凌晨 3 点定时快照首次跑完前没有当前季度的行(Quota Attainment by Rep 空着是对的),以及定时快照打开的每一行配额都是空的、直到有人手工填(没有任何自动化写配额)。依据 src/data/revenue.seed.ts 的 forecast_snapshot 与启动重播种的互动:每次 dev 重启后,当季出现一条无 owner 的幻影快照行与 owner 键控行并存 #702 注释与 src/flows/forecast-snapshot.flow.ts。
  2. 汇总仪表盘那条 bullet —— 补上"在定时快照打开当前季度之前它是空的",以及没设配额的代表那一行仍会出现、只是没有达成率(依据上面 ratio 的实现)。
  3. FAQ「预测能自动从商机汇总吗?」 —— 原文按"得由你自己搭流程"来答,而 forecast_snapshot 自 Forecast has no writer — implement the scheduled snapshot flow the object docs promise #590 起就在做这件事。改为:当前季度四个金额每晚从实时商机重新求和写回同一行,配额是唯一例外;对象不会在读取时重算已存储的金额,而派生字段正相反(所以在旧快照上把配额改对,那一行的达成率会跟着重算)。

其余仍然准确的段落(周期派生、桶的嵌套关系、权限、"始终把汇总限定在一个周期"提示框)未动。

中文译本

forecasting.zh-Hans.mdx 与 forecasting.zh-Hant.mdx 都存在,同样的修正都已应用(术语沿用相邻中文页与 src/translations/zh-CN.ts 的产品标签:配额 / 达成率(%)/ 覆盖倍数 / 数据集 / 度量 / 快照;zh-Hant 用 資料集 / 度量 / 小工具 / 儀表板)。

需要说明的是:两个中文页相对英文页还有更早的、与本 issue 无关的漂移 —— 例如把 pipeline / best case / commit 说成"相互独立的桶"(英文页是嵌套的置信度阶梯)、"承诺(按惯例 ≥ 80% 概率)"这个 #590 已经明确否定的概率阈值说法、以及整段缺失的"始终把汇总限定在一个周期"提示框。这属于整页重译的工作量(与 #685 / PR #723 对 dashboards 中文页做的事同类),混进本 PR 会把两件事搅在一起,因此未在此处处理,见下方 findings。

验证

  • pnpm typecheck → exit 0
  • pnpm test(全量,--maxWorkers=2)→ Test Files 59 passed (59) / Tests 1413 passed | 1 skipped (1414)
  • pnpm hygiene → clean
  • 控制字符自查:grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 对三个 mdx + changeset 无匹配;file 报告三个文件均为 UTF-8 text
  • 仓库里没有针对 forecasting.mdx 的 docs 守卫(test/docs-drift.test.ts 的页面清单只包含 analytics/dashboards 三页;它对 content/docs 的全目录扫描只看代码围栏里的 agent: 键,本 PR 未新增任何代码围栏),因此没有需要同步更新的测试期望。

顺带发现(已另行开 issue,未在本 PR 修)


Generated by Claude Code

…current quarter's single writer (#627)

The forecasting guide told admins attainment was "computed in the report
layer; no dedicated field". `crm_forecast.attainment_pct` has been a formula
field on every snapshot — closed / quota * 100, guarded so a zero or missing
quota reads 0.00 — surfaced on all three forecast list views and the record
form, with `coverage_ratio` beside it.

The rewritten bullet also states why the per-row field and the dashboard
number differ on purpose: the field is a per-row 0-100 percentage, the
`forecast_metrics` dataset's Attainment measure is a 0-1 ratio that sums both
sides first so a group is weighted by the quota each rep carries. They part
ways on a missing quota too — the field reads 0.00, the ratio measure returns
no value (verified against the analytics `ratio` op).

Same-page corrections, all statements #702 left stale: where a current-quarter
row comes from (seeds ship settled quarters and the current month only, the
nightly sweep is the sole automated writer), when rows appear (a fresh org has
none until the 03:00 sweep, so Quota Attainment by Rep is legitimately empty),
who writes quota (nothing does), and the FAQ that answered as though you had
to build the roll-up flow yourself.

Both Chinese translations carry the same corrections.

Fixes #627.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 5, 2026 3:51pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forecasting guide says attainment has "no dedicated field", but crm_forecast.attainment_pct is one

2 participants