`to` — conversion
to converts between the types a rule handles. It is the namespace you reach for whenever a value
came from a document rather than from a typed field — because what a document gives you is text,
and what a rule wants to compare is a number, a date or a truth value.
Twenty-four functions, and three properties hold across every one of them.
Total. Nothing panics and nothing errors. A conversion that cannot happen returns the zero
value, or your explicit default from the *Or forms.
Predicated. Every conversion has an Is* twin, so a rule can ask “is this convertible?”
instead of reading a silent zero as a real zero. This is the difference between a rule that says
“the loan amount is 0” and one that says “the loan amount could not be read”, and on a document
those are not the same finding.
Unwrapping. A value carried inside a result envelope converts as the value it holds, so
to.Num(out.Get("LoanAmount")) works without reaching through the envelope by hand.
rule LargeLoan "flag loans over the threshold" salience 10 { when to.Num(bbox.Doc().Right("Loan Amount").Currency()) > 500000.0 then findings.Warn("LargeLoan", "underwriting", "Loan exceeds 500k"); Retract("LargeLoan");}Numbers
Section titled “Numbers”to.Num(v) → float64
Section titled “to.Num(v) → float64”Reads a number out of document text. Currency symbols, thousands separators, percent signs and
accounting parentheses are all handled: "$1,234.56" is 1234.56, "6.125%" is 6.125, and
"(500.00)" is -500.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read. Text, a number, or an envelope carrying either |
Returns 0 when the value is not a number. That is the trap — use to.IsNum to tell a real
zero from an unreadable value, or to.NumOr to supply a default.
rule InterestRateInRange "the rate must be within the permitted band" { when to.Num(bbox.Doc().Right("Interest Rate").Percent()) > 12.0 then findings.Error("InterestRateInRange", "compliance", "Rate above 12%");}to.NumOr(v, def) → float64
Section titled “to.NumOr(v, def) → float64”to.Num with an explicit fallback.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
def | float64 | Returned when the value does not read as a number |
Reach for this when the absence of a value has a defined meaning in your domain — a fee that is zero when unstated, a count that defaults to one.
rule ApplyDefaultFee "unstated origination fee is zero" { when in.Has("application") then out.Set("fee", to.NumOr(bbox.Doc().Right("Origination Fee").Currency(), 0.0));}to.Int(v) → int64
Section titled “to.Int(v) → int64”Reads a whole number, truncating any fraction — "1,234.9" is 1234, not 1235.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
Truncation rather than rounding is deliberate: a term in months, a count of pages or a number of borrowers is a quantity you are extracting, not a measurement you are rounding.
rule TermTooLong "loan term over 360 months" { when to.Int(bbox.Doc().Right("Loan Term")) > 360 then findings.Warn("TermTooLong", "underwriting", "Term exceeds 30 years");}to.IntOr(v, def) → int64
Section titled “to.IntOr(v, def) → int64”to.Int with an explicit fallback.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
def | int64 | Returned when the value does not read as a whole number |
to.IsNum(v) → bool
Section titled “to.IsNum(v) → bool”Reports whether the value reads as a number. The guard that makes a zero trustworthy.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to test |
rule LoanAmountUnreadable "distinguish a missing amount from a zero one" salience 20 { when !to.IsNum(bbox.Doc().Right("Loan Amount").Currency()) then findings.Critical("LoanAmountUnreadable", "extraction", "Loan amount could not be read"); Complete();}Writing this as to.Num(...) == 0.0 would fire on a genuine zero and report the wrong problem.
to.IsInt(v) → bool
Section titled “to.IsInt(v) → bool”Reports whether the value reads as a whole number. "12.5" is a number but not an integer, so
this is false where to.IsNum is true.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to test |
Use it where a fractional value is itself the error — a count, a term in whole months, a number of units.
rule UnitCountNotWhole "a fractional unit count is a data error" { when to.IsNum(bbox.Doc().Right("Units")) && !to.IsInt(bbox.Doc().Right("Units")) then findings.Error("UnitCountNotWhole", "extraction", "Unit count is not a whole number");}Truth values
Section titled “Truth values”to.Bool(v) → bool
Section titled “to.Bool(v) → bool”Reads a truth value. The affirmative words — yes, y, checked, x, 1 — are true, the
negatives false, and anything unrecognised is false.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
That last clause matters on a form: an unreadable checkbox and an unticked one both come back
false. Where the difference is material, guard with to.IsBool first.
rule EscrowWaived "escrow waiver box is ticked" { when to.Bool(bbox.Doc().Checked("Waive Escrow")) then out.Set("escrowWaived", true); audit.Log("EscrowWaived", "Borrower waived escrow");}to.BoolOr(v, def) → bool
Section titled “to.BoolOr(v, def) → bool”to.Bool with an explicit fallback for unrecognised text.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
def | bool | Returned when the text is neither affirmative nor negative |
to.IsBool(v) → bool
Section titled “to.IsBool(v) → bool”Reports whether the value reads as a truth value at all.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to test |
rule ConsentIllegible "the consent box could not be read either way" salience 15 { when !to.IsBool(bbox.Doc().Right("Consent")) then findings.Error("ConsentIllegible", "extraction", "Consent value is not readable as yes or no");}to.Date(v) → time
Section titled “to.Date(v) → time”Reads a date, trying the format profile’s layouts in order.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
An unreadable value returns the zero time. Test with to.IsDate rather than comparing against
a sentinel date — the zero time is a real time, and comparisons against it succeed in ways you did
not intend.
rule ClosingBeforeApplication "closing cannot precede application" { when to.IsDate(bbox.Doc().Right("Closing Date")) && to.IsDate(bbox.Doc().Right("Application Date")) && time.IsBefore(to.Date(bbox.Doc().Right("Closing Date")), to.Date(bbox.Doc().Right("Application Date"))) then findings.Critical("ClosingBeforeApplication", "consistency", "Closing date is before the application date");}Both IsDate guards are load-bearing: without them, two unreadable dates are two zero times, and
neither is before the other, so the rule silently passes.
to.DateIn(v, layout) → time
Section titled “to.DateIn(v, layout) → time”Reads a date using one explicit layout, for input whose format is known and would be ambiguous under the profile — day-first versus month-first being the usual case.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
layout | string | The layout to use, e.g. "02/01/2006" for day-first |
rule ReadUKDate "this form is day-first regardless of the default profile" { when bbox.Doc().Has("UK Mortgage Application") then out.Set("applicationDate", to.DateIn(bbox.Doc().Right("Date"), "02/01/2006"));}to.IsDate(v) → bool
Section titled “to.IsDate(v) → bool”Reports whether the value reads as a date.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to test |
to.DateStr(v, layout) → string
Section titled “to.DateStr(v, layout) → string”Reads a date from any recognised form and renders it in the given layout — one call in place of a parse-then-format pipeline, with no sentinel date to compare against on failure: an unreadable value renders as the empty string.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to read |
layout | string | The output layout, e.g. "2006-01-02" |
rule NormaliseClosingDate "store the closing date in ISO form" { when to.IsDate(bbox.Doc().Right("Closing Date")) then out.Set("closingDate", to.DateStr(bbox.Doc().Right("Closing Date"), "2006-01-02"));}Text and formatting
Section titled “Text and formatting”to.Str(v) → string
Section titled “to.Str(v) → string”Renders any value as text: numbers without float noise, dates in ISO form, nil as the empty string.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to render |
“Without float noise” is the point — a float that prints as 1234.5600000000001 in most languages
renders here as 1234.56.
to.Fixed(v, decimals) → string
Section titled “to.Fixed(v, decimals) → string”Renders a number with a fixed number of decimals: "1234.5" with 2 becomes "1234.50".
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to render |
decimals | int64 | How many decimal places |
Unreadable values render as the empty string, never as "0.00" — a formatted zero would read
as real data in the output. Halves round away from zero.
rule FormatMonthlyPayment "monthly payment for the summary, to the cent" { when to.IsNum(out.Get("monthlyPayment")) then out.Set("monthlyPaymentDisplay", to.Fixed(out.Get("monthlyPayment"), 2));}to.Grouped(v, decimals) → string
Section titled “to.Grouped(v, decimals) → string”Renders a number with thousands separators: "1234567.5" with 2 becomes "1,234,567.50".
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to render |
decimals | int64 | How many decimal places |
to.Pct(v, decimals) → string
Section titled “to.Pct(v, decimals) → string”Renders a number as a percentage string.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to render |
decimals | int64 | How many decimal places |
The number is not scaled. to.Num("6.125%") is 6.125 and to.Pct(6.125, 2) is "6.13%", so
the pair round-trips. If your value is a fraction — 0.06125 meaning 6.125% — scale it explicitly
before rendering.
rule DisplayRate "render the rate as it appeared on the note" { when to.IsNum(bbox.Doc().Right("Interest Rate").Percent()) then out.Set("rateDisplay", to.Pct(to.Num(bbox.Doc().Right("Interest Rate").Percent()), 3));}Collections
Section titled “Collections”to.List(v) → list
Section titled “to.List(v) → list”Converts a value to a list. A single scalar becomes a one-element list, so a rule can treat “one or many” uniformly; nil stays empty.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to convert |
to.StrList(v) → list of string
Section titled “to.StrList(v) → list of string”Converts every element to text.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to convert |
to.NumList(v) → list of float64
Section titled “to.NumList(v) → list of float64”Converts every element that reads as a number, skipping those that do not — so a stray header row or a blank cell does not silently become a zero in your sum.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to convert |
That skipping behaviour is the reason to prefer it over converting element by element: a column extracted from a table usually carries at least one cell that is not a number.
to.Map(v) → map
Section titled “to.Map(v) → map”Converts a value to a map, or an empty map.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to convert |
Unlike the scalar conversions this does not unwrap envelopes — asking for the map means wanting the envelope itself, metadata included.
to.Rows(v) → list of map
Section titled “to.Rows(v) → list of map”Converts a value to table rows, or an empty list.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to convert |
The usual companion to a table read, where what you want to hand to out.Set is the records.
rule CaptureLineItems "store the line-item table" { when bbox.Doc().Table().Found() then out.Set("lineItems", to.Rows(bbox.Doc().Table().Records()));}Inspection
Section titled “Inspection”to.IsEmpty(v) → bool
Section titled “to.IsEmpty(v) → bool”Reports whether the value carries nothing: nil, blank text, a zero date, or an empty map or list.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to test |
A zero number is not empty. That is the distinction this function exists to preserve — an amount of zero is a fact, and an amount that was never read is not.
rule BorrowerNameMissing "the name field is blank" salience 20 { when to.IsEmpty(bbox.Doc().Right("Borrower Name")) then findings.Critical("BorrowerNameMissing", "completeness", "Borrower name is blank"); Complete();}to.Kind(v) → string
Section titled “to.Kind(v) → string”Names what a value effectively is after unwrapping — the type the conversions will see.
| Parameter | Type | Meaning |
|---|---|---|
v | any | The value to inspect |
Diagnostic. Log it when a conversion surprises you: it answers “what did the engine actually get?” without guessing.
rule DebugAmountType "why is this amount not converting" salience 100 { when !to.IsNum(bbox.Doc().Right("Loan Amount")) then log.Warn("amount did not convert", "kind", to.Kind(bbox.Doc().Right("Loan Amount")), "text", to.Str(bbox.Doc().Right("Loan Amount")));}The pattern worth keeping
Section titled “The pattern worth keeping”Nearly every bug in a conversion-heavy rule is the same shape: a zero or a false or a zero time
being read as data when it means “unreadable”. The namespace is built so the fix is always the
same — guard with the Is* twin, or supply a default with the *Or form. If a rule compares
a converted value against zero, it is usually a rule that should have asked to.IsNum first.
See also
Section titled “See also”- Values — the coercions on a document read, which run before this
- Built-in functions — the full callable surface
- The rule language — what the grammar accepts