**背景**:#134(补齐组合情报"无实质变化"运行路径)已合并。审查时发现判定逻辑存在方向相反的偏差,建议单独跟进。 **会把「有变化」误判成「无变化」(风险方向)** 1. **NaN 不落 failure**:`packages/shared/src/automation/runner.ts:112` 对 `priceMovePct` 只查 `=== undefined`,脏数据产生的 `NaN` 不落 failure,而 `NaN >= 阈值` 为 `false`(`runner.ts:295-299`)→ 被计成 `no_material_update`。 2. **探针异常静默降级**:`runner.ts:273-292` 的 calendarProbe/diff 探针异常时静默降级为"无信号",所以 `failures.length === 0` 并不等于"确实没有事件"。 3. **新鲜度判断缺失**:`Quote.timestamp` 未参与判断,`scopeSnapshot.sourceFetchedAt`(`packages/core/src/automation.ts:40-47`)只写不读 → 无法区分"新取的旧数据"。 **反方向:过于严格** `failures` 把通知失败/研究失败也算进去(`runner.ts:122,130`),于是**一次 OS 通知失败就会让整轮变成 incomplete**,新路径在生产可能长期走不到,日报的 quiet 段会静默丢符号。 **测试缺口** 本 PR 新增的两个 catch(`runner.ts:101-106` evaluation failed、`208-211` scope unavailable)、NaN `priceMovePct`、以及"通知失败 → incomplete"都没有测试;`RuleCard` 只测了 `no_material_update`,没测 `material_update`/`incomplete`。 **建议** - `priceMovePct` 用 `Number.isFinite` 判定; - 探针异常至少留一条 failure 或单独的 degraded 标记,让"无信号"和"探测失败"可区分; - 考虑把通知/研究失败从"决定 material 与否"的 failures 里拆出来,避免 noisy 通知把整轮降级。
Activity
xiaodu55 commentedon Sep 23, 2026
/claim
github-actions commentedon Sep 23, 2026
@xiaodu55 已认领该 Issue,并已自动设为 Assignee。提交 PR 时请在描述中写
Closes #185。fix(automation): 收紧无实质变化判定的三个误判口子,投递失败不再降级整轮运行
fix(automation): 收紧无实质变化判定的三个误判口子,投递失败不再降级整轮运行 (#188)
xiaodu55 commentedon Sep 24, 2026
#185 第 3 点(新鲜度语义)设计提议——#188 已收口前两个误判口子和过严问题,本条单独讨论,定案后我再提实现 PR。
现状:
runner.ts的fetchQuote只取result.data,把ProviderResult.provenance(含stale: boolean和fetchedAt)直接丢弃;scopeSnapshot.sourceFetchedAt也是只写不读。所以"新取到的旧数据"目前在判定里完全不可见。一个关键观察:provider 层其实已经有 staleness 判定——
provenance.stale由 Provider/Router 自己的口径维护,runner 不必自造"多旧算旧"的墙钟阈值。方案据此分两条:provenance.stale === true的 quote 记为评估类 failure({{symbol}}: quote stale,沿用 fix(automation): 收紧无实质变化判定的三个误判口子,投递失败不再降级整轮运行 #188 的 evaluationFailures 机制 → 整轮 incomplete,绝不当"无变化");portfolio scope 的sourceFetchedAt超过规则执行时刻一个可配置上限(如 24h)→ scope failure。阈值集中放常量并在 PR 里给出取值依据。AutomationRun增加可选staleQuotes: string[]/degraded: boolean,stale 数据照常参与判定但留下可观察痕迹,不改变 outcome;等产品定调后再收紧为方案 1。想请维护者拍板两点:① 用 provider 的
provenance.stale口径,还是 runner 自定墙钟阈值?② stale 时 fail-closed(方案 1)还是先只做可见性(方案 2)?定了我就提实现 PR。test(automation): RuleCard 补齐 material_update 与 incomplete 状态展示测试 (#194)
github-actions commentedon Oct 18, 2026
@xiaodu55 这个 Issue 已认领超过 7 天,目前没有发现关联的 open PR,也没有看到最近 7 天的进度更新。
如果仍在推进,请在 3 天内 留一条简短进度说明(包括正在等待的前置依赖也可以);如果已经有 PR,请贴出链接。若 3 天内没有回应,认领会自动释放,方便其他贡献者接手。
github-actions commentedon Oct 21, 2026
@xiaodu55 在提醒后 3 天内没有进度回应,且仍未发现关联的 open PR;本次认领已自动释放。Issue 保持开放,可以重新认领。
2Zest commentedon Oct 26, 2026
我想接手第 3 点“新鲜度语义”的后续,但不重复 #188 已合入的 NaN、探针异常和投递失败修复。
当前实现会丢弃
ProviderResult.provenance.stale,而scopeSnapshot.sourceFetchedAt只写不读;因此“新获取的陈旧数据”可能被当作无实质变化。请维护者确认采用哪种产品口径:stale === true或超出允许时限时,整轮标记为incomplete;degraded/staleQuotes供 UI 与追踪展示。确认后我会只实现选定策略,补充 stale quote、过期 scope、正常新鲜数据的回归测试,并附完整验证报告。
helsome commentedon Oct 26, 2026
@2Zest 可以按方案 1 推进第 3 点的新鲜度语义,范围不包含 #188 已合入的修复。
口径确认:
incomplete,不能据此得出“无实质变化”。降级标记可以补充说明原因,但不能替代这个判定。provenance.stale口径,runner 不另造统一的墙钟阈值。正常的数据源延迟、休市日与真正过期的数据需要区分。sourceFetchedAt单独定义时效规则,并说明依据;不要直接统一套用 24 小时。请补充 stale quote、过期 scope、正常新鲜数据和休市边界的回归测试,并在 PR 中附验证报告。