Skip to content
Talk to our solutions team

`num`, `math` and `time`

Three small namespaces: num for range tests and numeric bridging, math for arithmetic, time for dates.

Whether val is between min and max, inclusive. Whole numbers.

ParameterTypeMeaning
valint64The value to test
minint64Lower bound, inclusive
maxint64Upper bound, inclusive
rule TermWithinRange "loan term between 12 and 360 months" {
when
!num.Between(to.Int(bbox.Doc().Right("Loan Term")), 12, 360)
then
findings.Error("TermWithinRange", "underwriting", "Term outside the permitted 12–360 months");
}

The same for floats — and the one to use for money, rates and ratios.

ParameterTypeMeaning
valfloat64The value to test
minfloat64Lower bound, inclusive
maxfloat64Upper bound, inclusive
rule RateWithinBand "rate must sit between 2% and 12%" {
when
!num.BetweenF(to.Num(bbox.Doc().Right("Interest Rate").Percent()), 2.0, 12.0)
then
findings.Error("RateWithinBand", "compliance", "Rate outside the 2–12% band");
}

num.ToInt64(v) → int64 · num.ToFloat64(v) → float64

Section titled “num.ToInt64(v) → int64 · num.ToFloat64(v) → float64”

Bridge any numeric value into the type the language dispatches on.

ParameterTypeMeaning
vanyThe value to convert

For values that came from a document, prefer to.Int and to.Num — they read currency symbols, separators and accounting parentheses, which these do not.

Every function here takes and returns floats.

ParameterTypeMeaning
xfloat64The value

The tolerance idiom: compare a difference against a threshold rather than testing equality on values that were read off a page.

rule PaymentsRecomputeCorrectly "the stated total matches the sum of parts" {
when
math.Abs(to.Num(out.Get("statedTotal")) - to.Num(out.Get("computedTotal"))) > 0.01
then
findings.Error("PaymentsRecomputeCorrectly", "consistency",
strings.Sprintf("Stated total %s does not match computed %s",
to.Fixed(out.Get("statedTotal"), 2), to.Fixed(out.Get("computedTotal"), 2)));
}

A cent of tolerance is deliberate: two figures rounded independently on the same document routinely differ in the last place, and a rule that fires on that is noise.

math.Min(a, b) → float64 · math.Max(a, b) → float64

Section titled “math.Min(a, b) → float64 · math.Max(a, b) → float64”
ParameterTypeMeaning
a, bfloat64The two values

Rounds to decimals places.

ParameterTypeMeaning
xfloat64The value
decimalsint64How many decimal places — a bare integer here

Note the mixed types: x needs its decimal point, decimals must not have one.

math.Floor(x) → float64 · math.Ceil(x) → float64

Section titled “math.Floor(x) → float64 · math.Ceil(x) → float64”
ParameterTypeMeaning
xfloat64The value

part as a percentage of whole.

ParameterTypeMeaning
partfloat64The portion
wholefloat64The total

Returns a percentage, not a fraction: math.Percent(25.0, 100.0) is 25, not 0.25. That matches to.Pct, which also does not scale.

rule LoanToValue "LTV above 80% requires mortgage insurance" {
when
math.Percent(to.Num(out.Get("loanAmount")), to.Num(out.Get("propertyValue"))) > 80.0 &&
!out.Bool("hasMortgageInsurance", false)
then
findings.Critical("LoanToValue", "underwriting",
strings.Sprintf("LTV of %s%% requires mortgage insurance",
to.Fixed(math.Percent(to.Num(out.Get("loanAmount")),
to.Num(out.Get("propertyValue"))), 1)));
}

Guard the denominator where it may be zero or unread — to.IsNum on the property value first.

The current time. Use sparingly: a rule that depends on the wall clock produces a different result on a rerun, which makes a decision hard to reproduce when someone asks why it was made. Prefer a date carried on the input.

Parses value using layout.

ParameterTypeMeaning
layoutstringThe reference layout, e.g. "2006-01-02"
valuestringThe text to parse

to.Date is usually better — it tries the profile’s layouts in order, so it reads what documents actually contain without you naming the format.

ParameterTypeMeaning
ttimeThe time
layoutstringThe output layout

to.DateStr does the parse and the format in one call, and returns the empty string rather than a formatted zero date on failure.

time.IsBefore(t1, t2) → bool · time.IsAfter(t1, t2) → bool

Section titled “time.IsBefore(t1, t2) → bool · time.IsAfter(t1, t2) → bool”

Whether t1 is before / after t2.

ParameterTypeMeaning
t1, t2timeThe two times

Guard both sides with to.IsDate first. An unreadable date is the zero time, and the zero time compares as before everything — so an unguarded comparison silently passes or silently fires, depending which side failed.

time.DaysBetween(t1, t2) → int64 · time.MonthsBetween(t1, t2) → int64 · time.YearsBetween(t1, t2) → int64

Section titled “time.DaysBetween(t1, t2) → int64 · time.MonthsBetween(t1, t2) → int64 · time.YearsBetween(t1, t2) → int64”

The interval between two times.

ParameterTypeMeaning
t1, t2timeThe two times
rule RescissionPeriod "closing must be at least three days after disclosure" {
when
to.IsDate(bbox.Doc().Right("Closing Date")) &&
to.IsDate(bbox.Doc().Right("Disclosure Date")) &&
time.DaysBetween(to.Date(bbox.Doc().Right("Disclosure Date")),
to.Date(bbox.Doc().Right("Closing Date"))) < 3
then
findings.Critical("RescissionPeriod", "compliance",
"Fewer than three days between disclosure and closing");
}

time.AddDays(t, days) → time · time.AddMonths(t, months) → time

Section titled “time.AddDays(t, days) → time · time.AddMonths(t, months) → time”
ParameterTypeMeaning
ttimeThe starting time
days / monthsint64How many to add — negative to subtract

AddMonths clamps rather than overflowing: adding one month to 31 January gives the last day of February, not 2 or 3 March.

rule RateLockExpiry "record when the rate lock runs out" {
when
to.IsDate(bbox.Doc().Right("Lock Date"))
then
out.Set("lockExpiry",
time.FormatTime(
time.AddDays(to.Date(bbox.Doc().Right("Lock Date")), 45),
"2006-01-02"));
}