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
+5 -4
View File
@@ -249,10 +249,11 @@ func (p *Postgres) ListLinks(ctx context.Context, owner, kb string) ([]DocLink,
// 表名 sundynix_pricing。ModelID 关联 sundynix_model.id,唯一。
type Pricing struct {
BaseModel
ModelID string `gorm:"size:24;uniqueIndex"` // 关联 sundynix_model.id
InputPer1K float64 `gorm:"column:input_per_1k"` // 每 1K 输入 token 单价
OutputPer1K float64 `gorm:"column:output_per_1k"` // 每 1K 输出 token 单价
Currency string `gorm:"size:8"` // 币种(CNY / USD…)
ModelID string `gorm:"size:24;uniqueIndex"` // 关联 sundynix_model.id
InputPer1K float64 `gorm:"column:input_per_1k"` // 每 1K 输入 token 单价
OutputPer1K float64 `gorm:"column:output_per_1k"` // 每 1K 输出 token 单价
Currency string `gorm:"size:8"` // 币种(CNY / USD…)
CreditWeight float64 `gorm:"column:credit_weight"` // 积分权重(每 token 烧积分的倍率;0/缺省按 1.0 计)
}
func (Pricing) TableName() string { return "sundynix_pricing" }