Files
ModelRouter/internal/core
JianFeeeee 29c87ce015 feat(channels): 余额分档选号(照参考 balance-rank/selector)
解决什么:一个号常同时持有多**种**资源包(如「14 天到期」+「8 年后扣费截止」),
而**服务端扣哪个包不由我们决定** —— 插件能做的是决定**用哪个号**:
- 优先用「还有 15 天内到期积分」的号(那部分不用就作废)
- 只剩永久积分的号排最后
- **锁定永久积分**时,只剩永久积分的号**等同于不可用**

为什么是 15 天(照参考的实测对照表,两站差别极大):
- WorkBuddy 5 个号:Bonus Pack 250(8~9 天)+ Free Plan 100(3008 天)
  → 锁定后 4 个号仍可用、1 个不可用,正是期望效果
- CodeBuddy 中国版:裂变包距到期**最少 17 天** → 17 > 15 ⇒ 全算永久
  ⇒ 锁定后立刻「无可用账号」—— 判据没错,这类池想用锁定要把窗口放宽

实现要点(三条都是参考点名的坑):
1. **只缓存原料,绝不冻结分类**。「临时/永久」是**当前时刻的函数**,而宿主长期
   开着、时间只向前流:一笔距到期 15.02 天的余额,在用户什么都不做的两分钟里就
   越过了线。若把分桶结果也缓存,选号会继续按「永久」处理一笔马上要作废的积分
   (锁定时更糟:本该可用的号被判成不可用)。故缓存 60s 的**包列表明细**,
   每次都重新分桶。
2. **失效包必须跳过**(`active === false`):服务端仍返回它们的余额,并进任何
   一桶都会虚增可用额度。
3. **到期时间未知归入永久桶**:保守方向 —— 最坏是锁定时少用一个号,
   而不是误把长期积分当快到期烧掉。

口径与既有 `total_fn` 一致:只算 **remaining**(CycleCapacityRemain,本周期剩余),
与 IDE 顶部的 Credits Balance 一致。刻意不用 TotalDosage —— 那是**终身口径**
且取整(实测同一响应 TotalDosage=655 而 IDE 显示 155.67)。

★ 两个 Lua 环境的实测发现(写在这里免得下次再踩):
- 本 VM 的 **os 表没有 setenv**(实测键:clock/date/difftime/execute/exit/
  getenv/remove/rename/setlocale/time/tmpname),所以"面板改窗口"必须走
  settings 而不是设环境变量;getenv 只用于读部署期注入值。
- `os.setenv and os.setenv(...)` 是**语法错误** —— `and` 是表达式,不能作语句。

判据(channels_balance_tier_test.go,假余额上游):
临时桶=100(只有 10 天内那个)、永久桶=50(远期+未知)、失效包的 9999 被跳过、
档位判定、锁定后仍可用(有临时积分)、窗口设 0 后临时桶归零且锁定后不可用。
回归:9 个包全绿。
2026-10-14 15:24:24 +08:00
..