feat: 合入火山免费额度自动消耗任务(每日23点洗过期包防扣费)

- 新增 volc_consume.py:消耗任务模块,直连 Ark 洗 9 天内过期资源包
  (旧 calculate_plan 均分×1.1/24h冲刺保留 + 双 clamp:2M 采集上限、余额−BUFFER 防扣费)
- volc_limit_sync.py:限额 v2 简化(stable_limit = usage+balance−BUFFER,删除峰值状态机 ~90 行),
  HARD_DAILY_LIMITS 统一 2M(pre-block 不滞后,1.8M 折扣作废),
  新增 _maybe_trigger_daily_consume(北京时间 23:00 ± 抖动,SETNX 去重,延迟导入)
- docker-compose 挂载 volc_consume.py;AGENTS.md/文档同步
This commit is contained in:
vibe-code
2026-08-11 13:15:24 +00:00
parent 2cc7c7d08b
commit 96b81d52c4
5 changed files with 630 additions and 98 deletions
+39 -9
View File
@@ -155,21 +155,51 @@ daily_usage ≥ 2M → 停(采集上限,再烧不返还
---
## 四、限额设计(防扣费)
## 四、限额设计(防扣费)——v2 简化逻辑
### 4.1 effective_limit 语义收敛
### 4.1 为什么废弃 Peak-Buffer 峰值状态机(存量场景产物)
旧逻辑 `stable_limit = PeakPool × 0.85`(PeakPool = usage + balance,首锁/门控重锁/DROP 状态机维护)有两个问题:
1. **比例 margin 在 usage 接近 balance 时数学失效**(致命):
- `u=0.5b` 时限额允许再烧 `0.775b`,实际余额只剩 `0.5b`(超 55%)
- `u=0.8b` 时允许再烧 `0.73b`,实际只剩 `0.2b`(超 3.6 倍)→ **烧穿扣费**
- 存量场景没爆是因为 usage 远小于 balance;新模型"返还 = 消耗"使 usage 天天逼近 balance → 必炸
2. **峰值状态机防的是旧 bug**(存量包余额只减不增时,滞后虚高把 PeakPool 漂移、限额越算越多 399,836 → 599,511)。新模型 balance 每天真实跳涨(返还),锁峰值会把真实发放当异常压住——**跟随现值即可,状态机删除**
### 4.1b 1.7M/1.8M 保守值的来历:post-hoc 滞后时代的遗产
旧拦截机制是"请求成功后记账 + 踢模型"(`log_success_event`),**超限请求本身已放行**,存在"滞后一个请求"的漏网量。因此限额必须打折留安全垫:`HARD_DAILY_LIMITS` 默认 1.8M(2M 打 9 折)、`stable_limit` 打 8.5 折(≈1.7M 效应)。
**现状已改为请求前闸门**:`async_pre_call_hook` → `_check_request_block`(429 拒绝,不进 router,不触发 retries/fallback)——主拦截不再滞后,"滞后一个请求"的折扣失去存在必要。新公式精确到 2M;BUFFER 30 万保留,但它防的是**余额统计滞后**(实测 20 万内不动,固定量非比例),与请求滞后无关。
### 4.2 新公式
```
effective_limit = min(HARD_DAILY_LIMITS, stable_limit)
HARD_DAILY_LIMITS → 统一改为 2M(含 doubao-seed-code-preview-251028 的 480 万遗留,全部模型 2M 上限)
stable_limit → PeakPool − 15% margin(保留,防余额统计滞后:实测 20 万内余额不动)
effective_limit = min(HARD_DAILY_LIMITS, max(0, usage + balance − BUFFER))
HARD_DAILY_LIMITS → 统一 2M(采集上限;doubao-seed-code-preview-251028 480 万遗留一并改为 2M)
```
- PeakPool = usage + balance 在"日返还"模型下仍成立(≈ 当日可用总额)
- 真实用户被限在 ~1.7M,消耗任务洗过期额度 → 两者不抢额度(FIFO 方向一致)
- 返还延迟/发放不足时:balance 低 → `remaining` 低 → limit 低 → 真实用户提前被限 ✓
**`usage + balance` = 当日初始可用池,天然稳定**:
- 消耗中:usage ↑ / balance ↓ → 池恒定 → 限额恒定 ✓
- 返还:balance 跳涨 → 池涨 → 限额次日自动解锁(无需 RECHARGE)✓
- 过期:balance 跳跌 → 池跌 → 限额收紧(无需 DROP)✓
- 余额滞后(实测 20 万内不动):池虚高 ≤ 20 万 → BUFFER 30 万覆盖 ✓
- 消耗任务 23:00 烧(usage 计入):池不变 → 滞后窗口内限额偏松 ≤ BUFFER ✓
- 余额耗尽:池 − 30 万 ≤ 0 → 限额 0 → 全拦,扣费结构性杜绝 ✓
### 4.2 空窗期防扣费闭环(消耗量计入 daily_usage 的理由)
**与消耗任务同构**:限额放行条件 = `balance − BUFFER ≥ 0`;消耗任务停止条件 = `余额快照 − 已烧 ≤ 0`——同一把尺子。
### 4.3 简化后保留/删除清单
| 保留 | 删除(~100 行) |
|---|---|
| 同步频率控制(驱动改为 balance,余额越低同步越勤) | `volc:peak_pool:*` 状态机 |
| `remaining = balance − BUFFER` 写入 | RECHARGE / DROP / 首锁 / 门控重锁分支 |
| 软踢 / 恢复 / 审计 / pre-block / usage 记账 | `volc:last_seen_pool` / `volc:last_balance` |
| `get_usage_key` 北京时间滚动 | stable_limit 改直接写 `max(0, usage + balance − BUFFER)` |
### 4.4 空窗期防扣费闭环(消耗量计入 daily_usage 的理由)
23:00 消耗任务把额度洗掉后余额可能很低,此时真实用户请求必须被拦。拦截依据 = `daily_usage vs effective_limit`: