Skip to main content

Extract facts and calculate

Use this pattern when quantities and prices arrive inside a message rather than in predefined fields. The model extracts the variables, works out the calculation and returns the answer. OPA checks three things:

  1. The copied reference and numeric literals appear verbatim in the original text.
  2. Each calculation uses a supported operator and references extracted variables or earlier results. All extracted variables and calculation steps contribute to the final answer.
  3. Every intermediate result is correct, and the final answer matches the last step.

For example: “For quote 'QT-07/ab-19', we need 6 notebooks at 350 cents each. Delivery adds 200 cents once for the whole order. What is the total payable in cents?” Nothing supplies those numbers separately.

Supply the text, schema and policy​

The input file contains one JSON string: the original message. The schema describes the information to extract. Numeric values are copied as strings to preserve their spelling; Rego converts them to numbers for calculation. This example uses amounts written as whole cents.

The formula is a short sequence of arithmetic steps. step_1 refers to the first result, step_2 to the second, and so on. The model chooses the operators and their order; there is no list of complete formulas to choose from. OPA permits addition, subtraction, multiplication and division, rejects division by zero, and checks that every reference is available at that step. This example requires all three extracted variables to be used and disallows discarded intermediate steps. Combining three inputs requires at least two binary operations, so the schema sets minItems: 2; maxItems: 4 bounds the calculation's length.

source-calculation.input.json
"For quote 'QT-07/ab-19', we need 6 notebooks at 350 cents each. Delivery adds 200 cents once for the whole order. What is the total payable in cents?"
source-calculation.schema.json
{
"type": "object",
"properties": {
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"description": "Extract the quote reference from the input text. Copy the complete text between single quotes exactly, preserving case and punctuation, without the surrounding quotes."
},
"variables": {
"type": "object",
"description": "Extract the quantities and amounts from the prose. Copy each numeric literal verbatim as a string; identify its role from the wording. Do not invent values. Amounts are written as whole cents.",
"properties": {
"quantity": {
"type": "string",
"pattern": "^[0-9]+$",
"maxLength": 12,
"description": "Copy the number of items being purchased."
},
"unit_price_cents": {
"type": "string",
"pattern": "^[0-9]+$",
"maxLength": 12,
"description": "Copy the price for one item before any extra charge or discount, in cents."
},
"adjustment_cents": {
"type": "string",
"pattern": "^[0-9]+$",
"maxLength": 12,
"description": "Copy the stated charge or discount amount in cents. The formula determines whether to add or subtract it and whether it applies once or per item."
}
},
"required": [
"quantity",
"unit_price_cents",
"adjustment_cents"
],
"additionalProperties": false
},
"formula": {
"type": "array",
"minItems": 2,
"maxItems": 4,
"description": "Build the arithmetic calculation that answers the question, in order. Each step applies its operator to left and right, then records the exact result. Reference the extracted variable names or an earlier result: step_1 is the first step's result, step_2 the second, and so on. Choose the operations and their order from the wording. Do not reference the current step or a future step. Use all three extracted variables. Every step before the final step must feed a later step; return the complete total, not a subtotal.",
"items": {
"type": "object",
"properties": {
"operator": {
"type": "string",
"enum": [
"add",
"subtract",
"multiply",
"divide"
]
},
"left": {
"type": "string",
"enum": [
"quantity",
"unit_price_cents",
"adjustment_cents",
"step_1",
"step_2",
"step_3"
]
},
"right": {
"type": "string",
"enum": [
"quantity",
"unit_price_cents",
"adjustment_cents",
"step_1",
"step_2",
"step_3"
]
},
"result": {
"type": "number",
"description": "The exact arithmetic result of this step, without rounding."
}
},
"required": [
"operator",
"left",
"right",
"result"
],
"additionalProperties": false
}
},
"result_cents": {
"type": "integer",
"description": "The final step's result, in whole cents."
}
},
"required": [
"reference",
"variables",
"formula",
"result_cents"
],
"additionalProperties": false
}
source-calculation.rego
package source_calculation

default allow := false

calculate("add", a, b) := a + b
calculate("subtract", a, b) := a - b
calculate("multiply", a, b) := a * b
calculate("divide", a, b) := a / b if { b != 0 }

used(reference) if {
some step in input.output.formula
reference in {step.left, step.right}
}

