Skip to content

Receipt extraction

Turn receipts and invoices into typed records, with checks that catch inconsistent totals.

Request

{
  "system": "You extract numbers from receipts. Write numbers without digit separators.",
  "context": "PURCHASE RECEIPT\nStore: Sunrise Cafe\nSubtotal: 96.000\nDiscount: 6.000\nService Charge: 9.000\nVAT: 9.900\nTOTAL: 118.900\nPayment Method: QRIS",
  "schema": {
    "subtotal": {"type": "number", "integer": true, "max_digits": 8},
    "discount": {"type": "number", "integer": true, "max_digits": 8},
    "service":  {"type": "number", "integer": true, "max_digits": 8},
    "vat":      {"type": "number", "integer": true, "max_digits": 8},
    "total": {
      "type": "number", "integer": true, "max_digits": 8,
      "instruction": "Final TOTAL written on the receipt.",
      "on": [
        {"when": {"ne_expr": "subtotal - discount + service + vat", "tol": 1}, "do": "return", "max": 1},
        {"when": {"ne_expr": "subtotal - discount + service + vat", "tol": 1}, "do": "review"}
      ]
    }
  }
}

This receipt is misprinted on purpose: the items add up to 108.900 but the printed total is 118.900.

What happens

  1. All five numbers are read in parallel.
  2. Code checks total == subtotal - discount + service + vat. It does not match, so the request starts over once (return) with the expected value in the prompt.
  3. The model reads 118.900 again, because that is what the document says.
  4. The second rule flags total for review.

"result": {"subtotal": 96000, "discount": 6000, "service": 9000, "vat": 9900, "total": 118900},
"needs_review": ["total"],
"flagged": [{"field": "total", "value": 118900,
  "reason": "value 118900 matched ne_expr subtotal - discount + service + vat (expected subtotal - discount + service + vat = 108900)"}],
"trace": [{"field": "total", "do": "return"}, {"field": "total", "do": "review"}] ```

With a correct receipt (TOTAL 108.900) the request passes in one round with no flags.

Variations

  • Names and document numbers: add {"type": "string", "extract": true} fields so they are copied verbatim.
  • Unusual formats such as "56k": say it in the instruction ("56k = 56000") and add a range check ({"when": {"lt": 1000}, "do": "review"}).
  • Measure on your own receipts with evaluation.

Playground presets: 1–7 (basics), 20–22 (accuracy tools).