feat: SaaS P2 计量·增量1 —— 用量按租户落持久明细 + 积分/成本折算

链路打通 tenant → 计费事实源:
- 契约:Task.Meta 加 MetaTenantID;UsageEvent 加 TenantID。
- 提交:网关 task.Meta[MetaTenantID]=tenantID(c);dispatcher emitUsage 带租户。
- 明细表 sundynix_usage_event(追加式,task_id 唯一→幂等防重投重复计费):
  tenant/owner/model/tokens + credits_micro + cost_micros。
- 折算:credits=total_tok/TOKENS_PER_CREDIT×credit_weight(token 基准,设 1 即 token 直计);
  cost 按 Pricing 折算;Pricing 加 credit_weight 列(每模型积分权重,缺省 1)。
  模型名空则回退激活 chat 模型(近似,忽略 failover 备用模型,已在设计标注)。
- 网关 SubscribeUsage 折算落明细(保留 Redis 日计数作快速配额校验)。

live 验证:提交任务→一行 usage_event,tenant 匹配用户租户、
credits=89tok/1000×2×1e6=178000 微积分、cost=45/1000×1+44/1000×2=133000 微元 CNY,
折算数学与幂等键均正确。

设计见 SAAS_P2_DESIGN.md。增量2(credit_ledger 余额软扣 + rollup)待做。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-07 10:54:29 +08:00
parent 22183cac51
commit d032c198c8
8 changed files with 259 additions and 6 deletions
@@ -157,8 +157,9 @@ func (o *Orchestrator) emitUsage(t *contract.Task, b *harness.Budget) {
return
}
uid, _ := t.Meta[contract.MetaUserID].(string)
tid, _ := t.Meta[contract.MetaTenantID].(string)
if err := o.usageSink.PublishUsage(&contract.UsageEvent{
TaskID: t.ID, UserID: uid, PromptTok: p, CompTok: c, TotalTok: total,
TaskID: t.ID, UserID: uid, TenantID: tid, PromptTok: p, CompTok: c, TotalTok: total,
Exceeded: b.Exceeded(), TS: time.Now().UnixMilli(),
}); err != nil {
log.Printf("[usage] 回写用量失败 task=%s: %v", t.ID, err)