Skip to content

seedance price#4560

Open
Saiyi0203 wants to merge 1 commit intoQuantumNous:mainfrom
Saiyi0203:fix/seedance-price
Open

seedance price#4560
Saiyi0203 wants to merge 1 commit intoQuantumNous:mainfrom
Saiyi0203:fix/seedance-price

Conversation

@Saiyi0203
Copy link
Copy Markdown

@Saiyi0203 Saiyi0203 commented Apr 30, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

本次提交包含两组互相独立、但都围绕 Doubao Seedance 视频任务计费链路 的修复/功能增强:

  1. Seedance 2.0 新增 1080p 档位定价(relay/channel/task/doubao/)
    在原先只考虑「是否含视频输入」一个维度的基础上,把 seedance 2.0 系列的实际定价升级为 (模型, 输出分辨率, 是否含视频输入) 三元组,原因是上游 2026-01-28 价格表对 1080p 单独给出了一档不同的单价。

  2. 异步任务额度的"守恒"修复(service/task_billing.go 等)
    RefundTaskQuota / RecalculateTaskQuota 只动了 资金来源(钱包/订阅) 与 令牌额度,但没有同步回退 User.UsedQuota / Channel.UsedQuota / quota_data 看板统计,导致异步任务(seedance 等视频任务)一旦失败退款或事后差额结算,会出现:

用户「总额度 = Quota + UsedQuota」不再守恒,UsedQuota 永远偏高;实际消耗token未记录;
渠道用量(channels.used_quota)偏高;
/api/data/ 数据看板显示的 quota / token_used 与实际收费不一致;实际消耗token未记录。

🚀 变更类型 / Type of change

  • [√ ] 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • [√ ] ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • [√] 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • [√] 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • [√] Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • [√] 变更理解: 我已理解这些更改的工作原理及可能影响。
  • [√] 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • [√] 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • [√] 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

核验了用户扣费逻辑。
image
image
image
image

Summary by CodeRabbit

Release Notes

  • New Features

    • Added quota adjustment system supporting async refunds and adjustments with detailed token tracking
    • Implemented resolution-based pricing for video-capable models
  • Tests

    • Expanded test coverage for quota adjustment and billing recalculation scenarios

@Saiyi0203
Copy link
Copy Markdown
Author

image

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Walkthrough

The changes implement token-aware billing logs and establish quota adjustment mechanisms for asynchronous refund flows. They extend Doubao pricing from a single-dimension video-input discount to a two-dimensional model (resolution × video input) with validation. The task billing system is refactored to propagate token counts through quota operations and logging.

Changes

Cohort / File(s) Summary
Token-aware billing logging
model/log.go
Added PromptTokens and CompletionTokens fields to RecordTaskBillingLogParams and persists these values to the Log record. Clarifies that the function only writes to logs table without syncing quota_data.
Quota adjustment utilities
model/usedata.go, model/user.go
Added LogQuotaDataAdjust() for asynchronous refund/adjustment flows supporting signed deltas and hourly rounding. Added UpdateUserUsedQuotaDelta() to adjust used_quota via batch pipeline with no impact on request_count.
Doubao 2D pricing system
relay/channel/task/doubao/constants.go
Replaces single-dimension video-input discount with two-dimension pricing indexed by resolution and video-input presence. Introduces ResolveBillingRatios() that returns component ratios with normalization and unsupported combination detection. Removed GetVideoInputRatio().
Doubao validation and billing
relay/channel/task/doubao/adaptor.go
Adds pre-validation checks for Seedance 2.0 models using ResolveBillingRatios() with fast-fail on unsupported resolution/video combinations. Updates EstimateBilling() to compute 2D billing ratios and adds extractResolution() helper.
Task billing refactor
service/task_billing.go
Adds taskAdjustQuotaData() helper for quota_data updates. Updates RefundTaskQuota() to reverse User.UsedQuota and record refund logs with explicit token fields. Refactors RecalculateTaskQuota() to accept totalTokens parameter and sync token statistics to quota_data. Updates RecalculateTaskQuotaByTokens() to pass token count.
Task billing tests
service/task_billing_test.go
Adds quota_data migration/clearance and cache reset between tests. Introduces seeding and read-back helpers for quota state. Updates existing tests to model pre-consumed state, verify invariants across quota fields, and confirm request_count remains unchanged. Adds end-to-end tests for quota_data async flush and token-based recalculation.
Polling integration
service/task_polling.go
Updates RecalculateTaskQuota call to pass taskResult.TotalTokens parameter when adaptor adjustment succeeds.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Calcium-Ion

Poem

🐰 Two dimensions now shape the price,
Tokens flow through logs precise,
Quotas adjust without a hitch,
From Doubao's pixels to the switch—
All balanced, measured, just so nice!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'seedance price' directly relates to the main changes: adding 1080p pricing tier for Doubao Seedance 2.0 and fixing billing quota conservation, both centered on pricing and quota management.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@service/task_billing.go`:
- Around line 150-165: taskAdjustQuotaData currently logs adjustments with
common.GetTimestamp(), causing adjustments to land in the current hour instead
of the original task submission hour; change it to derive the bucket timestamp
from the task's original submission time (use task.SubmittedAt or task.CreatedAt
as provided by the Task model), truncate/round that time to the hour used by
LogTaskConsumption, and pass that derived timestamp into
model.LogQuotaDataAdjust (instead of common.GetTimestamp()) so quota_data
adjustments are recorded in the same hourly bucket as the original
LogTaskConsumption.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eeac2584-2df3-4d28-be4f-b3c3e2eb1ccb

📥 Commits

Reviewing files that changed from the base of the PR and between aa73039 and f6e6a28.

📒 Files selected for processing (8)
  • model/log.go
  • model/usedata.go
  • model/user.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/constants.go
  • service/task_billing.go
  • service/task_billing_test.go
  • service/task_polling.go

Comment thread service/task_billing.go
Comment on lines +150 to +165
// taskAdjustQuotaData 同步调整 quota_data 统计(/api/data/ 数据来源)。
// quotaDelta / tokenDelta 均为有符号增量:
// - 失败退款:quotaDelta=-quota, tokenDelta=0(原始记录 token 就是 0,对称回退即可)
// - 补扣 / 部分退款 + 真实 token:quotaDelta=±delta, tokenDelta=+totalTokens
// (token 之所以 *永远是 +*:原始 LogTaskConsumption 写入的 token 都是 0,
// 现在要补到 totalTokens,所以是单向增量,不随 quota 取号)
func taskAdjustQuotaData(task *model.Task, quotaDelta, tokenDelta int) {
if !common.DataExportEnabled {
return
}
if quotaDelta == 0 && tokenDelta == 0 {
return
}
username, _ := model.GetUsernameById(task.UserId, false)
model.LogQuotaDataAdjust(task.UserId, username, taskModelName(task), quotaDelta, common.GetTimestamp(), tokenDelta)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bucket quota_data adjustments into the original task hour.

LogTaskConsumption records the pre-consume stats in the submission bucket, but this helper always uses common.GetTimestamp(). If a task is refunded or reconciled after the hour rolls over, the original bucket stays inflated and the adjustment lands in a different hour, so /api/data is still wrong for time-scoped queries.

Suggested fix
 func taskAdjustQuotaData(task *model.Task, quotaDelta, tokenDelta int) {
 	if !common.DataExportEnabled {
 		return
 	}
 	if quotaDelta == 0 && tokenDelta == 0 {
 		return
 	}
 	username, _ := model.GetUsernameById(task.UserId, false)
-	model.LogQuotaDataAdjust(task.UserId, username, taskModelName(task), quotaDelta, common.GetTimestamp(), tokenDelta)
+	createdAt := task.CreatedAt
+	if createdAt == 0 {
+		createdAt = common.GetTimestamp()
+	}
+	model.LogQuotaDataAdjust(task.UserId, username, taskModelName(task), quotaDelta, createdAt, tokenDelta)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// taskAdjustQuotaData 同步调整 quota_data 统计(/api/data/ 数据来源)。
// quotaDelta / tokenDelta 均为有符号增量:
// - 失败退款:quotaDelta=-quota, tokenDelta=0(原始记录 token 就是 0,对称回退即可)
// - 补扣 / 部分退款 + 真实 token:quotaDelta=±delta, tokenDelta=+totalTokens
// (token 之所以 *永远是 +*:原始 LogTaskConsumption 写入的 token 都是 0,
// 现在要补到 totalTokens,所以是单向增量,不随 quota 取号)
func taskAdjustQuotaData(task *model.Task, quotaDelta, tokenDelta int) {
if !common.DataExportEnabled {
return
}
if quotaDelta == 0 && tokenDelta == 0 {
return
}
username, _ := model.GetUsernameById(task.UserId, false)
model.LogQuotaDataAdjust(task.UserId, username, taskModelName(task), quotaDelta, common.GetTimestamp(), tokenDelta)
}
// taskAdjustQuotaData 同步调整 quota_data 统计(/api/data/ 数据来源)。
// quotaDelta / tokenDelta 均为有符号增量:
// - 失败退款:quotaDelta=-quota, tokenDelta=0(原始记录 token 就是 0,对称回退即可)
// - 补扣 / 部分退款 + 真实 token:quotaDelta=±delta, tokenDelta=+totalTokens
// (token 之所以 *永远是 +*:原始 LogTaskConsumption 写入的 token 都是 0,
// 现在要补到 totalTokens,所以是单向增量,不随 quota 取号)
func taskAdjustQuotaData(task *model.Task, quotaDelta, tokenDelta int) {
if !common.DataExportEnabled {
return
}
if quotaDelta == 0 && tokenDelta == 0 {
return
}
username, _ := model.GetUsernameById(task.UserId, false)
createdAt := task.CreatedAt
if createdAt == 0 {
createdAt = common.GetTimestamp()
}
model.LogQuotaDataAdjust(task.UserId, username, taskModelName(task), quotaDelta, createdAt, tokenDelta)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/task_billing.go` around lines 150 - 165, taskAdjustQuotaData
currently logs adjustments with common.GetTimestamp(), causing adjustments to
land in the current hour instead of the original task submission hour; change it
to derive the bucket timestamp from the task's original submission time (use
task.SubmittedAt or task.CreatedAt as provided by the Task model),
truncate/round that time to the hour used by LogTaskConsumption, and pass that
derived timestamp into model.LogQuotaDataAdjust (instead of
common.GetTimestamp()) so quota_data adjustments are recorded in the same hourly
bucket as the original LogTaskConsumption.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant