Skip to main content

Business Rules

Business rules validate and normalize the values your project extracts. A validation rule checks a value and raises an error or warning in the Transaction Viewer; a normalization rule rewrites a value into a canonical form (dates, currencies, casing, codes). Rules are how you turn "the model extracted something" into "the data is correct, consistent, and enriched".

Starting a rule

New Rule asks how you want to make it, and there are three answers.

Describe it to Copilot. The fastest one, and the one to reach for first. Type what the rule should do in your own words ("flag invoices whose line items don't sum to the total", "match each line item against the purchase-order catalog") and Copilot picks the right preset or writes the code, then saves it to your draft for review. It already knows the project's document classes, fields and catalogs, and it picks up where you opened the dialog from, so a rule started from a field arrives scoped to that field. Copilot can edit and delete rules too, so this is also the quickest way to change one you already have.

Start from a preset. Ready-made, low-code rules with a few parameters to fill in. They are grouped by what you are trying to do (require a value, check the value itself, compare and calculate, check against a catalog or metadata, fill in a value, clean up formatting), and the search box matches on what a preset does as well as on its name. The code is generated for you and the rule stays editable afterwards. Catalog Lookup lives here too: match extracted fields against a reference table of vendors, purchase orders or budget codes without writing code.

Write code. JavaScript against the scripting API, for logic the presets cannot express. See Code Rules. A preset rule can be converted to code later, but not back again.

On a deployment where Copilot is switched off, New Rule opens the preset list directly, with the same link to a code rule.

The rule dialog

Creating or editing a rule opens the same dialog either way, with five groups on the left and one of them shown at a time, in the order below. A plain-English sentence at the bottom always describes the whole rule, so the groups you are not looking at are never out of sight.

GroupWhat it decides
BasicsThe name and the description. A note under them says which pipeline step the rule runs at: a normalization rule runs at Extract, a validation rule at Validate, and both run again during review when someone edits a field the rule can see.
ScopeThe level, and then what this rule may reach at that level: the document types, fields and catalogs, each its own list, with a summary of what they came to at the foot of the pane. It is an access list, not a performance hint. A field-level rule's field is the rule's subject; at document and transaction level the listed fields are the ones the code may name, the ones whose edits re-run the rule during review, and the ones it can confirm when it passes. Scope covers reading and writing alike, so a field listed here is one the rule may also update. Catalogs are a separate grant in the same group: only the ones listed can be queried from rule code. The level appears here only while it is still a choice — a preset fixes it, and so does saving the rule — and the chip beside the dialog's title says which level the rule is at either way.
LogicThe only group that changes with the kind of rule: a preset's form, a visual builder, the catalog mapping, or the code editor.
TriggerWhether the rule runs Always or Only when a condition holds. Every preset that starts at field level takes one, and adding it moves the rule to document level (see Preset reference). A code rule written at field level cannot have one: only document- and transaction-level rules are ever asked whether they should run.
OutcomeWhat a reviewer sees when the rule does not pass. See Rule properties.

The dot beside each group says where it stands: filled for complete, amber for something that still needs a decision, hollow for an option left at its default, and dashed for a group that does not apply to this rule. Hovering an amber dot says what is missing, and while any of them is amber the Save button stays off with the reason shown beside it. Groups never disappear: one that does not apply stays on the rail and says who decided that.

Enabled sits outside the groups, in the dialog header, because it is the control people reach for most.

Writing a code rule, the Logic group opens with Describe the check above the editor: it takes what the rule should do in your own words and writes the JavaScript against your project's own fields, so you get a draft to read, edit or discard, and nothing is saved until you save the rule.

Under the editor is the reference panel, and its first tab, What it can reach, lists the document types, fields and catalogs this rule is allowed to name, with a link back to Scope when something it needs is missing. Reading anything not on that list stops the rule saving, so it is worth a glance before you start. The panel follows you into the full-screen editor.

Rule levels

Every rule runs at one of three levels, which determines what data it sees:

LevelSeesTypical use
FieldOne field valueFormat checks, value normalization
DocumentAll fields in one documentCross-field checks (line items sum to total), catalog lookups
TransactionAll documents in a transactionCross-document checks (passport matches application)

Rule types

TypeWhen it runsPurpose
NormalizationDuring the Extract step, and on field edits in the Transaction ViewerRewrite values to a canonical form
ValidationAt the Validate step, and on field edits in the Transaction ViewerCheck values and raise errors or warnings

Field edits in the Viewer re-run only the affected rules (incremental validation), so a reviewer sees results update as they type without a full re-run.

Rule properties

These are set in the rule dialog and handled by the engine, not embedded in the rule's code. The dialog groups them under Outcome, except Enabled, which sits in the dialog header:

PropertyShown asDefaultDescription
EnabledEnabledOnWhen off, the rule is skipped entirely.
SeverityIf the check failsErrorWhen set to Warning, every error the rule raises is downgraded to a warning after it runs.
Confirm fields on successMark these fields confirmed when the rule passesOnWhen on, the fields a passing validation rule engaged are auto-confirmed (treated as reviewed).
WeightReview priorityNoneDisplay ordering only: higher-priority results appear first in the validation panel.

