Repository navigation
fix(dmn): 修热度图整片饱和与冷段跳到下周 (0.9.1) - #10
Conversation
两个问题都出在 0.9.0 的新口径上。 热度图全亮:颜色分母取的是累计计数峰值,升级当天累计计数还没攒起来 (只有新版本之后的消息),分母被压到 1,而衰减权重普遍在 1~3 之间, ratio 全部超过 1 被截断,整张图变成一片暖色。分母改为取累计峰值与 当前权重的较大者,既保留随时间变淡,也不会因为计数没攒够就顶满。 冷段跳到下周:_pick_run 原来用"起点是否已过"排序,今天已经开始的冷段 被排到最后,跨到下周的同类时段反而排前面,于是排期直接从周五 22 点 跳到下周五凌晨。改为看段尾是否还没结束,未结束的段交给 _refine 从当前时刻起算。 顺带补一个升级迁移:老快照没有 raw_fine/raw_coarse 时,用衰减值作为 累计计数起点,避免格内数字从 0 重来、和颜色对不上。
Reviewer's Guide本次修复针对热度图升级后整片饱和和调度跨周跳跃两个问题:使用当前权重参与归一化、为旧快照迁移累计计数,并按冷段结束时间选择仍可用的时段;项目版本同步升级至 0.9.1。 Flow diagram for heatmap normalization and snapshot migrationflowchart TD
A[Load planner snapshot] --> B{raw_fine or raw_coarse present?}
B -->|Yes| C[Use cumulative counts]
B -->|No| D[Use decayed fine/coarse values as baseline]
C --> E[Compute weights with decay]
D --> E
E --> F["peak = max(1, max(weights), max(counts))"]
F --> G[Render heatmap with gradual intensity]
Flow diagram for selecting an available cold scheduling segmentflowchart TD
A[Candidate cold segments] --> B[Calculate segment start and end]
B --> C{seg_end > now?}
C -->|Yes| D[Mark segment as live]
C -->|No| E[Mark segment as expired]
D --> F[Rank live segments by delay and length]
E --> F
F --> G[Select best segment]
G --> H[_refine starts from current time when needed]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
周五晚上看周一整天空着,排期却跳到下周五凌晨:候选冷段一直以"今天 是星期几"为基准展开,周一到周三根本不在候选里。今天的表里冷段被 周五到周日的活动挤光后,只剩同星期几的下周时段可选。 改为逐天展开今天起的一周,把每天的冷段换成绝对时间、丢掉已结束的, 再按真实距离挑最近的一段;分层仍按 fine → mid → coarse 优先。 _refine 相应改成收绝对区间,原来的 _pick_run 删除。 复现:按热力图的分布构造 12 天数据(周一二三近乎全空), 现在 2026-11-06 22:20、penalty 43,修复前落在下周五,修复后落在 2026-11-09 03:00(周一凌晨,52.7 小时后)。
There was a problem hiding this comment.
Hey - I've found 5 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="amrita_plugin_memory/rethinking/schedule.py" line_range="682" />
<code_context>
- delay = max(0.0, (seg_start - now.astimezone(self.tz)).total_seconds())
- key = (0 if delay > 0 else 1, delay, -length_slots)
+ seg_start, seg_end = self._seg_bounds(start_slot, length_slots, owner, now)
+ live = 0 if seg_end > now_local else 1
+ delay = max(0.0, (seg_start - now_local).total_seconds())
+ key = (live, delay, -length_slots)
</code_context>
<issue_to_address>
**Usable cold windows get skipped**
When the selected cold segment has too little time remaining for the run or safety buffer, while a later run in the same tier can fit, `_pick_run` ranks the in-progress segment first, and `_plan` refines only that selected run per tier. When it cannot fit, planning advances to another tier instead of trying the later run, potentially skipping a usable window or scheduling an unusable fallback.
Have `_plan` try the next run in the same tier when the selected run cannot produce a usable plan.
</issue_to_address>
### Comment 2
<location path="amrita_plugin_memory/rethinking/schedule.py" line_range="294-297" />
<code_context>
self.state.raw_coarse = raw_coarse
+ # 升级前的快照没有累计计数:拿衰减值当起点,
+ # 否则格内数字从 0 重来,和颜色对不上
+ if not any(self.state.raw_fine) and any(self.state.fine):
+ self.state.raw_fine = list(self.state.fine)
+ if not any(self.state.raw_coarse) and any(self.state.coarse):
+ self.state.raw_coarse = list(self.state.coarse)
seen = raw.get("seen_days", [])
if isinstance(seen, list):
</code_context>
<issue_to_address>
**Migrated counts are not raw observations**
When a saved state from before cumulative counters were stored is loaded, `IdlePlanner.load` copies decayed `fine` and `coarse` weights into the cumulative count fields for legacy snapshots. The heatmap and README then present those values as raw observation counts, so users upgrading from such snapshots see approximate decayed weights labeled as actual counts.
Clarify that counts in migrated snapshots are approximate, or preserve separate provenance so the UI can label them accurately.
</issue_to_address>
### Comment 3
<location path="amrita_plugin_memory/rethinking/heatmap.py" line_range="296" />
<code_context>
- peak = max(1.0, max(counts)) if counts else 1.0
+ # 分母取累计峰值与当前峰值的较大者:既随时间整体变淡,也不会因为
+ # 累计计数还没攒起来就把每格都顶到最亮(升级当天的典型情况)
+ peak = max(1.0, max(weights), max(counts)) if counts else 1.0
for wd in range(DAYS):
</code_context>
<issue_to_address>
**Heatmap stops fading**
When cumulative counts are still lower than the decayed fine-weight peak, as in the described upgrade-day state, `peak` follows the same decay as `weights` while the decayed maximum exceeds both 1 and the cumulative-count maximum, so the hottest cell stays at ratio 1 and the other cell ratios stay effectively unchanged. After an upgrade with low cumulative counts, the chart therefore does not fade overall until the weights drop below that fixed floor.
Normalize decayed weights against a stable peak reference, such as the corresponding pre-decay peak, rather than a maximum that decays in lockstep with the cells.
</issue_to_address>
### Comment 4
<location path="amrita_plugin_memory/rethinking/schedule.py" line_range="294-295" />
<code_context>
self.state.raw_coarse = raw_coarse
+ # 升级前的快照没有累计计数:拿衰减值当起点,
+ # 否则格内数字从 0 重来,和颜色对不上
+ if not any(self.state.raw_fine) and any(self.state.fine):
+ self.state.raw_fine = list(self.state.fine)
+ if not any(self.state.raw_coarse) and any(self.state.coarse):
+ self.state.raw_coarse = list(self.state.coarse)
</code_context>
<issue_to_address>
**Legacy activity is missing from counts**
When a legacy heatmap has older activity in cells with no post-upgrade observations, alongside at least one post-upgrade raw observation, `IdlePlanner.load` skips migration for the whole array as soon as any raw counter is nonzero. `render_heatmap_png` then uses raw counts for every cell, so legacy activity in cells without post-upgrade observations remains missing from displayed counts.
Migrate legacy weights into raw counts per cell whenever that cell has no raw observations, rather than gating migration on the whole array.
Also at `amrita_plugin_memory/rethinking/schedule.py:296-297`.
</issue_to_address>
### Comment 5
<location path="amrita_plugin_memory/rethinking/heatmap.py" line_range="294-295" />
<code_context>
weights = [max(0.0, v) * decay for v in state.fine]
- # 分母取累计峰值:整张图会随时间一起变淡,而不是每格重新拉满到最亮
- peak = max(1.0, max(counts)) if counts else 1.0
+ # 分母取累计峰值与当前峰值的较大者:既随时间整体变淡,也不会因为
+ # 累计计数还没攒起来就把每格都顶到最亮(升级当天的典型情况)
+ peak = max(1.0, max(weights), max(counts)) if counts else 1.0
</code_context>
<issue_to_address>
**Explanatory comments span multiple lines**
When an explanation needs more than one line, the new consecutive `#` lines violate the single-line short-comment convention.
Rewrite the explanation as a concise single-line comment.
Also at `amrita_plugin_memory/rethinking/schedule.py:292-293`, `amrita_plugin_memory/rethinking/schedule.py:671-676`.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 4 findings to address first, and when loading an older snapshot, the change copies decayed fine/coarse values into the new raw counters, and those inferred counters can affect later heatmap values after a revert. The effect is bounded and can be cleared or corrected by resetting the affected state; the scheduling change otherwise alters runtime slot selection and is reversible.
Blocking findings: amrita_plugin_memory/rethinking/schedule.py:682, amrita_plugin_memory/rethinking/schedule.py:297, amrita_plugin_memory/rethinking/heatmap.py:296, amrita_plugin_memory/rethinking/schedule.py:295
- _plan 选中最近一段后若 _refine 放不下,继续试同层后面的候选, 不再直接跳到下一层或退回保底窗口 - 迁移改成逐格补位:老快照里只有部分格子有新计数时, 其余格子仍用衰减值补上,累计计数不再漏掉旧活动 - 新增 migrated_counts 标记,含估算时图下副标题注明「含迁移估算」 - 颜色分母回到累计峰值(不衰减),恢复"整张图随时间变淡"; 累计计数为 0 时才退回当前权重,避免除零把每格顶满 - 按仓库规范把多行内联注释全部收成单行(heatmap、schedule、runner)
症状
原因
全亮:颜色分母用的是累计计数峰值(
raw_fine)。升级当天累计计数只有新版本之后的消息(图里显示「观测 1 条」),分母被压到 1,而衰减权重普遍在 1~3 之间,ratio全部超过 1 被截断成最亮。跳周:
_pick_run按「起点是否已过」排序,今天已经开始、但还没结束的冷段被排到最后;跨到下周的同类时段起点在未来,反而排前面。漏掉周一二三:候选冷段一直以「今天是星期几」为基准展开,只算今天与次日两天的表。周五晚上看,今天的表里冷段早被周五到周日的活动挤光,剩下的候选只有同星期几的下周时段,周一到周三整天空窗根本不参与比较。
改动
peak = max(1.0, max(weights), max(counts)):分母取累计峰值与当前权重的较大者,保留「随时间整体变淡」,又不会因为计数没攒够就顶满_candidates),把每天的冷段换成绝对时间、丢掉已结束的,再按真实距离挑最近的一段(_nearest);分层仍按 fine → mid → coarse 优先_refine改成收绝对区间,删除只按今天算的_pick_runIdlePlanner.load增加迁移:老快照没有raw_fine/raw_coarse时,用衰减值作为累计计数起点验证
2026-11-09 03:00(周一凌晨,52.7 小时后);penalty 1 / 10 / 43 三档结果一致ruff check/ruff format --check/pyright全部通过Summary by Sourcery
Fix heatmap intensity scaling and idle-window selection across weekly scheduling boundaries.
Bug Fixes:
Enhancements:
Build: