功能增加

This commit is contained in:
andy committed 2026-09-11 15:33:18 +08:00
1 parent b340fc7ad7
commit f1993eb388
69 files changed
+2243 -162

No files matched your search

+121 -3
View File
@@ -24,9 +24,9 @@ type FencedSettlementStateWriter interface {
WriteBillingFenced(context.Context, string, json.RawMessage, jobs.Status, string) error
}
// SettlementProcessor adds actual-usage settlement for Seedance videos and
// Seedream layer decomposition. Other providers and non-successful advances
// pass through.
// SettlementProcessor adds actual-usage settlement for Seedance and MiniMax
// videos plus Seedream layer decomposition. Other providers and unsuccessful
// advances pass through.
type SettlementProcessor struct {
inner jobs.Processor
ledger SettlementLedger
@@ -52,6 +52,9 @@ func (p *SettlementProcessor) Advance(ctx context.Context, job jobs.Job) (jobs.J
if advanced.Provider == "seedream" && seedreamLayerRequest(advanced.RequestPayload) {
return p.settleSeedreamLayers(ctx, advanced)
}
if advanced.Provider == "minimax" && billing.IsMinimaxH3BillingModel(advanced.ReqKey) {
return p.settleMinimaxH3(ctx, advanced)
}
if advanced.Provider != "seedance" {
return advanced, nil
}
@@ -139,6 +142,85 @@ func (p *SettlementProcessor) Advance(ctx context.Context, job jobs.Job) (jobs.J
return p.write(ctx, advanced, charge.raw)
}
func (p *SettlementProcessor) settleMinimaxH3(ctx context.Context, job jobs.Job) (jobs.Job, error) {
var charge billingState
if json.Unmarshal(job.Billing, &charge) != nil {
return jobs.Job{}, errors.New("settle MiniMax H3 generation charge")
}
status, _ := charge.raw["settlementStatus"].(string)
if status == "settled" || status == "estimated" {
return job, nil
}
var use usageContext
if len(job.UsageContext) != 0 && json.Unmarshal(job.UsageContext, &use) != nil {
return jobs.Job{}, errors.New("settle MiniMax H3 generation charge")
}
quotaExempt := charge.QuotaExempt || use.Source == "platform" && use.Role == "super_admin"
chargeReady := charge.Status == "charged" || quotaExempt && charge.Status == "not_charged"
if !chargeReady || !quotaExempt && use.OrganizationID == "" {
return job, nil
}
usage := minimaxH3Usage(job.ResponsePayload)
if usage.outputSeconds <= 0 {
charge.raw["settlementStatus"] = "estimated"
charge.raw["settlementReason"] = "provider_usage_unavailable"
charge.raw["settledAt"] = p.now().UTC().Format(time.RFC3339Nano)
return p.write(ctx, job, charge.raw)
}
resolution := minimaxResolution(charge.raw)
actualAmount, err := billing.CalculateMinimaxH3ActualAmountFen(billing.MinimaxH3ActualAmountInput{
Resolution: resolution, OutputSeconds: usage.outputSeconds, InputVideoSeconds: usage.inputSeconds,
InputImageCount: usage.inputImageCount, MarkupMultiplier: numberOrZero(charge.raw["markupMultiplier"]),
})
if err != nil {
return jobs.Job{}, errors.New("settle MiniMax H3 generation charge")
}
delta := actualAmount - charge.AmountFen
settledAt := p.now().UTC()
var settlementLedgerID string
if delta != 0 && !quotaExempt {
if p.ledger == nil {
return jobs.Job{}, errors.New("settle MiniMax H3 generation charge")
}
description := capabilityLabel(job.Capability) + "实际用量差额退回"
if delta > 0 {
description = capabilityLabel(job.Capability) + "实际用量补扣"
}
posting, postErr := p.ledger.Settle(ctx, billing.SettlementRequest{
OrganizationID: use.OrganizationID, AccountID: use.AccountID, JobID: job.ID,
DeltaFen: delta, Description: description,
Metadata: map[string]any{
"operation": "minimax_h3_actual_settlement", "reservedAmountFen": charge.AmountFen,
"actualAmountFen": actualAmount, "outputSeconds": usage.outputSeconds,
"inputVideoSeconds": usage.inputSeconds, "inputImageCount": usage.inputImageCount,
"resolution": resolution, "model": job.ReqKey,
},
})
if postErr != nil {
return jobs.Job{}, errors.New("settle MiniMax H3 generation charge")
}
settlementLedgerID = posting.LedgerID
if !posting.CreatedAt.IsZero() {
settledAt = posting.CreatedAt.UTC()
}
}
charge.raw["amountFen"] = actualAmount
charge.raw["settlementStatus"] = "settled"
charge.raw["settledAt"] = settledAt.Format(time.RFC3339Nano)
if settlementLedgerID != "" {
charge.raw["settlementLedgerEntryId"] = settlementLedgerID
} else {
delete(charge.raw, "settlementLedgerEntryId")
}
charge.raw["providerUsage"] = map[string]any{
"model": job.ReqKey, "resolution": resolution, "outputSeconds": usage.outputSeconds,
"inputVideoSeconds": usage.inputSeconds, "inputImageCount": usage.inputImageCount,
"videoPriceFenPerSecond": billing.MinimaxH3VideoPriceFenPerSecond(resolution),
}
return p.write(ctx, job, charge.raw)
}
func (p *SettlementProcessor) settleSeedreamLayers(ctx context.Context, job jobs.Job) (jobs.Job, error) {
var charge billingState
if json.Unmarshal(job.Billing, &charge) != nil {
@@ -242,6 +324,34 @@ func seedanceCompletionTokens(payload json.RawMessage) int64 {
return value
}
type minimaxUsage struct {
outputSeconds int64
inputSeconds int64
inputImageCount int64
}
func minimaxH3Usage(payload json.RawMessage) minimaxUsage {
var response struct {
Usage map[string]any `json:"usage"`
}
if json.Unmarshal(payload, &response) != nil {
return minimaxUsage{}
}
outputSeconds, _ := nonNegativeInteger(response.Usage["outputSeconds"])
inputSeconds, _ := nonNegativeInteger(response.Usage["inputSeconds"])
inputImageCount, _ := nonNegativeInteger(response.Usage["inputImageCount"])
return minimaxUsage{outputSeconds: outputSeconds, inputSeconds: inputSeconds, inputImageCount: inputImageCount}
}
func minimaxResolution(snapshot map[string]any) string {
parameters, _ := snapshot["parameters"].(map[string]any)
resolution, _ := parameters["resolution"].(string)
if strings.EqualFold(strings.TrimSpace(resolution), "2K") {
return "2K"
}
return "768P"
}
func seedreamLayerRequest(payload json.RawMessage) bool {
var request struct {
Settings map[string]any `json:"settings"`
@@ -341,4 +451,12 @@ func integer(value any) (int64, bool) {
return int64(number), true
}
func nonNegativeInteger(value any) (int64, bool) {
number, ok := value.(float64)
if !ok || number < 0 || number != float64(int64(number)) {
return 0, false
}
return int64(number), true
}
var _ jobs.Processor = (*SettlementProcessor)(nil)