Docs·Get started

Symbolic programs

For every question, XERJ Reader-1 writes a small program over your document, and an exact executor runs it. How to read a program, the symbols, the statuses and the confidence.

XERJ Reader-1 (xerj-reader-1, preview) does not write the answer. Its program says where the values are in your document, how to read them, and what to compute. The executor returns a status, a value and the evidence.

Every example on this page is a real run. Nothing is edited except the layout.

Why a program #

The model writes a literal value itself only for a Yes or No (YN), which must point at the sentence that decides it, and for a constant from your question, such as the 12 in “times 12”. Every literal is visible in the program.

How to read a program #

Each answer has the program in two forms. symbolic.program is JSON. symbolic.program_compact is one line for people:

Text
SUB(DATE(“30.11.2026”, "auto"), DATE(“02.11.2026”, "auto")) = 28

Read it from the inside out:

  1. Curly quotes “…” are spans. “30.11.2026” is the exact text of your document at two character positions. The model chose the positions; the text is copied from your document.
  2. Straight quotes "…" are arguments the model wrote, such as a locale ("auto") or a unit ("hours").
  3. Functions read their arguments. DATE(“30.11.2026”, "auto") reads the span as a date: 2026-11-30.
  4. The outer function computes. SUB(a, b) is a minus b. A date minus a date is a number of days.
  5. After = comes the value the executor computed. After → comes a status other than ANSWER, with the reason.

The same program as JSON:

JSON
["SUB", [["SPAN", "d1", 1458, 1468], ["DATE", "auto"]],
        [["SPAN", "d1", 163, 173], ["DATE", "auto"]]]

Worked examples #

Most examples ask questions about one email thread. Here it is in full, so you can check every offset:

Text
Subject: Re: Re: Physio tariff 2027 / October claims

----- Message 1 -----
From: Ruth Ackermann <[email protected]>
To: Partner practices
Date: 02.11.2026 17:40

Dear practice partners,
Tariff update for physiotherapy (code PT-7301):
- session rate CHF 96.00 replaced by CHF 104.50 as of 01.01.2027
- home visits: no longer reimbursed as a separate item from 01.01.2027 (incl. travel surcharge CHF 18.00)
- group sessions: unchanged
- required on every claim: referral copy, diagnosis code, session date, therapist GLN

Ruth Ackermann
Claims Officer, Helvetia Claims Services

----- Message 2 -----
From: Dr. Hanne Solberg <[email protected]>
To: Ruth Ackermann
Date: 06.11.2026 08:05

Ruth,
- is CHF 96.00 still valid for December sessions?
- could you send our Sept/Oct statement? September was 27 claims if I remember right.
- Marco Keller, our billing clerk, asks whether home visits done in December are still billable.

Dr. Hanne Solberg
Practice Manager, Gesundheitszentrum Limmat, Zurich

----- Message 3 -----
From: Ruth Ackermann
To: Dr. Hanne Solberg
Cc: Marco Keller
Date: 09.11.2026 14:22

Hi Dr. Solberg,
- yes, CHF 96.00 applies to all sessions up to 31.12.2026
- December home visits are still billable, incl. the CHF 18.00 surcharge
- September: 27 claims, all approved
- October: 38 claims received, 35 approved, 3 returned (missing referral copy)
- please resend the 3 October claims with referral copies by 30.11.2026

Best,
Ruth

----- Message 4 -----
From: Marco Keller
To: Ruth Ackermann
Date: 10.11.2026 09:11

Thanks Ruth, will resend the 3 claims this week.

1. A value read from one span #

Question: “new session rate from january” (type money)

Text
MONEY(“CHF 104.50”, "auto") = 104.50 CHF
  1. Point at CHF 104.50 (characters 290 to 300).
  2. Read it as money. "auto" means: work out the number format from the document.

Value: {"amount": "104.50", "currency": "CHF"}. Confidence 0.5782. The answer is right, but the model was unsure of one pointer, and the confidence shows it.

2. A number computed from two spans #

Question: “By how many CHF does the session rate go up in 2027?” (type number)

Text
SUB(MONEY(“CHF 104.50”, "auto"), MONEY(“CHF 96.00”, "auto")) = 8.50
  1. Read CHF 104.50 as money: the new rate.
  2. Read CHF 96.00 as money: the old rate.
  3. Subtract. Both are CHF, so the executor allows it.

Value: 8.50. Evidence: the two spans. Confidence 0.7017.

3. A date difference #

Question: “Counting from Ruth's first tariff email, how many days did the practice get to resend the returned claims?” (type number)

Text
SUB(DATE(“30.11.2026”, "auto"), DATE(“02.11.2026”, "auto")) = 28
  1. Read 30.11.2026, the resend deadline in message 3, as a date: 2026-11-30.
  2. Read 02.11.2026, the date of message 1, as a date: 2026-11-02.
  3. Subtract. A date minus a date is a number of days.

Value: 28. Confidence 0.9554. The question never names either date. The program shows which two the model chose.

4. Yes or No, with the sentence that decides it #

Question: “are group sessions changing in 2027” (type yesno)

Text
YN(false, “group sessions: unchanged”) = no
  1. Point at the line that decides the question: group sessions: unchanged.
  2. Judge it: false, so the answer is No.

Value: false. Confidence 0.9358. A YN without a pointer is not allowed, so every Yes or No comes with its sentence.

5. A list #

Question: “what has to be on every claim” (type list)

Text
LIST(“referral copy”, “diagnosis code”, “session date”, “therapist GLN”) = [referral copy; diagnosis code; session date; therapist GLN]

Four spans, one per item. Confidence 0.9939.

6. Not in the document #

Question: “What is the rate per group session under code PT-7301?” (type text)

Text
DECLINE("ABSENT") → ABSENT (declined by the program)

The email says group sessions are unchanged, but never gives their rate. The program declines. Status ABSENT, no value. Confidence 0.9918: here the confidence says how sure the model was of declining.

7. A wrong answer, and how the program shows it #

Question: “what's the session rate for december sessions?” (type money)

Text
MONEY(“CHF 104.50”, "auto") = 104.50 CHF

This is wrong. The document says CHF 96.00 applies to all sessions up to 31.12.2026, so the December rate is CHF 96.00. The model pointed at the 2027 rate. Confidence 0.8268, so a confidence bar alone would not catch it.

The program makes the mistake easy to see: the evidence is CHF 104.50, on the line that ends as of 01.01.2027. A reviewer who looks at the highlighted span sees it is the wrong line. This is why every value comes with its spans: check them when the answer matters.

Statuses #

status says what the executor could do with the program. Only ANSWER has a value.

Status What it means What to do
ANSWER The program ran and gave a value of the field's type. Use the value. Show the evidence where a person checks the result.
ABSENT The document does not say. The program declined. Do not fill the field. Ask another document, or a person.
UNKNOWN A value could not be read or computed. Examples: a number in an unexpected format, a date without a year and nothing to take it from, a division by zero, or a check on the value failed. Read reason. Send ref_date for dates without a year, or ask more precisely.
AMBIGUOUS Two candidates where one was needed: two numbers in one span, two currencies, or a tie in ARGMAX. The executor never picks one. Ask more precisely, or send the part of the document that holds one value.
CONFLICT Different documents give different values. Decide which source wins, or ask a person.
INCOMPLETE Some elements of a list or table had no value, so a sum or count would be wrong. coverage says which. Read coverage. Fix or skip the missing rows yourself.
UNIT_MISMATCH Values in different units or currencies were combined, such as EUR plus CHF. Ask for one currency at a time.
UNSUPPORTED The program returned the wrong kind of value for the field (a date for a number field), or could not run. Try another type, or rephrase the question.
DECLINE Platform status, not from the executor: the program gave an ANSWER, but its confidence was not above your min_confidence. The value is in best_guess. Treat it as no answer. Show the best guess for review only.

Real examples of the statuses other than ANSWER:

Text
AGG("SUM", LIST(MONEY(“EUR 420”, "auto"), MONEY(“CHF 1,150”, "auto"), MONEY(“EUR 96”, "auto"))) → UNIT_MISMATCH (SUM over ['CHF', 'EUR'])
MONEY(“INV-20931 for 48,200 EUR”, "auto") → AMBIGUOUS (2 numbers in 'INV-20931 for 48,200  ')
MONEY(“48,200.00 EUR”, "nl-NL") → UNKNOWN ('48,200.00  ' is not a nl-NL number)
SUB(DATE(“1 Dec 2025”, "auto"), DUR(“9 yrs”)) → UNSUPPORTED (program returns Date, field is number)

Confidence #

The confidence comes from the model, not from the executor. It is computed from the program the model wrote:

Text
confidence = (lowest pointer probability) × (mean token probability)

symbolic.confidence has the parts: ptr_min, ptr_mean, tok_min, tok_mean, the counts n_ptr and n_tok, and conf, the product. The answer's confidence is conf, rounded to 4 decimals.

What it does and does not tell you:

Field types #

The program must return a value of the field's type. Send type with the question, or leave it out and the type is inferred from the wording (symbolic.type_inferred is then true).

type Value
text A string, copied from the document.
number A decimal string, such as "8.50". Also takes an amount of money or a duration and returns its number.
money {"amount": "104.50", "currency": "CHF"}, with an ISO 4217 currency code.
date "2026-11-30".
time "07:40", or "07:58:12" with seconds.
duration {"amount": "75", "unit": "minutes"}.
pct A fraction: 19% is "0.19".
yesno, bool true or false.
list A list of values.
choice One of the options you send.
id {"kind": "iban", "value": "DE89370400440532013000", "checksum": true}, for IBAN, LEI, card, ISIN and GTIN, with the checksum verdict.

A date without a year, such as 30 November, takes the year of the nearest full date before it in the document. If there is none that the executor can read, it takes the year of ref_date, or of today's date when you send none. Send ref_date (YYYY-MM-DD) when your documents have dates without a year: an example.

