Budgets, pricing, and cache¶
Hard budgets¶
Pass budget to read() or execute() to set a hard ceiling. Before each
provider attempt, docgate compares the provider's one-page estimate to the
remaining budget. It does not start an attempt that would exceed the ceiling.
from decimal import Decimal
result = docgate.read(
"bundle.pdf",
providers=[vision],
budget=Decimal("0.10"),
)
assert result.receipt.spent <= Decimal("0.10")
The default on_budget_exceeded="stop" returns partial results. Selected pages
that were not attempted are recorded as BUDGET_EXHAUSTED. Pass
on_budget_exceeded="raise" to execute() when the application should fail
instead; it raises RuntimeError after the budget condition is reached.
Budgeting is based on the provider's quoted estimate, not a promise of a provider's eventual invoice. Cache hits cost zero to the docgate receipt.
Record pricing provenance¶
Provider pricing is supplied by your application because commercial pricing can
change. PricingProfile preserves the per-page value, the verification date,
and an optional source in each accepted provider CostQuote.
from datetime import date
from decimal import Decimal
import docgate
pricing = docgate.PricingProfile(
per_page=Decimal("0.004"),
verified_on=date(2026, 8, 30),
source="https://provider.example/pricing",
)
ocr = docgate.MistralOcrProvider(api_key="...", pricing=pricing)
Use receipt.spent (also exposed as estimated_spend) and receipt.quotes
for the run-level record. A quote is added for a successful provider response,
including one that the quality gate later rejects before fallback.
SQLite cache¶
SQLiteCache persists successful non-empty transcriptions locally.
import docgate
cache = docgate.SQLiteCache(".docgate-cache.db")
result = docgate.read("scan.pdf", providers=[vision], cache=cache)
The execution key combines the SHA-256 hash of the exact rendered PNG bytes
with the provider's non-secret cache_identity. It does not use a document
path or filename. Empty, malformed, and failed responses are not cached. This
is why a cache hit is safe to treat as a completed page but a blank provider
response should be handled by the provider ladder instead.