A cross-document check compares a shared fact across two or more documents in a run. The check's match type decides how those values are compared, what a Pass means, and when the case falls through to a human.

For configuration, see Cross-Document Checks. For the officer view of the results, see Checks → Cross-document checks.

At a glance

Match type Compares Verdicts it can return Best for
Exact Normalised equality of the joined value Pass, Mismatch, Insufficient data IDs, VAT/PE numbers, emails, phones, dates
Fuzzy Token-based similarity with an AI grey band Pass, Mismatch, Needs review, Insufficient data Names, wording, free-text descriptions
Range (period covers) Whether candidate periods fully cover a reference period Pass, Mismatch, Needs review, Insufficient data Insurance, tenancy, or employment that must span a stay
Signature Nothing automatically Needs review Signature-image comparison by a human
External system Nothing automatically Needs review Verifications that require a third-party check

All match types share three additional outcomes:

  • Insufficient data when fewer than two documents carry the compared fields, or the reference side of a Range check is missing.
  • Needs review when the computed result is not confident enough to stand on its own.
  • Officer overrides ("mark as OK", "confirm mismatch", "not applicable") live alongside the computed result and are preserved through reprocessing while the computed status is unchanged.

Exact

Use Exact when both sides carry the same standardised identifier and any difference should be treated as a mismatch.

Before comparing, each value is normalised in a kind-aware way:

  • Dates are parsed from common formats (YYYY-MM-DD, DD/MM/YYYY, 1 January 2025, ISO, etc.) and compared as ISO dates.
  • Emails are trimmed and casefolded.
  • Identifiers (values with digits and optional separators such as spaces, hyphens, dots or slashes) are reduced to alphanumerics and casefolded. For all-digit values longer than eight characters, only the trailing eight digits are compared so that country-code prefixes on phone numbers do not cause false mismatches.
  • Everything else is casefolded with whitespace collapsed.

If every document produces the same normalised value the check passes. Otherwise the raw values from each document are reported in the mismatch message.

Tip

Exact is the right choice for ID card numbers, passport numbers, VAT numbers, PE numbers, dates of birth, and email addresses. For anything that a human would still call "the same" despite a spelling variant, use Fuzzy.

Fuzzy

Use Fuzzy when the values should agree in meaning but the exact spelling or word order may vary — most commonly names, addresses, job titles, and free-text descriptions.

Values are compared pair-wise using rapidfuzz token_set_ratio (0-100), and the weakest pair decides the check:

Similarity score Result
≥ 90 Pass
55 - 90 (grey band) Ask the AI adjudicator, else Needs review
≤ 55 Mismatch, unless the AI adjudicator confidently disagrees

The grey band exists because token order and initials — for example P. Palackathadathil vs Palackathadathil P — legitimately land there.

Person names

If either side of the comparison is stored with the PersonName field type, Identità applies name-aware rules on top of the fuzzy score:

  • Honorifics (Mr, Mrs, Dr, Prof, Eng, Rev, Hon, Sir, …) are stripped;
  • Punctuation is removed and all tokens are compared in upper case;
  • Missing middle names are tolerated as long as every token on the shorter side matches a distinct token on the longer side; and
  • A single-letter token stands for any longer token that starts with it, so John P Smith matches John Palackathadathil Smith.

AI adjudication

When the score does not clear-pass, the check is handed to the AdjudicatorAgent (see apps.zf_ai.agents.adjudicator). The agent returns a verdict, a confidence, and a short reason. Two thresholds are involved: the similarity score decides whether the agent is consulted, and the agent's own confidence decides whether its answer is trusted.

In the grey band (55 - 90):

  • If confidence < 70 %, the result is Needs review and the reason is attached to the check details.
  • Otherwise the agent's Pass or Mismatch verdict stands and its reasoning is stored on the result.
  • If the adjudicator cannot be reached (for example the model provider is unavailable), the check falls back to Needs review with the raw similarity scores in the details rather than guessing.

On a clear fail (≤ 55) the agent gets a chance to rescue wording variance that the lexical score cannot see — translations, synonyms, or a title buried in a list:

  • A confident match verdict (confidence ≥ 70 %) turns the result into a Pass, with the reasoning stored on the result.
  • A confident mismatch verdict keeps the Mismatch, now with the agent's reasoning attached.
  • An unsure verdict or an unreachable adjudicator keeps the deterministic Mismatch — AI doubt never softens a clear lexical fail.

List fields and multi-value sources