allow if {
contains(input.input, sprintf("'%s'", [input.output.reference]))

numbers := regex.find_n(`[+-]?\b[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b`, input.input, -1)
every _, literal in input.output.variables {
literal in numbers
}
variables := {name: to_number(literal) |
some name, literal in input.output.variables
}
every name, _ in variables { used(name) }

every i, step in input.output.formula {
earlier := {sprintf("step_%d", [j + 1]): previous.result |
some j, previous in input.output.formula
j < i
}
operands := object.union(variables, earlier)
step.result == calculate(step.operator, operands[step.left], operands[step.right])
}
last := count(input.output.formula) - 1
every i, _ in array.slice(input.output.formula, 0, last) {
used(sprintf("step_%d", [i + 1]))
}
input.output.result_cents == input.output.formula[last].result
}

Extract and calculate​

Save the three files together and set your API key. No separate state or permission context is needed. In Rego, input.input is the original message and input.output is the proposed answer.

Before you run this example

Call https://api.blatent.ai from your server. Set your API key in the same terminal where you run the example:

export BLATENT_API_KEY='YOUR_SERVICE_KEY'

Python requires Python 3.10+ and HTTPX:

python -m pip install "httpx>=0.28,<1"

TypeScript requires Node.js 24+ and uses its built-in fetch. The cURL examples require curl and jq.

Save the displayed files and the selected client in one directory. Run the client there with python example.py, node example.ts or bash example.sh. Downloads use the example's name in place of example.

import json
import os
from pathlib import Path
import httpx

request = {
"policy": {
"engine": "opa",
"entrypoint": "data.source_calculation.allow",
"modules": {
"source-calculation.rego": Path("source-calculation.rego").read_text(encoding="utf-8"),
},
},
"policy_max_retries": 8,
"input": json.loads(Path("source-calculation.input.json").read_text(encoding="utf-8")),
"schema": json.loads(Path("source-calculation.schema.json").read_text(encoding="utf-8")),
}
with httpx.Client(
base_url="https://api.blatent.ai",
headers={"Authorization": f"Bearer {os.environ['BLATENT_API_KEY']}"},
timeout=195,
) as client:
response = client.post("/v1/resolve", json=request)
response.raise_for_status()
print(json.dumps(response.json()["output"]))

Illustrative output:

{
"reference": "QT-07/ab-19",
"variables": {"quantity": "6", "unit_price_cents": "350", "adjustment_cents": "200"},
"formula": [
{"operator": "multiply", "left": "quantity", "right": "unit_price_cents", "result": 2100},
{"operator": "add", "left": "step_1", "right": "adjustment_cents", "result": 2300}
],
"result_cents": 2300
}

What is enforced​

Proposed answerPolicy result
Exact source literals and 6 × 350 + 200 = 2300Allowed
Reference changed to QT-07/AB-19 or shortened to QT-07Denied: the complete quoted reference is absent
Quantity changed to 8, or price shortened from 350 to 35Denied: that numeric literal is absent
An unknown variable, unsupported operator, self-reference or future referenceDenied
A subtotal that leaves out the adjustment, or a discarded calculation stepDenied
Incorrect intermediate calculation or final totalDenied
Exact literals, but a discount calculation instead of a delivery chargeAllowed if the arithmetic is correct; the interpretation is wrong

OPA verifies literal evidence, valid calculation steps and arithmetic. It does not understand which number represents the unit price, or whether the wording calls for addition or subtraction. Those remain model decisions. If your application already knows a required business rule, enforce that too.

This policy deliberately checks whole numeric literals, so 35 cannot be justified by 350. It does not translate number words, currencies or units. The final amount must be a whole number of cents; no rounding rule is implied.

If a candidate fails a check, Struct can search for an alternative within policy_max_retries. If it cannot find a compliant answer, it returns 422 POLICY_SEARCH_EXHAUSTED without output.

Observed results​

We tested three messages against Struct using this schema and policy. Each message contained its reference, quantities and prices only in prose, with different wording and values.

Message describesConstructed calculationResult in cents
6 notebooks at 350 cents, plus 200 cents delivery for the order6 × 350 + 2002300
8 folders at 240 cents, with 125 cents off the order8 × 240 − 1251795
4 mugs at 475 cents, plus 30 cents bagging per mug(475 + 30) × 42020

All three extracted the expected literals, constructed a complete calculation and passed the policy on the first attempt. No alternative search was needed with this schema. Separate policy tests confirmed that the invalid cases above are rejected.