Today a law YAML stores money, ratios and percentages as bare numbers, so 3971900 or
0.01896 is indistinguishable from any other number, a factor-of-100 faithfulness risk. This RFC
makes a value’s quantity-kind an explicit label.
Decided (YAML/modeling layer):
type + type_spec.unit, on inputs, outputs, parameters and
definitions constants.type_spec.unit enum with euro, ratio, percentage (alongside existing
eurocent, years, weeks, months, days).ratio and percentage are distinct labels; any division by 100 is an explicit value operation,
never implied by the label.definitions entries may be optionally structured to carry type/type_spec; the bare
naam: 123 form stays valid.Out of scope (deferred): how the engine checks, infers or reconciles units across laws. Rounding is RFC-024. See In practice for worked YAML.
The question is narrow: how does a law YAML say what a number means? Today it often can’t. The situations below, all from the current corpus, are what the language must be able to express.
1. The corpus already encodes quantities, but ambiguously. wet_op_de_zorgtoeslag stores, in one
definitions block, both a money amount and a ratio as bare numbers:
Guess wrong and you are off by a factor of 100, in either direction.
2. The same legal concept is stored in two incompatible ways. Where zorgtoeslag uses decimal
ratios, the Diemen afstemmingsverordening_participatiewet stores percentages as whole integers, and
these are bare, untyped constants in the schema-free definitions block: no type, no
unit, nothing to disambiguate them:
There is no way today to tell “30 percent” from “raw multiplier 30.” The type: number declarations
that do appear elsewhere in this file sit on parameters and input/output fields, never on
these constants, which by construction cannot carry a type (see situation 5).
3. Quantities cross law boundaries. A consuming law pulls another law’s output via
source.regulation + output, e.g. zorgtoeslag consumes toetsingsinkomen from the AWIR
(algemene_wet_inkomensafhankelijke_regelingen). Both ends are already labeled today: the AWIR output
and the zorgtoeslag input each declare type: amount + type_spec.unit: eurocent. So the YAML can already
express this; what is missing is checking that producer and consumer agree, which is an engine concern
(see Implementation Notes), not a gap in the language.
4. Money is routinely sub-cent. The corpus is social-domain today, but fiscal law is coming, and it is dense with sub-cent amounts, reached two different ways:
Different mechanisms, same modeling consequence: a money value is not always a whole number of cents.
This forces a principle the rest of this RFC relies on: a unit is a label, not a computational
constraint. The arithmetic and the literal values determine the sum, so tagging a value never
changes it by itself. The one way a unit: eurocent tag could corrupt a correct sum is by
triggering implicit rounding, which is why labeling and rounding are kept strictly separate (rounding
lives in RFC-024).
5. The place where misreading originates cannot carry a label at all. The definitions block, the
home of every constant in points 1–2, is schema-free: in the schema it is
{ "type": "object", "additionalProperties": true }. Inputs and outputs can carry type /
type_spec; constants cannot. That is backwards: constants are raw magic numbers
with no surrounding context to disambiguate them.
A value’s quantity-kind becomes a first-class, explicit label in the law representation. The table maps each situation above to the YAML expressiveness it requires and the modeling primitive that provides it.
| # | Situation in legislation (grounded) | What the YAML must express | Modeling primitive (YAML layer) |
|---|---|---|---|
| 1 | Money in whole cents (zorgtoeslag 3971900) | “this is money, denominated in eurocent” | type: amount + type_spec.unit: eurocent (exists) |
| 2 | Sub-cent tariffs (accijns, energiebelasting) | “this money value is not a whole number of cents; the label must not force whole cents” | a money label usable without implying integer cents (euro / eurocent), paired with type_spec.precision (in the schema; issue #444) to state decimal places. Rounding is out of scope (see RFC-024) |
| 3 | Percentage stored as ratio (0.01896, 0.137) | “this is a 0–1 ratio, not a 0–100 percent, not a bare multiplier” | type_spec.unit: ratio |
| 4 | Percentage stored as whole percent (5, 30) | “this is a 0–100 percent” | type_spec.unit: percentage |
| 5 | Constants in definitions carry no type | ”a constant can declare its quantity-kind” | make definitions entries optionally structured so a constant carries type + type_spec; the bare naam: 123 form stays valid |
(Durations and cross-law unit flow are already expressible with existing vocabulary and are not decisions of this RFC; see Already expressible below.)
Concretely, this RFC decides, at the YAML/modeling layer:
type + type_spec.unit,
available on inputs, outputs, parameters, and definitions/constants, closing the schema-free
definitions gap (situation 5).type_spec.unit enum with euro, ratio, and percentage, alongside the
existing eurocent, years, weeks, months, days. Adding euro does not deprecate
eurocent; both are valid money labels and the choice is the modeler’s, reflecting how the law
denominates the value (cf. accijns per 1.000 L).ratio and percentage are distinct labels for the same dimension, and the corpus keeps both.
We do not force a single canonical form, because some texts state “1,896” as a ratio and others
state “30 procent” literally. Whether a percentage is divided by 100 is a value concern,
expressed by an explicit DIVIDE … 100 in the YAML where the value is applied, never something
the label silently performs. The corpus already works this way, and across a law boundary: the
Diemen afstemmingsverordening_participatiewet stores the verlagingspercentages as whole percents
(5, 30) and uses them directly in its IF cases, while the national participatiewet that
consumes that verlaging_percentage (via the art. 8 open term) performs the
… * verlaging_percentage / 100 at the point of application (participatiewet/2022-03-15.yaml:432).unit: eurocent → round_to_integer,
proposed in RFC-015); where rounding belongs is decided in
RFC-024.definitions entries become optionally structured. The bare form naam: 123 remains valid
(backward-compatible); the structured form lets a constant carry type and type_spec, reusing
the existing field shape so there is one way to describe a quantity everywhere.This RFC does not decide how the engine checks, infers, or reconciles units, only what the YAML may say. See Implementation Notes.
The zorgtoeslag constants and a cross-law input, labeled per this RFC (compare the bare forms in Context situations 1–2):
The bare naam: 123 form stays valid, so labeling is incremental: a constant is upgraded only when
someone annotates it.
Two situations the corpus raises are already covered by existing modeling vocabulary. They are not
decisions of this RFC; they are recorded here so authors model them with the existing primitive instead
of reaching for a bare type: number:
years, weeks, months, days (RFC-001), not a bare type: number. No new unit is required.source input already declares its expected
type_spec.unit, and producing laws label their outputs too (Context situation 3: zorgtoeslag’s
toetsingsinkomen and the AWIR output both carry unit: eurocent). Reconciling a producer/consumer
mismatch is an engine concern (see Implementation Notes), not a YAML-layer addition.3971900 is euros or cents.type/type_spec (a boolean switch, a euro field; see PR #748), so a single YAML-layer
decision pays off across the engine, the editor UI, and scenario inputs.definitions blind spot, where the factor-of-100 ambiguity actually originates.ratio and percentage preserves a known representational inconsistency. We accept it
deliberately: collapsing to one canonical form would distort faithful transcription of laws that
literally say “30 procent” versus those that state a ratio.Alternative 1: a single canonical percentage form (ratio only).
Force every percentage to a 0–1 ratio. Rejected: it distorts faithful modeling. A law that says “30
procent” should be transcribable as 30 with a percentage label, not silently rewritten to 0.30.
Alternative 2: drop eurocent, use euro floats everywhere.
Rejected: much of the corpus and Belastingdienst convention is cent-denominated, and forcing euro
floats invites the very precision loss this work is trying to prevent.
Alternative 3: bundle rounding into the unit (the RFC-015 coercion).
Let unit: eurocent imply round-to-integer. Rejected: empirically false for sub-cent tariffs
(accijns, energiebelasting), and it hides a legal decision inside a unit label. Rounding must be a
law-modeled instruction (see RFC-024).
Alternative 4: leave definitions schema-free and rely on naming conventions.
Rejected: that is the status quo that produces the factor-of-100 ambiguity in situations 1–2.
This RFC is intentionally silent on the engine. The unit algebra (which unit pairs are valid under
each operation), unit inference over expression trees, the static-validation and runtime mismatch
checks, the error type for a mismatch, and cross-law unit reconciliation are engine concerns,
specified and implemented separately. A prototype of that engine machinery already exists in PR #729
(units of measurement on values), which originally drafted itself as “RFC-019” (a number since
assigned on main to an unrelated RFC, Law End Dates). This RFC supersedes the modeling decisions
in that draft, reframed situation-first; the Rust code in PR #729 can serve as the implementation of
this RFC’s deferred engine track. The only concrete artifact this RFC implies is a future schema
version that adds the euro/ratio/percentage units and the optional structured
definitions form (schema versions are immutable; the current version is v0.5.3).
type_spec and unit enum
this RFC extends (the type_spec.precision field lives in the schema; see issue #444)unit: eurocent → round_to_integer coercion this RFC
declines to adopt at the label leveltype/type_spec (a non-engine consumer)type_spec.precision/min/max parsed but not enforcedAn exploration by Bureau Architectuur of the Dutch Ministry of the Interior into the possibilities of transparent, executable legislation.
Bureau Architectuur
Ministry of the Interior and Kingdom Relations