Python cost cap for a mixed media job: round up, then refuse

A 25-line Python estimator with Decimal prices that rounds the total up to the cent, as Sume's reservation does, and exits before submit if it is over your cap.

5 min readSume
All posts

Sum the list prices of every call in a job with Decimal, round the total up to the cent, and refuse to submit if it passes your cap. For one 8-second Wan 3.0 clip at 720p, three 2K Nano Banana 2.1 stills, a 900-character voiceover and a one-minute render, the exact total is 1.59275 dollars, which rounds up to $1.60. That matches how Sume's catalog describes reservation: the estimate is stored in USD micros and rounded up to cents for balance compatibility.

The estimator

The price table holds four lines from the Sume catalog, read 2026-10-09. Keep the table in one place and regenerate it from GET /v1/catalog rather than copying it into many scripts. The script runs as is.

from decimal import Decimal as D, ROUND_CEILING

PRICE = {  # USD, Sume list prices read from /v1/catalog
    "wan-3.0:720p": D("0.125"),         # per output second
    "nano-banana-2.1:2k": D("0.15"),    # per image
    "tts": D("0.0475"),                 # per 1,000 characters
    "render": D("0.10"),                # per ceil(output minute)
}

def estimate(plan, cap):
    total = D(0)
    for kind, qty in plan:
        total += PRICE[kind] * qty
    total = total.quantize(D("0.01"), rounding=ROUND_CEILING)
    if total > cap:
        raise SystemExit(f"refuse: {total} is over the {cap} cap")
    return total

plan = [("wan-3.0:720p", 8), ("nano-banana-2.1:2k", 3),
        ("tts", D(900) / 1000), ("render", 1)]
print(estimate(plan, D("2.00")))

The arithmetic it performs

Each line multiplies a unit price by a quantity. The voiceover quantity is 900 / 1000 = 0.9 thousand characters.

Estimate for the example plan (Sume list prices, catalog as of 2026-10-09)
LineMathCost
Wan 3.0 720p8 x 0.125$1.00
Nano Banana 2.1 2K3 x 0.15$0.45
TTS0.9 x 0.0475$0.04275
Timeline render1 x 0.10$0.10
Exact total$1.59275
Rounded up to the cent$1.60

Per-job and per-day caps

Use two caps. A per-job cap catches a bug in one request, such as a duration of 120 instead of 12. A daily cap catches a loop. With the $1.60 job above, a $25.00 daily cap allows 15 jobs (25 / 1.60 = 15.6, rounded down).

Add the daily total to a small file or database keyed by date, and compare the sum of today's estimates plus the new one with the cap. Do it before the submit call, never after.

Test the estimator with a case you can check by hand: one render minute alone must return 0.10, and ten thousand characters of TTS must return 0.48 (10 x 0.0475 = 0.475, rounded up). If a Sume price changes, those tests fail and tell you to update the table.

Why refuse on the client

The server will also refuse. Sume returns 402 insufficient_credits when it cannot reserve the estimate from the balance, and 429 queue_full when the workspace is out of accepted-job capacity. A local cap catches a different mistake: a loop that is allowed by the balance but not by your budget. With a $2.00 cap the script above passes; change the clip to 12 seconds and the total is $2.10 (12 x 0.125 = 1.50, plus 0.45, 0.04275, 0.10 = 2.09275, rounded up), and it exits with a refusal.

The script does not submit anything. Put the call to your submit function after estimate() returns, and pass an Idempotency-Key so a retry is safe.

Extending the table

Adding a line is a one-row change. Keep the keys in a model:size form, and quantities in the unit of the price.

  • Per-second video: quantity is output seconds.
  • Per-image: quantity is image count.
  • TTS: quantity is characters divided by 1,000.
  • Timeline render: quantity is the billed minutes, ceil(seconds / 60) with a minimum of 1.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume