| name | law-machine-readable-interpreter |
| description | Interprets legal text in regelrecht YAML files and generates machine-readable execution logic with parameters, operations, outputs, and cross-law references. Use when user wants to make a law executable, add machine_readable sections, or interpret legal articles for computational execution. |
| allowed-tools | Read, Edit, Grep, Glob |
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 authorityrequires- dependencies on other laws/regulationsdefinitions- constants and fixed valuesexecutionsection containing:produces- legal character and decision typeparameters- caller-provided inputsinput- data from other sourcesoutput- what this article producesactions- 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
textfieldIdentify 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 numberpeildatum(date) - Reference datejaar(number) - Yearbedrag(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 valuesnumber- Numeric valuesboolean- True/falseamount- Monetary values (in eurocent)date- Date valuesobject- Complex structuresarray- 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 decisionTOETS- Check/verificationWAARDEBEPALING- Value determinationBESLUIT_VAN_ALGEMENE_STREKKING- General binding decision
Decision type options:
TOEKENNING- GrantAFWIJZING- RejectionGOEDKEURING- ApprovalAANSLAG- Tax assessmentALGEMEEN_VERBINDEND_VOORSCHRIFT- General binding regulationBELEIDSREGEL- Policy ruleVOORBEREIDINGSBESLUIT- Preparatory decisionANDERE_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 YAML linting
uv run yamllint {LAW_FILE_PATH}
This checks for:
- Line length (max 125 chars - wrap long text!)
- Proper indentation
- Quote usage
- YAML formatting
Step 13b: Run schema validation
uv run python script/validate.py {LAW_FILE_PATH}
This validates against the JSON schema.
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