The severity override is one-directional and applied after the rule runs: Warning turns this rule's errors into warnings, but it never turns warnings into errors. If a rule intentionally mixes errors and warnings, setting it to Warning collapses that distinction (both become warnings); leave it at Error and choose per-condition in code instead.

Preset reference

The New Rule dialog groups presets by what they do; the reference below groups them by the level they run at, which is what decides when they run and what they can see. Names are the labels shown in the dialog. Anything a preset generates is plain JavaScript you can take over later with Edit as code.

The field-level presets do not belong to one level. Every one of them is listed as field-level because that is where it starts, but each moves up to document level on its own as soon as you give it a condition under Trigger. Only a document rule is ever asked whether it should run, so a check that has to depend on the rest of the document runs there. Auto-Replace also moves up when you name a second field under Scope, because rewriting several fields the same way is one coherent request; every other field preset stays on the one field you picked, so requiring or checking four fields is four rules. You do not choose the level for these: the chip beside the dialog's title shows which one the rule ended up at, and Trigger says what would change it.

Field-level presets

PresetTypeWhat it does
Can't Be EmptyValidationRequires a non-empty value (whitespace counts as empty). Runs on one field; add a Trigger and it becomes a document-level rule on that same field.
Choice Field ValidationValidationChecks Choice selections; can require a selection and disallow multiples.
Format Validation by RegexValidationValidates the value against a regular expression. Use Build with AI beside the pattern box to describe the format in plain words and have the expression written for you.
Allowed Values ListValidationRequires the value to be one of a predefined list.
Number RangeValidationRequires a number within a min/max range.
Date RangeValidationRequires a date within a relative window from today.
Default ValueNormalizationFills a default when the field is empty.
Case NormalizationNormalizationConverts to lower, upper, or title case.
Diacritic NormalizationNormalizationRemoves accents and other diacritics from the stored value, keeping its case (Gökçe Ünlü becomes Gokce Unlu). Only needed when the stored value must be plain letters: every comparison already ignores accents (see below).
Code NormalizationNormalizationCleans alphanumeric codes (Tax ID, VAT, IBAN, BIC/SWIFT, card numbers).
Auto-ReplaceNormalizationApplies an ordered list of text replacements. Add a Trigger, or a second field, and it becomes a document-level rule.
Date NormalizationNormalizationReformats dates to a chosen component order and separator.
Number NormalizationNormalizationStandardizes thousands/decimal separators and decimal places.
Currency Code NormalizationNormalizationMaps currency values to ISO 4217 codes (USD, EUR, GBP).
Country Code NormalizationNormalizationMaps country names to ISO 3166-1 codes.
Populate Field From MetadataNormalizationSets the field from a transaction metadata key.
Validate Field Against MetadataValidationCompares the field to a metadata key (exact or fuzzy).

Every preset in this table takes a Trigger, and adding one is what makes the rule run once per document instead of once per value. It still checks the one field you picked in Scope; the condition is what lets the check depend on the rest of the document.

Accents and case never break a comparison

Wherever a preset compares text case-insensitively it also ignores diacritics. See Accents and diacritics below.

Accents and diacritics

OCR is unreliable on small marks: the Turkish ç, ğ, ı, İ, ö, ş and ü, French accents, German umlauts, Polish and Romanian letters. The same supplier name can come back as Çağ Şirketi, Cag Sirketi or Çag Şırketi from three scans of the same stamp.

You do not need a rule to cope with that. Every comparison that ignores case also ignores diacritics, on both sides:

WhereEffect
Compare Fields, exact and fuzzyÇağ Şirketi and CAG SIRKETI are equal; a fuzzy score is not lowered by dropped accents.
Catalog Lookup, exact and fuzzyİzmir Lojistik A.Ş. in the catalog is found by Izmir Lojistik A.S.; the catalog's own spelling is what gets written back.
Allowed Values List (case-insensitive), Choice options, Validate Field Against MetadataFatura Turu matches the option Fatura Türü.
Classification conditions (contains, equals, starts with, ends with)A keyword written with accents matches page text that lost them, and the reverse.
Field highlighting in the viewerA value extracted as Şişli is located on the page even where OCR read Sisli.
Code rulesCall fold_text(a) === fold_text(b); see custom rules.

None of this changes the value you see or export. The extracted spelling stays as it is.

Diacritic Normalization is for the other case: the stored value itself must lose its accents, typically because an export target or a downstream system cannot take them. It rewrites Gökçe Ünlü to Gokce Unlu and keeps the case, so put it before Case Normalization or Code Normalization when you use both. Letters with no plain form are mapped (ß to ss, ø to o, ı to i, æ to ae); everything else, including digits, punctuation and characters such as ², is left alone. The preset has no parameters, and a value that has nothing to strip is left unchanged.

