Repository navigation
Forecast has no writer — implement the scheduled snapshot flow the object docs promise #590
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpm:queueReady for the PM dispatch loopReady for the PM dispatch looppm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchand removedpm:queueReady for the PM dispatch loopReady for the PM dispatch loop
on Aug 2, 2026 认领:PM 循环第 2 轮
会话:session_019SS7C5SXpniKeCApxgARyf
分支:claude/issue-590-forecast-snapshot
Worktree:hotcrm-issue-590
Generated by Claude Code
PM 复核:通过,已入队合并 — #615
核验依据(以 GitHub 为准,不采信报告自述):
- PR feat(forecast): add the forecast_snapshot nightly writer (#590) #615 为 draft、目标
main、正文以Fixes #590开头 - 9 项检查全绿(Build and Test 22.x / Quality Checks / Check Changeset / link-check / CodeQL / Analyze Code / Playwright / Label PR / Vercel)
- 变更 9 个文件,全部在范围内:新增
src/flows/forecast-snapshot.flow.ts、src/flows/index.ts注册、forecast.hook.ts+forecast.object.ts、test/flow-scheduled.test.ts+test/helpers/flow-harness.ts、两处文档、一份 changeset。无content/docs/releases/改动,无无关文件
交付内容:每晚 03:00 的
forecast_snapshot调度 flow(runAs: 'system'),为每一位持有活跃商机的负责人 upsert 一条当季crm_forecast快照,按存储列forecast_category做累进分桶(pipeline ⊇ best_case ⊇ commit),quota永不写入。期间边界交给forecast.hook.ts派生period_start——flow 模板表达不了"本日历季度第一天",由对象派生使 #530 钉住的日历对齐不变式对运行期快照同样成立。best_case_amount/commit_amount的字段描述改为它们真正度量的东西(原先写的概率阈值没有任何 writer 执行,且与 stage→category 映射矛盾)。10 个 runtime 用例跑真实
AutomationEngine(非 PENDING_FLOWS 豁免),并做了反向验证:把 best-case 过滤器从$in: ['best_case','commit']改成'best_case'后测试如期失败,断言不是空转。派生发现已另开 issue,未夹带进本 PR:#613(forecast 种子
externalId: 'period_label'在按人快照后不再唯一)、#614(quota_attainment_by_rep缺期间过滤导致跨期间求和)。两者将在下一轮评估是否入队。
Generated by Claude Code
- PR feat(forecast): add the forecast_snapshot nightly writer (#590) #615 为 draft、目标
- addedpriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVPand removed
on Sep 9, 2026
Found during a full business/platform-capability review of the app (2026-08-02). This is the highest-value functional gap that needs no platform work.
Problem
src/objects/forecast.object.ts:14-16documents that forecast snapshots are produced by a "forecast.hook.tsscheduled job (default: nightly)". That job does not exist:forecast.hook.tscontains onlyforecast_derive_period, abeforeInsert/beforeUpdatefield-derivation hook. No flow, hook, or job anywhere insrc/ever creates acrm_forecastrow.Consequence: the entire forecasting story — the forecast object, its
attainment_pct/coverage_ratioformulas, theforecast_metricsdataset, and the quota-attainment dashboard table — runs on the 3 hand-seeded rows insrc/data/index.ts. On any live environment the feature is an empty shell.Proposed shape
A scheduled flow (weekly or nightly — consistent with the object's own doc comment) that:
crm_opportunity:pipeline(open),best_case/commit(byforecast_category),closed_amount(won in period).(owner, period)so re-runs are idempotent — same discipline ascontract_renewal/opportunity_stagnation.quotauntouched (manually maintained until a quota model exists — separate backlog item).Follow the repo's sweep conventions: idempotency gate,
runAs, and a runtime test in the designated runtime-coverage file (theruntime-coverage.test.tsratchet will demand one anyway).Acceptance
crm_forecastrow with non-trivial pipeline numbers.forecast.object.tsdoc comment matches the implemented schedule.