Law Machine-Readable Interpreter
Analyzes legal text in YAML files and generates complete machine_readable execution logic.
What This Skill Does
- •Reads YAML law files from
regulation/nl/ - •Analyzes each article's legal text
- •Identifies computational elements:
- •Input parameters (BSN, dates, amounts)
- •Constants and definitions
- •Conditions and logic
- •Cross-references to other laws/articles
- •Output values
- •Generates complete
machine_readablesections with:- •
competent_authority- who has binding authority - •
requires- dependencies on other laws/regulations - •
definitions- constants and fixed values - •
executionsection containing:- •
produces- legal character and decision type - •
parameters- caller-provided inputs - •
input- data from other sources - •
output- what this article produces - •
actions- operations and logic
- •
- •
- •Converts monetary amounts to eurocent (€ 795,47 → 79547)
- •Creates TODO comments for missing external law references
- •Uses aggressive AI interpretation (full automation)
Important Principles
- •Aggressive interpretation: Generate complete logic even if uncertain
- •Eurocent conversion: Convert all monetary amounts (€ X,XX → eurocent)
- •Cross-references: Detect references to other laws/articles
- •TODOs for missing refs: Add TODO comments when external laws don't exist in repo
- •legal_basis: Add traceability to specific law text where applicable
Schema Structure Overview
The schema has NO public or endpoint fields. The machine_readable section structure is:
machine_readable:
competent_authority: # Who has binding authority
name: "Belastingdienst"
type: "INSTANCE" # or "CATEGORY" (default: INSTANCE)
requires: # Dependencies (optional)
- law: "Zorgverzekeringswet"
values: ["is_verzekerd"]
definitions: # Constants (optional)
VERMOGENSGRENS:
value: 15485900
execution:
produces: # Legal character (optional)
legal_character: "BESCHIKKING" # or TOETS, WAARDEBEPALING, BESLUIT_VAN_ALGEMENE_STREKKING
decision_type: "TOEKENNING" # or AFWIJZING, GOEDKEURING, AANSLAG, etc.
parameters: # Caller-provided inputs
- name: "bsn"
type: "string"
required: true
input: # Data from other sources
- name: "toetsingsinkomen"
type: "amount"
source:
regulation: "awir" # Name of the law/regulation
output: "toetsingsinkomen" # Output field to retrieve
parameters:
bsn: "$bsn"
output: # What this produces
- name: "heeft_recht"
type: "boolean"
actions: # Computation logic
- output: "heeft_recht"
operation: "GREATER_THAN_OR_EQUAL"
subject: "$leeftijd"
value: 18
Step-by-Step Instructions
Step 1: Identify Target Law File
When user asks to "interpret" or "make executable" a law:
- •Search
regulation/nl/for the law file - •If multiple versions exist, ask which date to use
- •Read the entire YAML file
Step 2: Analyze Each Article
For each article in the articles array:
- •
Read the legal text in the
textfield - •
Identify if it's executable:
- •Does it define a calculation, condition, or decision?
- •Does it provide a concrete output value?
- •If YES → Add
machine_readablesection - •If NO (just definitions) → Skip or add minimal section
- •
Extract key elements:
- •Parameters: What inputs are needed? (BSN, dates, amounts, etc.)
- •Constants: Fixed values defined in the text
- •Conditions: If/when/unless statements
- •Calculations: Mathematical operations
- •References: Mentions of other articles/laws
- •Outputs: What the article calculates/determines
Step 3: Identify Competent Authority
Determine who has binding authority for the decision:
competent_authority: name: "Belastingdienst/Toeslagen" type: "INSTANCE" # Specific organization
Or for categories (must be resolved per context):
competent_authority: name: "gemeente" type: "CATEGORY" # Abstract category, resolved at runtime
Step 4: Identify and Define Parameters
Look for inputs that must be provided by the caller:
Common parameters:
- •
bsn(string) - Citizen service number - •
peildatum(date) - Reference date - •
jaar(number) - Year - •
bedrag(amount) - Amount
Example from text:
"Een persoon heeft recht op zorgtoeslag indien hij de leeftijd van 18 jaar heeft bereikt"
→ Needs bsn to look up person's age
YAML output:
parameters:
- name: "bsn"
type: "string"
required: true
description: "Burgerservicenummer van de persoon"
Step 5: Extract Constants and Definitions
Look for fixed values mentioned in the text:
Example from text:
"De grens bedraagt € 154.859 voor een alleenstaande"
YAML output:
definitions:
VERMOGENSGRENS_ALLEENSTAANDE:
value: 15485900 # Converted to eurocent!
description: "Vermogensgrens voor alleenstaande personen"
Monetary Conversion Rules:
- •€ 154.859 → 15485900 (eurocent)
- •€ 2.112 → 211200 (eurocent)
- •€ 795,47 → 79547 (eurocent)
- •Always use integer eurocent values
Step 6: Identify Cross-Law References (Input Sources)
Look for references to other laws or articles:
Patterns to detect:
- •"ingevolge de [Law Name]"
- •"bedoeld in artikel X"
- •"genoemd in [regulation]"
- •Markdown links:
[text](https://wetten.overheid.nl/BWBR...)
Source Structure:
input:
- name: "is_verzekerd"
type: "boolean"
source:
regulation: "zorgverzekeringswet" # Law identifier
output: "is_verzekerd" # Output field name
parameters:
bsn: "$bsn"
description: "Verzekerd ingevolge de Zorgverzekeringswet"
For external data (not from another law):
input:
- name: "geboortedatum"
type: "date"
source:
output: "geboortedatum" # No regulation = external data source
description: "Geboortedatum uit BRP"
If external law not found in repo:
input:
- name: "is_verzekerd"
type: "boolean"
source:
# TODO: Implement zorgverzekeringswet
regulation: "zorgverzekeringswet"
output: "is_verzekerd"
parameters:
bsn: "$bsn"
Step 7: Define Outputs
Identify what the article produces:
Data types available:
- •
string- Text values - •
number- Numeric values - •
boolean- True/false - •
amount- Monetary values (in eurocent) - •
date- Date values - •
object- Complex structures - •
array- Lists
With type specifications:
output:
- name: "zorgtoeslag_bedrag"
type: "amount"
type_spec:
unit: "eurocent"
description: "Het bedrag van de zorgtoeslag"
With temporal metadata:
output:
- name: "toetsingsinkomen"
type: "amount"
temporal:
type: "period"
period_type: "year"
Step 8: Interpret Conditions and Logic (Actions)
Convert legal conditions to operations:
Available Operations:
| Category | Operations |
|---|---|
| Arithmetic | ADD, SUBTRACT, MULTIPLY, DIVIDE, MIN, MAX |
| Comparison | EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL |
| Logical | AND, OR, NOT |
| Membership | IN, NOT_IN |
| Null check | NOT_NULL |
| Conditional | IF |
| Iteration | FOREACH |
| Date | SUBTRACT_DATE |
| String | CONCAT |
Common Legal Patterns → Operations:
| Legal Text | Operation |
|---|---|
| "heeft bereikt de leeftijd van 18 jaar" | GREATER_THAN_OR_EQUAL, subject: $leeftijd, value: 18 |
| "niet meer bedraagt dan X" | LESS_THAN_OR_EQUAL |
| "ten minste X" | GREATER_THAN_OR_EQUAL |
| "indien ... en ..." | AND with values array |
| "indien ... of ..." | OR with values array |
| "niet ..." | NOT |
| "gelijk aan" | EQUALS |
Simple comparison:
actions:
- output: "is_volwassen"
operation: "GREATER_THAN_OR_EQUAL"
subject: "$leeftijd"
value: 18
Multiple conditions (AND/OR):
actions:
- output: "voldoet_aan_voorwaarden"
operation: "AND"
values:
- operation: "EQUALS"
subject: "$is_verzekerd"
value: true
- operation: "GREATER_THAN_OR_EQUAL"
subject: "$leeftijd"
value: 18
Conditional with if-then-else (using conditions array):
actions:
- output: "toeslag_percentage"
conditions:
- test:
operation: "EQUALS"
subject: "$huishouden_type"
value: "alleenstaand"
then: 100
else: 50
Conditional with IF operation:
actions:
- output: "resultaat"
operation: "IF"
test:
operation: "GREATER_THAN"
subject: "$inkomen"
value: 50000
then: 0
else: "$berekend_bedrag"
Calculation chain:
actions:
- output: "premie_basis"
operation: "MULTIPLY"
values:
- "$standaardpremie"
- "$percentage"
- output: "premie_na_korting"
operation: "SUBTRACT"
values:
- "$premie_basis"
- "$korting"
Date calculation:
actions:
- output: "leeftijd"
operation: "SUBTRACT_DATE"
values:
- "$peildatum"
- "$geboortedatum"
Resolve from ministeriele regeling:
actions:
- output: "standaardpremie"
resolve:
type: "ministeriele_regeling"
output: "standaardpremie"
match:
output: "jaar"
value: "$jaar"
Step 9: Add Legal Basis (Traceability)
For important computations, add legal_basis to trace back to the law:
actions:
- output: "heeft_recht"
operation: "AND"
values:
- "$is_verzekerd"
- "$is_volwassen"
legal_basis:
law: "Wet op de zorgtoeslag"
bwb_id: "BWBR0018451"
article: "2"
paragraph: "1"
url: "https://wetten.overheid.nl/BWBR0018451#Artikel2"
explanation: "Lid 1 bepaalt de voorwaarden voor recht op zorgtoeslag"
Step 10: Set Produces (Legal Character)
If the article produces a formal decision:
execution:
produces:
legal_character: "BESCHIKKING" # Individual decision
decision_type: "TOEKENNING" # Grant/approval
Legal character options:
- •
BESCHIKKING- Individual administrative decision - •
TOETS- Check/verification - •
WAARDEBEPALING- Value determination - •
BESLUIT_VAN_ALGEMENE_STREKKING- General binding decision
Decision type options:
- •
TOEKENNING- Grant - •
AFWIJZING- Rejection - •
GOEDKEURING- Approval - •
AANSLAG- Tax assessment - •
ALGEMEEN_VERBINDEND_VOORSCHRIFT- General binding regulation - •
BELEIDSREGEL- Policy rule - •
VOORBEREIDINGSBESLUIT- Preparatory decision - •
ANDERE_HANDELING- Other action
Step 11: Complete Example
Legal text:
Artikel 2 1. Een persoon heeft recht op zorgtoeslag indien hij: a. de leeftijd van 18 jaar heeft bereikt; b. verzekerd is ingevolge de Zorgverzekeringswet.
Complete machine_readable section:
machine_readable:
competent_authority:
name: "Belastingdienst/Toeslagen"
requires:
- law: "Zorgverzekeringswet"
values: ["is_verzekerd"]
execution:
produces:
legal_character: "BESCHIKKING"
decision_type: "TOEKENNING"
parameters:
- name: "bsn"
type: "string"
required: true
description: "Burgerservicenummer"
- name: "peildatum"
type: "date"
required: true
description: "Datum waarop het recht wordt getoetst"
input:
- name: "geboortedatum"
type: "date"
source:
output: "geboortedatum"
description: "Geboortedatum uit BRP"
- name: "is_verzekerd"
type: "boolean"
source:
# TODO: Implement zorgverzekeringswet
regulation: "zorgverzekeringswet"
output: "is_verzekerd"
parameters:
bsn: "$bsn"
output:
- name: "leeftijd"
type: "number"
type_spec:
unit: "years"
- name: "heeft_recht"
type: "boolean"
description: "Geeft aan of de persoon recht heeft op zorgtoeslag"
actions:
- output: "leeftijd"
operation: "SUBTRACT_DATE"
values:
- "$peildatum"
- "$geboortedatum"
legal_basis:
article: "2"
paragraph: "1"
explanation: "Leeftijd bepaald op peildatum"
- output: "heeft_recht"
operation: "AND"
values:
- operation: "GREATER_THAN_OR_EQUAL"
subject: "$leeftijd"
value: 18
- operation: "EQUALS"
subject: "$is_verzekerd"
value: true
legal_basis:
article: "2"
paragraph: "1"
explanation: "Voorwaarden a en b van lid 1"
Step 12: Apply Changes to YAML
For each article that needs a machine_readable section:
- •Use the Edit tool to add the section after the
urlfield - •Maintain proper YAML indentation (2 spaces per level)
- •Add comments for TODOs and clarifications
- •Convert all monetary amounts to eurocent
Step 13: Validate Against Schema and Lint
Before reporting, validate the updated YAML:
Step 13a: Run validation (linting + schema)
just validate {LAW_FILE_PATH}
This checks for:
- •Line length (max 125 chars - wrap long text!)
- •Proper indentation
- •Quote usage
- •YAML formatting
- •JSON schema compliance
Step 13b: Run schema validation only
just validate {LAW_FILE_PATH}
This validates against the JSON schema (serde + JSON Schema Draft-07).
If validation fails:
- •Review schema errors carefully
- •Common issues with machine_readable sections:
- •Missing required
outputfield in source - •Wrong operation types (check enum values)
- •Missing required fields in parameters/input/output
- •Incorrect nesting or indentation
- •Missing required
- •Fix errors and re-validate
- •Continue until both lint and validation pass
Step 14: Reverse Validation (Hallucination Check)
After schema validation passes, verify that every element in the machine_readable section can be traced back to the original legal text.
For each element, check:
- •
Definitions/Constants:
- •Is this value explicitly mentioned in the article text?
- •If NOT → Remove it from the YAML
- •If needed for logic but not in text → Add to "Assumptions" in report
- •
Input fields:
- •Is this data source referenced in the article text?
- •Look for phrases like "ingevolge", "bedoeld in", "genoemd in"
- •If NOT traceable → Remove or mark as assumption
- •
Output fields:
- •Does the article actually produce this output?
- •Is it stated or clearly implied in the legal text?
- •If NOT → Remove it
- •
Actions/Operations:
- •Does the legal text contain the logic for this operation?
- •Can you point to specific sentences that justify this action?
- •If NOT → Remove or simplify
- •
Conditions:
- •Are these conditions explicitly stated in the article?
- •Watch for invented edge cases not in the law
- •If NOT → Remove
Decision matrix:
| Traceable in text? | Needed for logic? | Action |
|---|---|---|
| YES | YES | Keep |
| YES | NO | Keep (may be informational) |
| NO | YES | Report as assumption |
| NO | NO | Remove |
Example check:
# Article text: "Een persoon heeft recht indien hij 18 jaar is" # GOOD - traceable: - output: "heeft_recht" # ✓ "heeft recht" in text operation: "GREATER_THAN_OR_EQUAL" subject: "$leeftijd" value: 18 # ✓ "18 jaar" in text # BAD - not traceable (hallucinated): - output: "woont_in_nederland" # ✗ Not mentioned in article operation: "EQUALS" subject: "$woonland" value: "NL" # → REMOVE THIS
After reverse validation:
- •Remove all non-traceable elements that aren't needed
- •Add "Assumptions" section to report for elements that are:
- •Not explicitly in text
- •But required to make the logic complete
- •These need user review
Step 15: Report Results
After successful validation:
- •
Count processed articles:
- •How many articles total?
- •How many now have machine_readable sections?
- •
List TODOs:
- •Which external laws need to be downloaded?
- •Any ambiguous interpretations?
- •
List Assumptions (from reverse validation):
- •Elements not explicitly in text but needed for logic
- •These require user verification
- •
Report to user:
Interpreted {LAW_NAME}
Articles processed: {TOTAL}
Made executable: {EXECUTABLE_COUNT}
Schema validation: PASSED
Reverse validation: PASSED
Assumptions (need review):
- Article 2: Added "peildatum" parameter (implied but not stated)
- Article 3: Assumed "inkomen" refers to toetsingsinkomen
TODOs remaining:
- Download and interpret: {external_law_1}
- Clarify calculation in article {X}
The law is now executable via the engine!
Common Patterns
Pattern 1: Age Check
input:
- name: "geboortedatum"
type: "date"
source:
output: "geboortedatum"
description: "Geboortedatum uit BRP"
actions:
- output: "leeftijd"
operation: "SUBTRACT_DATE"
values:
- "$peildatum"
- "$geboortedatum"
- output: "is_volwassen"
operation: "GREATER_THAN_OR_EQUAL"
subject: "$leeftijd"
value: 18
Pattern 2: Income Threshold
definitions:
INKOMENSGRENS:
value: 7954700 # € 79.547 in eurocent
input:
- name: "toetsingsinkomen"
type: "amount"
source:
regulation: "awir"
output: "toetsingsinkomen"
parameters:
bsn: "$bsn"
jaar: "$jaar"
actions:
- output: "onder_inkomensgrens"
operation: "LESS_THAN_OR_EQUAL"
subject: "$toetsingsinkomen"
value: "$INKOMENSGRENS"
Pattern 3: Multiple Conditions (AND)
actions:
- output: "voldoet_aan_voorwaarden"
operation: "AND"
values:
- operation: "EQUALS"
subject: "$is_verzekerd"
value: true
- operation: "GREATER_THAN_OR_EQUAL"
subject: "$leeftijd"
value: 18
- operation: "EQUALS"
subject: "$woont_in_nederland"
value: true
Pattern 4: Calculation Chain
actions:
- output: "premie_basis"
operation: "MULTIPLY"
values:
- "$standaardpremie"
- "$percentage"
- output: "premie_na_korting"
operation: "SUBTRACT"
values:
- "$premie_basis"
- "$korting"
- output: "premie_finaal"
operation: "MAX"
values:
- 0
- "$premie_na_korting"
Pattern 5: Conditional Value
actions:
- output: "vermogensgrens"
conditions:
- test:
operation: "EQUALS"
subject: "$heeft_partner"
value: true
then: "$VERMOGENSGRENS_PARTNERS"
else: "$VERMOGENSGRENS_ALLEENSTAANDE"
Pattern 6: Lookup from Ministeriele Regeling
actions:
- output: "standaardpremie"
resolve:
type: "ministeriele_regeling"
output: "standaardpremie"
match:
output: "jaar"
value: "$jaar"
Tips for Success
- •Be aggressive: Generate complete logic even if uncertain
- •Use descriptive names:
toetsingsinkomennotincome - •Always eurocent: Never use decimal euro amounts
- •Check for existing laws: Use Glob to search
regulation/nl/ - •Break down complex logic: Multiple simple actions > one complex action
- •Add descriptions: Help future readers understand the logic
- •Mark TODOs clearly: Use
# TODO:comments for missing refs - •Validate types: Ensure type consistency (boolean, number, string, date, amount)
- •Document assumptions: Add comments when interpretation is unclear
- •Add legal_basis: Trace important computations back to the law text
Error Handling
If legal text is ambiguous:
- •Make best guess with TODO comment
- •Explain uncertainty to user
- •Suggest manual review
If external law not found:
- •Create TODO placeholder in source
- •Add to list of missing dependencies
- •Continue with other articles
If operation unclear:
- •Use simpler operations
- •Break into multiple steps
- •Add explanatory comments