diff --git a/service/pricing/charge_calc.go b/service/pricing/charge_calc.go index 8b61b96..ce97cb2 100644 --- a/service/pricing/charge_calc.go +++ b/service/pricing/charge_calc.go @@ -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 按秒 ======================