Regular expressions

Format Validation by Regex asks for a pattern, and the dialog shows a short reference beside the box: the characters worth knowing, a set of worked examples, and the four rules below. If you would rather not write one, Build with AI takes a description in plain words ("a UK VAT number, GB followed by 9 digits") and writes the pattern for you, where the assistant is switched on.

Patterns use standard JavaScript (ECMAScript) syntax, the same as most online regex testers. Four things are specific to this preset:

  • Write the pattern on its own, with no surrounding slashes and no flags.
  • Matching is case-sensitive. For either case, write the character class out: [Ss].
  • Nothing is anchored for you. A pattern with no ^ and $ matches anywhere in the value, so \d{4} accepts INV-2026-A. Anchor it to check the whole value.
  • An empty field passes. Use Can't Be Empty to require a value.

A forward slash inside a pattern has to be escaped as \/, so a date reads ^\d{2}\/\d{2}\/\d{4}$. Build with AI does this for you.

Some patterns to read:

PatternMatchesWhat it says
^\d{4}$2026exactly four digits, and nothing else
^GB\d{9}$GB123456789GB followed by nine digits
^[A-Z]{2}\d{6}$AB123456two capital letters, then six digits
^INV-\d+$INV-4021INV- followed by at least one digit
^\d{2}\/\d{2}\/\d{4}$31/12/2026a date with slashes, each one escaped
^[A-Za-z0-9 -]+$Acme Ltd - 4letters, digits, spaces and hyphens only
`^(EURUSDGBP)$`

Document-level presets

PresetTypeWhat it does
Catalog LookupValidationMatches extracted fields against a reference catalog; can run per line item and pull data back.
Compare FieldsValidationRequires fields, table columns, and constants to match within a document.
Cross-Field Date ValidationValidationRequires one date on or before another, with swap suggestions. It is the one preset that carries its own Severity setting among its parameters, alongside the rule's own under Outcome.
Arithmetic ValidationValidationChecks arithmetic relationships (sum fields, aggregate table columns).
Validate Fields Against MetadataValidationCompares several fields to metadata keys, each with its own mode.

Transaction-level presets

PresetTypeWhat it does
Compare FieldsValidationRequires fields and table columns to match across document types.
Transaction Integrity CheckValidationChecks document types, counts, page counts, and conditional requirements.

Defaults and metadata fills: rule or field property?

There are three ways to say "when this field is empty, use this value", and they are not interchangeable.

  • On the field itself. Under Field Properties, a field can carry a default value and a value source. This is the recommended way for a plain, unconditional fill. See Extraction Fields. It is applied during extraction, while the model's own confidence and reasoning for that field are still available, so a defaulted value keeps them and still reaches review if the confidence was low.
  • The field-level presets (Default Value, Populate Field From Metadata). The rule form, applied after extraction as a normalization step. Use one when the value must never be left empty, when you want the fill ordered against your other normalization rules, or when a rule is simply what you prefer to manage.
  • The same presets, with a Trigger. Use this when the fill has to depend on something else in the document. Adding a condition in the rule's Trigger group moves the rule to document level for you, because only document- and transaction-level rules are ever asked whether they should run. The rule still fills the one field you picked; to fill several, make one rule each.
note

Two presets used to cover that last case on their own, Conditional Default Value and Conditional Populate From Metadata. They are gone from the gallery. Rules you already have keep working and keep their settings; new ones start from Default Value or Populate Field From Metadata and add a Trigger. Default Value gained the Overwrite Existing Value option the conditional version had.

Starting value or enforced value

The choice between the first two is not only about where the setting lives. It changes what happens at review.

On the fieldAs a rule
Runsonce, during extractionevery time the field is edited
Reviewer clears the valueit stays clearedthe value comes back

A rule is re-evaluated whenever a reviewer changes the field. A Default Value rule sees the now-empty field and fills it again, so clearing the value in the viewer makes it reappear. That is exactly what you want for a field that must never be empty, and exactly what you do not want if you only meant to suggest a starting value.

So: a starting value the reviewer may override belongs on the field; a value that must always be present belongs in a rule. A Trigger does not change that: a triggered fill is still a rule, so it re-asserts on edit as well.

The metadata-driven presets (Populate/Validate Field(s) From/Against Metadata) compare extracted values against reference data you carry on transaction.metadata. They are the no-code path to the metadata helpers documented in Code Rules. Submit reference values (expected vendor, policy flags) alongside the document at upload time, and the rule compares against them.

Wherever a rule asks for a metadata key, the box suggests the keys your project actually uses: those your recent transactions carry, and those your import connectors write (an email connector contributes Subject, From, Received and the rest). You can still type any key by hand. The same suggestions appear in classification conditions and in workflow If/Switch conditions that test a metadata value, and when adding a metadata column.

Where to go next

  • Catalog Lookup Rule: match documents against reference tables (vendors, purchase orders), including per line item.
  • Code Rules: the full JavaScript scripting API, examples for every level, and the sandbox/security model.