Use Decimal, not float, to reconcile Sume video costs to the cent

Sume bills list x 1.25 and rounds each video job up to the cent. Python Decimal with ROUND_CEILING reproduces the bill exactly, where floats drift by a cent.

5 min readSume
All posts

Use Python's decimal.Decimal with ROUND_CEILING to estimate a Sume video bill: multiply the provider list price per second by 1.25 and by the seconds, then round up to the cent for each job. Floats can land a hair on the wrong side of a rounding boundary and move a job by a cent, which becomes a reconciliation mystery at volume.

When the Sora video models went away on 2026-09-24, per OpenAI's deprecations page, many teams rebuilt their cost reports from scratch. A report that cannot match the invoice to the cent is not a report, it is a guess.

The billing rule

The repo docs say Sume bills provider list times 1.25, rounded up to the cent for each job. List prices come from the model docs: Omni at 720p is $0.10 a second, Wan 3.0 at 1080p is $0.20, and minimax-h3 at 768p is $0.06. A five-second Omni clip is therefore 5 x 0.10 x 1.25 = 0.625, which bills as $0.63.

The ceiling applies to each job, then you sum. Summing exact values first and rounding at the end gives a different, smaller total than the invoice.

Three jobs, exact versus billed (list x 1.25, read 2026-10-05)
JobArithmeticExactBilled
Omni 720p, 5 s0.10 x 1.25 x 5$0.625$0.63
Wan 3.0 1080p, 7 s0.20 x 1.25 x 7$1.75$1.75
minimax-h3 768p, 9 s0.06 x 1.25 x 9$0.675$0.68

Reproducing it

The script builds each price from strings, never from float literals, so 0.10 is exactly one tenth. It multiplies, then quantizes to two places with ROUND_CEILING. The three jobs bill $0.63, $1.75 and $0.68, totaling $3.06, while the exact sum is $3.05, a cent apart. The last line prints a classic float comparison, which is False, as a reminder of why the table is built from strings.

from decimal import Decimal, ROUND_CEILING

LIST = {("gemini-omni-flash-1.1", "720p"): Decimal("0.10"),
        ("wan-3.0", "1080p"): Decimal("0.20"),
        ("minimax-h3", "768p"): Decimal("0.06")}
MARGIN = Decimal("1.25")

def bill(model, res, seconds):
    exact = LIST[(model, res)] * MARGIN * seconds
    return exact, exact.quantize(Decimal("0.01"), rounding=ROUND_CEILING)

if __name__ == "__main__":
    total = Decimal(0)
    for model, res, s in [("gemini-omni-flash-1.1", "720p", 5), ("wan-3.0", "1080p", 7),
                          ("minimax-h3", "768p", 9)]:
        exact, billed = bill(model, res, s)
        total += billed
        print(f"{model} {res} {s}s exact {exact} billed {billed}")
    print("total", total)
    print("float check:", 0.1 + 0.2 == 0.3)

Where to apply it

Use it for the estimate you show before submit, and for the expected value you compare against the cost field on each completed job. The poll response carries usage.cost, and a mismatch larger than a cent is worth a look: the wrong resolution in your table is the usual cause.

Store money as integer cents or as strings in your database, not as floating-point columns. Convert to Decimal at the edges.

  • Quantize per job, then sum.
  • Keep the rate table in one module, with the date you read it.
  • Seedance bills per 1,000 video tokens, so do not force it into this per-second table.

A test worth writing

Add a unit test with the three jobs above and assert the billed values $0.63, $1.75 and $0.68 and the total of $3.06. When prices or the margin change, the test fails and tells you to update the table. That one test protects every dashboard number downstream from a silent change.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume