Relations
A relation is a rule that must hold between fields, such as “the total is net plus VAT”. You write it as an expression that is either true or false:
{ "id": "sum", "expr": "total == net + vat" }A relation says what must hold, not which field to compute, and Tickmarks uses it in every
direction it can. The rule above computes total from net and vat, but if an invoice shows a
total and a net amount and leaves out the VAT, the same rule computes vat instead.
What relations do
Section titled “What relations do”The obvious use is filling in values the document doesn’t state, as in the VAT example. Relations
also help when the document says too much. If the model finds two amounts that could each be the
total, the one that equals net + vat wins. And if the document’s confident values can’t all be
true at once, say a VAT line that doesn’t add up, those fields get status conflict. The
response then explains which pieces of evidence disagree and what each possible fix would give,
so a person can decide.
Only == computes values. A comparison such as <, or logic such as and, can rule candidates
out but never fills anything in.
Relation keys
Section titled “Relation keys”| Key | Type | Default | Description |
|---|---|---|---|
id |
string | required | Unique name. Responses use it to say which relation derived a value or caused a conflict. |
expr |
expression | required | The rule. Must be true or false. |
eom |
"clamp" | "strict" |
defaults.eom |
End-of-month rule for date arithmetic in this relation. |
end |
"exclusive" | "inclusive" |
defaults.end |
End-date convention for this relation. |
null |
"vacuous" | "strict" |
"vacuous" |
What happens when an operand has no value. |
soft |
true or a decimal string in (0, 1) |
off | Make the relation a preference instead of a rule. |
Calendar settings: eom and end
Section titled “Calendar settings: eom and end”These override the schema’s defaults for a single relation, which helps when one date in a
document follows a different convention from the rest. Calendar and money
rules explains both settings.
{ "id": "term", "expr": "lastDay == startDate + duration", "end": "inclusive" }{ "id": "renewal", "expr": "renewalDate == startDate + dur'P1M'", "eom": "strict" }With end: inclusive, a 12-month lease starting on 2027-03-01 gives lastDay as
2028-02-29 instead of 2028-03-01.
Missing values: null
Section titled “Missing values: null”Documents often leave fields out, so by default a relation with a missing, null or not-applicable
operand is vacuous: it counts as satisfied and computes nothing. If a relation only makes sense
when every value is present, set "null": "strict" and it’s violated instead.
{ "id": "sum", "expr": "total == net + vat", "null": "strict" }With "strict", an invoice without a VAT amount violates sum and the response reports it,
where by default sum would simply be skipped.
Preferences: soft
Section titled “Preferences: soft”Some rules are usually true but not always, such as “leases rarely run longer than five years”.
A soft relation doesn’t rule out values that break it; it makes them less likely. With
"soft": true, an assignment that violates it keeps 5% of its weight (a factor of 0.05). A
string such as "0.2" sets your own factor. Soft relations never compute values.
{ "id": "usualTerm", "expr": "duration <= dur'P60M'", "soft": "0.2" }Conditional rules: when
Section titled “Conditional rules: when”when(condition, rule) applies rule only once condition is known to be true. It must be the
outermost part of the relation, or the body of another when.
{ "id": "cut", "expr": "when(status == 'terminated', terminationDate <= endDate)" }Examples
Section titled “Examples”[ { "id": "term", "expr": "endDate == startDate + duration" }, { "id": "order", "expr": "startDate < endDate" }, { "id": "sum", "expr": "total == net + vat" }, { "id": "vat", "expr": "vat == round(net * rate / 100, 2)" }, { "id": "line", "expr": "lineTotal == unitPrice * quantity" }, { "id": "deposit", "expr": "deposit <= rent * 3" }, { "id": "noticeGiven", "expr": "when(status == 'terminated', is_present(noticeDate))" }]Tickmarks catches relations that can’t do anything useful. One that’s always true, such as 1 == 1,
is dropped with the warning W-REL-TRIVIAL. One that can never be true, such as 1 == 2, is a
compile error, E-REL-FALSE, because it would rule out every possible answer.