When a field set consists of a single list-typed field (StringList, JobTitleList, and their aliases), the extracted items are compared individually and automatically — no configuration needed. Each item is a candidate, and the best-scoring pair wins. This is what lets a job advert whose job_titles list contains Network Security Engineer (among four other roles) satisfy the single job_title on a work contract. Because the raw list items are used directly, titles containing and, /, or commas are never split apart.

For plain free-text fields that pack several values into one string, mark the field set multi-value in the check configuration. The value is then split on delimiters (,, ;, /, |, and, &, and common bullet characters) and each part becomes a candidate.

When the AI adjudicator is consulted, list sides are presented to it as candidate sets with the same any-of semantics, so its verdict accounts for the fact that only one advertised title needs to match.

Range (period covers)

Use Range when one document defines a period that another document (or several) must fully cover. The most common example is a residence stay that must be covered end-to-end by health insurance, accommodation, or employment.

Configure exactly one field set as the reference — the period that must be covered. Every other field set contributes candidate periods that need to span it.

The engine reads each side's date fields with a light heuristic:

  • names containing from, start, or commencement become the start; and
  • names containing to, end, until, or expiry become the end;
  • otherwise the first parseable date is treated as the start and the next as the end;
  • if start and end are the wrong way round they are swapped.

For every candidate document, coverage passes when start ≤ reference_start and end ≥ reference_end.

Situation Result
Reference has no start or end that could be parsed Needs review
A candidate has no parseable period Needs review
A candidate period does not cover the reference Mismatch
Every candidate covers the reference Pass
No reference set, or no candidates present Insufficient data

The mismatch message names the offending documents and the periods they actually cover.

Signature

Signature comparison is not performed automatically. When the match type is Signature, the check always returns Needs review and the collected signature evidence is presented to the officer for a visual comparison. The values gathered from each document are still attached to the check details so the reviewer can jump straight to the relevant pages.

Use Signature when you want the check to appear on the Run Detail page for an officer to complete — for example, comparing the signature on a work contract against the signature on the ID card.

External system

External system works the same way as Signature in terms of the computed status: the check always resolves to Needs review so an officer can perform the third-party verification and record the outcome.

Pick this match type when the ground truth lives outside Identità — for example when a value must be looked up in a government registry, a police system, or another portal that the operator visits by hand.

Money (automatic sub-type)

Money is not a separate match type in the UI, but Identità automatically switches to a money-aware comparison whenever either side of an Exact or Fuzzy check uses a monetary field type (MoneyAmount, SalaryBand, or PayPeriod). This makes wage comparisons between adverts, contracts, and payslips work out of the box.

  • Concrete amounts (MoneyAmount and free-text numbers) are normalised to an annual figure using the pay period found in the value or hinted by the field name — recognised keywords include annum, annual, yearly, p.a., monthly, weekly, daily, and hourly. A MoneyAmount field is typically paired with a PayPeriod sibling field in the same field set so the period travels with the amount.
  • Salary bands are parsed as either:
    • the structured SalaryBand field type — a {currency, minimum, maximum} object — which yields an inclusive (minimum, maximum) range; or
    • a free-text value on a field whose name contains band, in which case strings like €15,000 - €20,000, Under €15,000, and Over €30,000 are recognised, with an open top for "over"-style bands.
  • Annual amounts must agree within 2 % or EUR 50 (whichever is larger). A concrete amount checked against a band passes when the amount sits inside the band.
  • Any value that cannot be parsed produces Needs review for that document; if there is not enough parseable data to compare at all, the check is also Needs review rather than a silent pass.

The details attached to the result show the parsed annual amounts and bands, so an officer can see exactly what the engine compared.

Note

Extracted MoneyAmount values are normalized to EUR before they reach the comparison. The comparison itself does not perform foreign-exchange conversion, so manually imported values must already be EUR. The currency captured on SalaryBand (and any currency symbol found in a free-text amount) is still not converted and those values are compared as plain decimal amounts.

Choosing a match type

If the question is… Use
"Do these identifiers match exactly?" Exact
"Do these names or wordings mean the same thing?" Fuzzy
"Does this period fully cover the stay?" Range (period covers)
"Do these two signatures look like the same person?" Signature
"Is this record valid in an external system?" External system

When in doubt, prefer the strictest match type that will not generate false positives on the routine variance you expect to see. Reserve Fuzzy for values a human would still call "the same" despite spelling or ordering differences.

For where a rule belongs in the overall validation model (field type, Step condition, per-document rule, or cross-document check), see Where should a new rule live?.