feat(pricing): 支持 per_item 按逐条产出时长计费
- 新增 ChargeUsage.DurationItems 字段用于接收逐条时长明细 - 无明细时回落单条 DurationSec,兼容旧客户端和直接 /calc 调用 - 重构 charge 逻辑,按时长落档求和并支持上不封顶线性叠加
This commit is contained in:
@@ -18,7 +18,11 @@ import (
|
||||
// node_execution.token_info),本结构解析只取计价字段,未知键原样落库 order.usage 作审计(见 settleOrder)。
|
||||
// 模型计价字段见各计算器注释(spec §5.3)。
|
||||
type ChargeUsage struct {
|
||||
DurationSec float64 `json:"durationSec"` // 时长(秒):per_item/per_second 工作流结算、model 单位计价用
|
||||
DurationSec float64 `json:"durationSec"` // 时长(秒):per_second 工作流结算、model 单位计价用;per_item 无 durationItems 时回落单条
|
||||
// DurationItems 逐条产出时长明细(秒,per_item 工作流结算用):一条 = 一次视频节点执行产出的一条成片
|
||||
// (ai-agent 按 node_execution 记录收集,各条独立时长)。per_item 逐条各自落档求和(2×4s→2×29);
|
||||
// 为空回落单条 DurationSec(旧客户端 / 直接 /calc 调用,单条数值与旧口径一致)。
|
||||
DurationItems []float64 `json:"durationItems"`
|
||||
// FeeByModel 各模型已消耗费用(= 调用方按次调 shop /calc 返回费的合计,每次调用已 ceilFen 到分、各次媒体/费率
|
||||
// 按调用时快照)。per_item/per_second 取消补收、per_token 结算的 token 部分按此合计实收(不再按聚合 token
|
||||
// 重算,避免丢失「每次调用不足1分按1分」的兜底与逐调用媒体价——聚合重算会把两笔 0.01 合并成 0.01 且拿错媒体价)。
|
||||
@@ -146,19 +150,39 @@ func (perItemCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||
|
||||
func (perItemCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||
r := rules.(*perItemRules)
|
||||
// 0秒无产出(中途取消/未生成视频):按已消耗=0 计费,不落入第一档位价
|
||||
if usage.DurationSec <= 0 {
|
||||
// 逐条明细(durationItems:多产出,一条=一次视频节点执行产出的一条成片)为空时回落单条 durationSec,
|
||||
// 兼容旧客户端与直接 /calc 调用(单条数值与旧口径完全一致)。
|
||||
items := usage.DurationItems
|
||||
if len(items) == 0 {
|
||||
if usage.DurationSec <= 0 {
|
||||
return 0, nil // 0秒无产出(中途取消/未生成视频):按已消耗=0 计费,不落入第一档位价
|
||||
}
|
||||
items = []float64{usage.DurationSec}
|
||||
}
|
||||
// 每条按时长落档求和(chargePerItem 单条口径与旧 per_item 单条完全一致)
|
||||
var total float64
|
||||
for _, dur := range items {
|
||||
if dur <= 0 {
|
||||
continue
|
||||
}
|
||||
total += chargePerItem(r, dur)
|
||||
}
|
||||
if total <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return ceilFen(roundCost(total)), nil
|
||||
}
|
||||
|
||||
// chargePerItem 单条按时长落档:命中 tier → tier.Price;超出最大档位(上不封顶)→ 最大档价 + 超出秒数×overflow 线性叠加(元)。
|
||||
func chargePerItem(r *perItemRules, dur float64) float64 {
|
||||
last := r.Tiers[len(r.Tiers)-1]
|
||||
for _, t := range r.Tiers {
|
||||
if usage.DurationSec <= t.MaxSec {
|
||||
return ceilFen(t.Price), nil
|
||||
if dur <= t.MaxSec {
|
||||
return t.Price
|
||||
}
|
||||
}
|
||||
// 超出最大档位:上不封顶,线性叠加(元)
|
||||
overflow := ceilFen((usage.DurationSec - last.MaxSec) * r.OverflowUnitPrice)
|
||||
return ceilFen(last.Price + overflow), nil
|
||||
overflow := ceilFen((dur - last.MaxSec) * r.OverflowUnitPrice)
|
||||
return last.Price + overflow
|
||||
}
|
||||
|
||||
// ====================== per_second 按秒 ======================
|
||||
|
||||
Reference in New Issue
Block a user