HoloLang / Rule Types
The five rule types (REQUIRED, VALIDATE FORMAT, ASSERT, IF/THEN and WARN), plus length validation and formula syntax.
Rule Types
| Rule Type | Syntax | Purpose |
|---|---|---|
| Presence | REQUIRED @field | Ensure field exists and is non-empty |
| Format | VALIDATE @field[, @field2, ...] FORMAT TYPE | Pattern validation (SIRET, DATE, MRZ_TD1, etc.) |
| Formula | @total = @a + @b | Computed field assignment |
| Assertion | ASSERT @a == @b WITHIN n | Equality check with tolerance |
| Comparison | ASSERT @a > 0 | Exact comparison (no tolerance) |
| Conditional | IF cond THEN rule | Execute rule when condition is met |
| Warning | WARN IF cond | Non-blocking alert |
REQUIRED - Presence Validation
Ensures a field is present and non-empty:
# Basic requirement
REQUIRED @company_name
# Conditional requirement
IF @company_type == 'SA' THEN REQUIRED @capital_socialVALIDATE FORMAT - Format Validation
Validates field values against predefined or custom patterns:
| Format | Description | Example Value |
|---|---|---|
| SIRET | French 14-digit business ID | 12345678901234 |
| DATE | Date (YYYYMMDD or DD/MM/YYYY) | 31/12/2024 |
| Email address | [email protected] | |
| NOT_EMPTY | Non-empty string | Any non-blank value |
| IBAN | Bank account: country, length and ISO 7064 mod-97 check digits | FR14 2004 1010 0505 0001 3M02 606 |
| /regex/ | Custom regex pattern | /^[A-Z]{3}-\d{4}$/ |
| MRZ_TD1 | TD1 identity card MRZ (ICAO 9303, 3 lines × 30 chars) | Requires 3 fields (see below) |
| MRZ_TD3 | TD3 passport MRZ (ICAO 9303, 2 lines × 44 chars) | Requires 2 fields (see below) |
| MRZ_CONSISTENCY | MRZ agrees with the visual fields; the layout (TD1, TD2, TD3, French CNI) is detected | Requires 7 fields (see below) |
| MRZ_TD1_CONSISTENCY MRZ_TD3_CONSISTENCY MRZ_FRA_CNI_CONSISTENCY | Same check for one fixed layout, with the MRZ split into one field per line | Requires 9 fields (see below) |
| ADDRESS_FR | French address exists, confirmed to the house number (Base Adresse Nationale) | 8 Boulevard du Port 80000 Amiens |
| ADDRESS_FR_STREET | French address exists at street level; the house number is not checked | Rue Sainte-Catherine 33000 Bordeaux |
2D-Doc seals need no FORMAT rule. A schema field declared with the 2D-Doc type is decoded and its signature verified on every run, and the result is published as @enriched.<field>.* plus its own TWODDOC_* check codes. The former FORMAT TWODDOC and FORMAT TWODDOC_CONSISTENCY verbs were removed in August 2026 and no longer parse. See enriched verdict fields.
IBAN Key Verification
FORMAT IBAN verifies a bank account number against the ISO 13616 structure and the ISO 7064 MOD-97-10 check digits. Everything is computed locally; no bank or external service is contacted.
# Country, length and mod-97 check digits
VALIDATE @iban FORMAT IBAN
# A FORMAT line only judges the value that is there.
# Demand presence on its own line:
REQUIRED @iban
VALIDATE @iban FORMAT IBANGrouping separators and case are normalised before the check, so FR14 2004 1010 0505 0001 3M02 606, fr1420041010050500013m02606 and the hyphenated form all pass. Spaces (including the non-breaking and narrow non-breaking spaces French typography puts in grouped numbers), hyphens and dots are stripped.
The validator checks, in order, so a failure names the actual problem:
- Shape: two letters, two digits, then letters or digits
- Country: the ISO 3166-1 prefix is a known IBAN country
- Length: exact, per country (27 for FR, 22 for DE, 16 for BE…)
- Key: the MOD-97-10 remainder over the rearranged string equals 1
Two behaviours to plan for:
- An empty or missing value passes. Presence is expressed with
REQUIREDorFORMAT NOT_EMPTYon its own line, so an absent optional field is never reported as a forged one. - Around twenty countries issue IBANs without appearing in the ISO 13616 registry (the Maghreb and much of francophone Africa). No authoritative length exists for them, so only the key is enforced. Enforcing a guessed length would reject genuine customer accounts.
A valid key proves the value was transcribed correctly, which is what makes it worth running over extracted text: it catches every single-character substitution and very nearly all digit transpositions. It says nothing about whether the account exists or belongs to the holder. Unlike ADDRESS_FR, this validator publishes no derived fields.
Address Coordinates
When ADDRESS_FR resolves an address, it also publishes the point the reference dataset places it at, as @enriched.<field>.latitude and @enriched.<field>.longitude, where <field> is the field the VALIDATE line ran on. Both are decimal degrees, stored as numbers rather than strings, so later lines can compute with them.
# The VALIDATE line publishes the coordinates for the lines below it
VALIDATE @adresse FORMAT ADDRESS_FR
IF @enriched.adresse.granularity == 'housenumber' THEN ASSERT @enriched.adresse.latitude > 41Two properties to write rules against:
- They come as a pair or not at all. A verdict with no point publishes neither field, so a rule that reads one without a guard reports a missing field rather than comparing against an invented zero.
- Granularity decides whether they mean anything. A
housenumberorstreetmatch points at the building or the street. Amunicipalitymatch points at the town centre, which is a different place from the address. Gate on@enriched.<field>.granularitybefore trusting a coordinate.
Their main consumer is dossier-level: a document uploaded as a geotagged photo can be compared against the address printed on it, which answers « was this proof of address photographed anywhere near the address it shows? ». See Photo Capture Fields.
A rule inside the extractor asks that same question without these fields, and without this VALIDATE line: it names the address field and reads @enriched.<field>.exif_distance_km, measured against the enrichment sidecar that every Address (FR) schema field gets on its own. See Was the photo taken at the address?.
Multi-field Format Validation
Some validators require multiple fields. Use comma-separated field references to pass all required values:
# MRZ validation for TD1 identity cards (3 lines × 30 characters)
# Validates: character set, line lengths, all 4 ICAO 9303 check digits
VALIDATE @mrz_line1, @mrz_line2, @mrz_line3 FORMAT MRZ_TD1
# MRZ validation for TD3 passports (2 lines × 44 characters)
# Validates: character set, line lengths, all 5 ICAO 9303 check digits
VALIDATE @mrz_line1, @mrz_line2 FORMAT MRZ_TD3The MRZ_TD1 validator checks:
- Each line is exactly 30 characters, using only A-Z, 0-9, and <
- Document number check digit (line 1, position 15)
- Date of birth check digit (line 2, position 7)
- Expiry date check digit (line 2, position 15)
- Global check digit spanning both lines (line 2, position 30)
The MRZ_TD3 validator checks:
- Each line is exactly 44 characters, using only A-Z, 0-9, and <
- Document type is P (passport)
- Document number check digit (line 2, position 10)
- Date of birth check digit (line 2, position 20)
- Expiry date check digit (line 2, position 28)
- Personal number check digit (line 2, position 43; < or 0 accepted when the field is unused)
- Global check digit (line 2, position 44)
MRZ Consistency
MRZ_TD1 and MRZ_TD3 prove the zone is well formed. The consistency formats prove it says the same thing as the rest of the document: they decode the MRZ and compare it with the visual fields, so a photo-edited birth date that left the MRZ untouched fails. The fields are positional; list them in this order.
# One field holding the whole MRZ, lines separated by newlines.
# The layout is detected from the line count and the length of the data line.
VALIDATE @mrz, @date_of_birth, @date_of_expiry, @document_number, @nationality, @sex, @country_code FORMAT MRZ_CONSISTENCY
# One field per MRZ line, for a single layout. A two-line layout leaves the third slot empty.
VALIDATE @mrz_line1, @mrz_line2, @mrz_line3, @date_of_birth, @date_of_expiry, @document_number, @nationality, @sex, @country_code FORMAT MRZ_TD1_CONSISTENCY- No MRZ on the document passes. Make the MRZ
REQUIREDif its absence should fail. - Another layout is skipped, so several consistency rules can sit on one schema. A data line of the wrong length fails when the zone's own document code names this layout: that is a corrupt MRZ, not someone else's.
- A character outside A-Z, 0-9 and < is skipped as unreadable, not failed.
- A visual field left empty is not compared. If none of them could be compared the rule fails, because that almost always means the field names do not match the extractor schema.
The verdict fields these validators publish (@enriched.<validated field>.*) and the per-type 2D-Doc sealed-field catalogue are documented in Sealed & Derived Fields.
Length Validation Syntax
The LENGTH modifier checks an exact character count or a range without a regex:
| Base Type | Description | Example |
|---|---|---|
| NUMERIC | Only digits (0-9) | VALIDATE @numero FORMAT NUMERIC LENGTH 13 |
| ALPHANUMERIC | Letters and digits (a-z, A-Z, 0-9) | VALIDATE @code FORMAT ALPHANUMERIC LENGTH 10 |
| ALPHA | Only letters (a-z, A-Z) | VALIDATE @country FORMAT ALPHA LENGTH 2 |
| TEXT | Any characters | VALIDATE @name FORMAT TEXT LENGTH 10-100 |
Length Specifications:
LENGTH N- Exactly N charactersLENGTH N-M- Between N and M characters (inclusive)
# Built-in formats
VALIDATE @company_siret FORMAT SIRET
VALIDATE @fiscal_year_end FORMAT DATE
VALIDATE @contact_email FORMAT EMAIL
VALIDATE @iban FORMAT IBAN
# LENGTH syntax - Exact length
VALIDATE @numerofiscaldeclarant FORMAT NUMERIC LENGTH 13
VALIDATE @siren FORMAT NUMERIC LENGTH 9
VALIDATE @country_code FORMAT ALPHA LENGTH 2
# LENGTH syntax - Range
VALIDATE @company_name FORMAT TEXT LENGTH 2-100
VALIDATE @reference FORMAT ALPHANUMERIC LENGTH 5-20
# Custom regex pattern (for complex patterns)
VALIDATE @phone FORMAT /^\+33[0-9]{9}$/
VALIDATE @code FORMAT /^[A-Z]{3}-\d{4}$/
# Multi-field validation (MRZ check digits for ID cards and passports)
VALIDATE @mrz_line1, @mrz_line2, @mrz_line3 FORMAT MRZ_TD1
VALIDATE @mrz_line1, @mrz_line2 FORMAT MRZ_TD3Formula Rules - Computed Fields
Define computed fields using addition and subtraction. This is a pure assignment without tolerance checking:
# Sum line items
@f110_brut = @f010_brut + @f014_brut + @f028_brut
# Calculate net value
@net = @gross - @depreciation
# Balance sheet equation
@total_passif = @capitaux_propres + @dettes
# With nested object fields (e.g., payslip with currency amounts)
@netsalary.amount = @grosssalary.amount - @deductions.amountASSERT - Assertions
ASSERT rules validate field values. Use WITHIN for numeric equality comparisons to handle floating-point tolerance:
# Equality with tolerance (recommended for numeric ==)
ASSERT @total == @expected WITHIN 0.01
ASSERT @actif_total == @passif_total WITHIN 10
# Exact equality (WITHIN is optional - when omitted, exact match is used)
ASSERT @city == 'MADRID'
ASSERT @status == 'active'
# Other comparisons (exact match, no WITHIN needed)
ASSERT @profit >= 0
ASSERT @debt_ratio < 1.0
ASSERT @balance != 0
ASSERT @status != 'inactive'
# Strict equality: no case or accent folding, no conversion of any kind
ASSERT @reference === 'aB12-X'WITHIN is optional for == comparisons. Use it for numeric values to handle rounding differences. Omit it for exact string or value matching. Other operators (>, <, >=, <=) always perform exact comparisons.
String == and != ignore case and accents, because the same value legitimately reaches two fields in two presentations (a 2D-Doc seal carries PILCZER where the extracted field reads Pilczer, and a machine-readable zone cannot carry Ü at all). Everything else still counts: punctuation, digits and internal spacing, so '2.1' and '2.10' remain different.
When the exact characters are the point, === and !== compare the values as they are: no case or accent folding, no numeric or date conversion, and a missing field stays missing rather than becoming an empty string.
IF/THEN - Conditional Rules
Execute a rule only when a condition is met. If the condition is false, the rule passes automatically:
# Conditional presence
IF @company_type == 'SA' THEN REQUIRED @capital
# Conditional assertion
IF @is_active == 'yes' THEN ASSERT @balance == @expected WITHIN 0.01
IF @profit > 0 THEN ASSERT @tax > 0
# Conditional formula
IF @is_consolidated == 'true' THEN @total = @a + @b + @c
# Conditional format validation (skip the check when a related field is absent)
IF @nomdeclarant2 != '' THEN VALIDATE @numerofiscaldeclarant2 FORMAT NUMERIC LENGTH 13
IF @country == 'FR' THEN VALIDATE @company_siret FORMAT SIRET
# Multiple conditions with AND/OR
IF @a > 0 AND @b > 0 THEN ASSERT @c > 0
IF @type == 'A' OR @type == 'B' THEN REQUIRED @codeWARN IF - Non-blocking Warnings
Warnings alert on potential issues without failing validation. The all_passed result is True even when warnings are triggered:
# Standard condition-based warnings
WARN IF @capitaux_propres < 0
WARN IF @debt > @equity
WARN IF @balance < 0 AND @status == 'active'
# Warning based on assertion failure
WARN IF NOT ASSERT @expected == @actual WITHIN 10
WARN IF NOT ASSERT @count > 0