Symbol reference #

These are the symbols the reader writes today. Each entry gives the arguments, what the symbol does, and a short example with the executor's real output.

Address: where the values are #

SPAN(doc, start, end) → text. The characters start to end (end exclusive) of a document. Every value starts here.
“INV-2026-0091” = INV-2026-0091

Decode: how to read a span #

NUM(text, locale) → number. Reads exactly one number. "auto" works out the format from the document. Signs, brackets for negatives and number words count. Two numbers in the span is AMBIGUOUS; a wrong format is UNKNOWN, never a partial read.
NUM(“1'200 kg”, "auto") = 1200 and NUM(“(1,200)”, "auto") = -1200

MONEY(text, locale, currency) → money. Amount and currency. The currency comes from an ISO code or a symbol in the text, or from the third argument when the text has none.
MONEY(“EUR 3,324.86”, "auto") = 3324.86 EUR

PCT(text, locale) → percent. The value is a fraction.
PCT(“19%”, "auto") = 0.19

DATE(text, format) → date. Many formats and languages. A date without a year takes it from the document or ref_date.
DATE(“3 November 2026”, "auto") = 2026-11-03 and DATE(“Friday 20 November”, "auto") = 2026-11-20

TIME(text) → time. 10:55, 6.30pm, 18h00, noon. A bare number is not a time.
TIME(“07:40”) = 07:40

DUR(text, unit) → duration. Seconds to years, in several languages; parts are added (1 h 15 min is 75 minutes). No unit in the text and none given is UNKNOWN.
DUR(“1 h 15 min”) = 75 minutes and DUR(“Net 30”) = 30

Compute #

SUB(a, b) → a minus b. A date minus a date is days. A time minus a time is minutes. Add "business_days" to count Monday to Friday only.
SUB(TIME(“08:15”), TIME(“07:40”)) = 35

MUL(a, n) → same kind. A number, amount or duration times a number. Money times money is UNSUPPORTED.
MUL(DUR(“1 h 15 min”), LIT("Num", "4")) = 300

DIV(a, b) → a divided by b. Money divided by a number is money; money divided by money in one currency is a number. Division by zero is UNKNOWN.
DIV(MONEY(“EUR 384.00”, "auto"), NUM(“120”, "auto")) = 3.20 EUR

ROUND(x, places, mode) → same kind. Default half up. UP rounds away from zero, DOWN toward zero.
ROUND(DIV(LIT("Num", "800"), NUM(“250”, "auto")), 0, "UP") = 4

CONVERT(x, unit) → same kind. Time units, and mass, length, volume, area, energy and data units. Money needs the rate as the third argument; there is no built-in exchange rate.
CONVERT(MUL(DUR(“1 h 15 min”), LIT("Num", "4")), "hours") = 5 and CONVERT(NUM(“1'200 kg”, "auto"), "t") = 1.2

Several values #

LIST(a, b, …) → set. A set from separate places in the document.
LIST(“CHF 12”, “CHF 7”) = [CHF 12; CHF 7]

PAIR(label, value) → pair. A label with its value, so ARGMAX can return the label.
ARGMIN(LIST(PAIR(“standard”, MONEY(“CHF 12”, "auto")), PAIR(“reduced”, MONEY(“CHF 7”, "auto")))) = reduced

ARGMAX(set, value, result) → element. The element with the largest value, or what result reads from it. A tie is AMBIGUOUS.
ARGMAX(LIST(PAIR(“extract_orders”, DUR(“15 min”)), PAIR(“transform_orders”, DUR(“37 min”)))) = transform_orders

Answers #

YN(true or false, span, …) → yes/no. A Yes or No judged from the sentences it points at. At least one span is required.
YN(false, “Parking on site is no longer free for visitors”) = no and YN(true, “IBAN: DE89 3704 0044 0532 0130 00”) = yes

DECLINE(status, span, …) → no value. Declines with ABSENT, UNKNOWN, UNSUPPORTED, AMBIGUOUS or CONFLICT. Optional spans show where the model looked.
DECLINE("ABSENT") → ABSENT and DECLINE("ABSENT", “Parking on site is no longer free for visitors”) → ABSENT

The symbolic object #

Every answer carries a symbolic object: the full record of the run. The main keys:

Key What it holds
program The program as JSON.
program_compact The one-line form, with the value or the status.
spans Every span in the program: {doc, start, end, quote}.
status, value, value_text, reason The result. value_text is the value as one line of text; reason explains a status other than ANSWER.
evidence The spans the value came from: {doc_id, char_start, char_end, quote, exact, …}.
checks The result of each check in the field definition, if any.
coverage, withheld_value, candidates Details for INCOMPLETE, for a value withheld by a failed check, and for AMBIGUOUS / CONFLICT.
field, type, type_inferred, typecheck The field as it was asked, and the type the program returns.
confidence The parts of the confidence, above.
n_prompt_tokens, n_gen_tokens, finished, ms Size and time of the run.

The Answers reference shows a complete one.