Skip to content

Cell-Level Legislative Grounding

Linking legislation to decision logic at decision-table cell granularity โ€” finer than DMN's native decision-level knowledgeSource, and finer than CPRMV's rule-level extends/ruleType/confidence.

Shipped in v2026.08.0. See the Changelog for the release summary and Namespace & Property Reference for the property definitions.


Why cell granularity

A decision table rule is a row; each of its cells is a separate condition or outcome, and in practice each cell can rest on a different article. Take a rule that qualifies an applicant: one cell tests residence, another tests household composition, a third sets the resulting entitlement. Those three conditions routinely come from three different provisions.

The existing linking levels cannot express that:

Level Mechanism Granularity
DMN native <knowledgeSource> + authorityRequirement Whole decision
CPRMV cprmv:isBasedOn on a cprmv:Rule Whole rule (row)
This cprmv:hasPart โ†’ per-cell cprmv:Rule One cell

Cell-level grounding adds the third row without replacing either of the first two โ€” a DMN keeps its knowledgeSource links, and rule-level isBasedOn still emits as before.

The implementation spans three layers: attributes carried in the DMN file, the reader that extracts them, and the TTL emitter that publishes them.


Layer 1 โ€” DMN attributes

Grounding travels inside the DMN file, on the <inputEntry> and <outputEntry> elements themselves, so it survives any tool that round-trips the XML. Two namespaces are declared on <dmn:definitions>:

<dmn:definitions
    xmlns:cprmv="https://standaarden.open-regels.nl/standards/cprmv/0.4.1#"
    xmlns:dct="http://purl.org/dc/terms/">

Each grounded cell then carries up to five attributes โ€” three that ground it, and two that name the concept it is grounded in:

Attribute Meaning
dct:source The annotation or concept id this cell was derived from
cprmv:sourceQuote The quoted text fragment the grounding rests on
cprmv:isBasedOn The citation โ€” a full URL, or a bare JuriConnect (JCI) reference
cprmv:conceptName The name of the concept the cell is grounded in
cprmv:conceptType That concept's type, for example Juridisch relevant feit
<inputEntry id="ie1a"
            dct:source="apt-1"
            cprmv:sourceQuote="Woonadres"
            cprmv:isBasedOn="https://lokaleregelgeving.overheid.nl/CVDR1/1"
            cprmv:conceptName="natuurlijk persoon heeft woonadres"
            cprmv:conceptType="Juridisch relevant feit">
  <dmn:text>"Amsterdam"</dmn:text>
</inputEntry>

The two concept attributes come from the knowledge-domain export (HvA_annotaties.xml), where a <textannotation> carries @concept pointing at a <concept>. The concept is the authority for the name โ€” every concept in that export has one, where only a tenth of the annotations do โ€” so both attributes describe the concept however the cell happens to reference it.

Cells carrying none of the three grounding attributes are ungrounded, which is the normal case โ€” wildcards and cross-decision references have nothing to cite. A concept name or type on an ungrounded cell is not published: there is no cell resource to carry it.

Compound cells

A single cell can be a compound FEEL expression whose conjuncts each need their own citation. Two encodings were tested directly against Operaton: repeatable child elements were rejected outright (the DMN schema's unary-tests content model has no extension point for them), while a numbered attribute family deploys cleanly with no upper bound. That is what shipped:

<inputEntry id="ie2b"
            dct:source1="concept-A"
            cprmv:isBasedOn1="jci1.3:c:BWBR0002&amp;artikel=9"
            dct:source2="concept-B"
            cprmv:sourceQuote2="tweede grondslag">
  <dmn:text>&gt;= 18 and &lt; 67</dmn:text>
</inputEntry>

The reader scans dct:source1/cprmv:sourceQuote1/cprmv:isBasedOn1, then โ€ฆ2, and so on, stopping at the first index where none of the three is present. The concept attributes are not numbered: they describe the cell as a whole, so a compound cell carries at most one cprmv:conceptName and one cprmv:conceptType.

A quoteless source is read as a concept id

The kind of source is inferred from the presence of a quote: a dct:source (numbered or not) with a matching cprmv:sourceQuote is an annotation id, and one without is a concept id. An annotation id therefore cannot be recorded without its quote โ€” it would be published as ?type=CPT&id=<annotation id>, which does not resolve. Where no quote is available, record the citation without the annotation id.

Grounding attributes are inert to evaluation

They are extension attributes in foreign namespaces, so Operaton ignores them. This was confirmed empirically rather than assumed: after grounding was applied, the Amsterdam model's 100-case rule-coverage suite (one case per rule) still passed 100/100 against the redeployed model.


Layer 2 โ€” Reading the DMN

src/utils/dmnHelpers.js extracts groundings while it parses the decision table. extractCellGroundings(entryEl) returns an array of { source, sourceQuote, isBasedOn } โ€” empty for an ungrounded cell, one entry for the shorthand form, N for the numbered form. extractCell(entryEl) wraps that together with the cell's own id, its FEEL text, and the concept's conceptName and conceptType (each null when absent), and extractRulesFromDMN attaches the results to each rule as inputEntries and outputEntries.

The cell's id matters: it is the stable key the published cell URI is built from. The iKnow re-export gives every <inputEntry> and <outputEntry> its own id for exactly this reason โ€” keying off the id rather than column position keeps URIs stable when columns are reordered. A positional fallback covers sources that emit no per-cell ids.

The namespace-agnostic lookup

Building this layer surfaced a defect that predates cell grounding entirely. Every selector-based DMN lookup in the codebase silently matched nothing against a real, dmn:-prefixed file:

// Matches only elements in the null namespace โ€” i.e. an unprefixed DMN.
xmlDoc.querySelectorAll('decision');

CSS type selectors match on the pair (namespace, local name), and an unprefixed selector implies the null namespace. Against a real DMN โ€” where every element is dmn:decision โ€” the selector returns nothing. Cell-level grounding could never have worked, and neither did the pre-existing rule-level export.

The fix is a pair of helpers that match on local name regardless of prefix:

function queryAllLocal(root, localName) { /* โ€ฆ */ }
function queryLocal(root, localName)    { /* โ€ฆ */ }

Every DMN lookup now goes through them.


Layer 3 โ€” Emitting the TTL

generateDmnSection() in src/utils/ttlGenerator.js emits a cprmv:hasPart list of per-cell resources on each cprmv:DecisionRule:

<โ€ฆ/rules/DecisionRule_1> a cpsv:Rule, cprmv:DecisionRule ;
    dct:identifier "DecisionRule_1" ;
    cprmv:hasPart ( <โ€ฆ/rules/DecisionRule_1/cell/ie1a>
                    <โ€ฆ/rules/DecisionRule_1/cell/ie1b> ) .

URI scheme

Resource URI cprmv:id
Cell {ruleUri}/cell/{cellId} {ruleId}-cell-{cellId}
Compound sub-grounding {cellUri}/grounding/{n} {ruleId}-cell-{cellId}-grounding-{n}
Minted concept {serviceUri}/concepts/{sourceId} {sourceId}

Two kinds of grounding

A cell with a sourceQuote grounds directly โ€” the quote and its citation are emitted on the cell resource:

<โ€ฆ/rules/DecisionRule_1/cell/ie1a> a cprmv:Rule ;
    cprmv:id "DecisionRule_1-cell-ie1a" ;
    dct:source <https://hva.pna-web.com/hva/?type=APT&id=apt-1> ;
    cprmv:sourceQuote "Woonadres" ;
    cprmv:isBasedOn <https://lokaleregelgeving.overheid.nl/CVDR1/1> .

A cell with only a dct:source points instead at a shared concept resource, minted once and reused by URI wherever it recurs:

<โ€ฆ/rules/DecisionRule_1/cell/ie1b> a cprmv:Rule ;
    cprmv:id "DecisionRule_1-cell-ie1b" ;
    cprmv:isBasedOn <โ€ฆ/concepts/cpt-shared> .

<โ€ฆ/concepts/cpt-shared> a cprmv:Rule ;
    cprmv:id "cpt-shared" ;
    dct:source <https://hva.pna-web.com/hva/?type=CPT&id=cpt-shared> .

Deduplication is not cosmetic โ€” a decision's own authorityRequirement and a rule's output cell were verified to resolve to the same underlying concept, so minting once and referencing by URI is what keeps the graph joinable.

A compound cell composes the two forms with a nested list:

<โ€ฆ/cell/ie2b> a cprmv:Rule ;
    cprmv:id "DecisionRule_2-cell-ie2b" ;
    cprmv:hasPart ( <โ€ฆ/cell/ie2b/grounding/1> <โ€ฆ/cell/ie2b/grounding/2> ) .

The concept's name and type

A cell carrying cprmv:conceptName or cprmv:conceptType emits them as skos:prefLabel and dct:type on the cell resource, ahead of its grounding:

<โ€ฆ/rules/DecisionRule_1/cell/ie1a> a cprmv:Rule ;
    cprmv:id "DecisionRule_1-cell-ie1a" ;
    skos:prefLabel "natuurlijk persoon heeft woonadres" ;
    dct:type "Juridisch relevant feit" ;
    dct:source <https://hva.pna-web.com/hva/?type=APT&id=apt-1> ;
    cprmv:sourceQuote "Woonadres" ;
    cprmv:isBasedOn <https://lokaleregelgeving.overheid.nl/CVDR1/1> .

On a compound cell they stay on the parent resource, next to its cprmv:hasPart list, and are not repeated on the individual groundings.

On import

Every resource this layer mints โ€” cell, sub-grounding, concept and citation stub โ€” is typed cprmv:Rule, but none of them is a policy rule. On import the parser treats only subjects under CPRMV_RULE_BASE (https://cprmv.open-regels.nl/rules/) as policy rules; anything else typed cprmv:Rule is re-typed as a DMN entity and preserved verbatim in the DMN block, so the grounding survives a round trip intact. See Vocabulary Configuration.

Citation resolution

cprmv:isBasedOn values are passed through when they are already full URLs, and resolved against https://wetten.overheid.nl/ when they are bare JuriConnect references โ€” the same construction the rule-level emission already used.


SHACL conformance

Live validation against a real published .ttl surfaced two RuleShape violations. Both are fixed, and both are worth knowing about before extending the emitter:

cprmv:id is mandatory on every cprmv:Rule (sh:minCount 1). Cell resources originally emitted dct:identifier โ€” a property RuleShape does not check at all, so the constraint failed while the data looked fine โ€” and concept resources carried no identifier whatsoever. Every resource this layer mints now emits cprmv:id, sub-groundings included.

cprmv:isBasedOn has sh:class cprmv:Rule, so its object must itself be typed cprmv:Rule. Concept targets already were. A direct external citation URI โ€” CVDR, wetten.overheid.nl โ€” was not, so every such reference failed the shape. Each distinct citation URI now gets a minted, deduplicated stub:

<https://lokaleregelgeving.overheid.nl/CVDR1/1> a cprmv:Rule ;
    cprmv:id "https://lokaleregelgeving.overheid.nl/CVDR1/1" .

The cprmv:id falls back to the URI itself โ€” there is no other identifier available for an external resource this graph does not otherwise describe.

No stub is minted for a rule the document already emits. When a citation points at a rule the same export publishes in its own Rules section, that subject is already typed cprmv:Rule there with a real cprmv:id, and a stub would assert a second, contradictory one โ€” the URI. Observed rather than hypothesised: twelve of the normenbrief export's 216 cprmv:Rule subjects carried two cprmv:id values before the fix. The generator collects the rule URIs its Rules section will emit, using that section's own emission condition, and skips the stub for any citation among them. A citation to a rule this graph does not otherwise describe still gets its stub, or cprmv:isBasedOn would point at an untyped subject and fail sh:class. ttlGenerator.citationStub.test.js asserts both directions.

No new SHACL shapes were needed: cell resources are ordinary cprmv:Rule instances composed with the existing cprmv:hasPart, so the recursion is covered by shapes that already exist.


Verification

  • 18 tests in src/utils/ttlGenerator.cellGrounding.test.js cover emission, concept dedup, compound nesting, the concept name and type, and the conformance rules; further tests in dmnHelpers.test.js cover the reader. See Testing.
  • End-to-end against the real Amsterdam HvA model: grounding applied, deployed, evaluated, and published live through the editor with zero DMN evaluation issues and zero SHACL validation issues. With the concept names and types added, the model still deploys to Operaton with all 25 decisions, and the generated Turtle validates against all three SHACL layers.
  • The 100-case rule-coverage suite (one case per rule) passes 100/100 unchanged after grounding, confirming the attributes stay inert to FEEL evaluation. MC/DC โ€” a pair of cases per condition โ€” is the candidate method DMN-AP NL proposes for ยง4.4, to be tested on Participatiewet art. 36.

The Amsterdam model

In examples/organizations/amsterdam/, all six grounded cells on Rule 1 carry the concept's name and type, taken from the @concept link in HvA_annotaties.xml. Following that link also gives _outputentry_15 a citation it did not have (artikel 3). Citations added by hand earlier are never overwritten. _inputentry_152 resolves to two annotations citing different articles (1 and 4); both are recorded through the numbered family rather than chosen between, so the cell emits a three-part cprmv:hasPart โ€” the concept plus one citation each โ€” and the choice is left to the modellers.

Grounding the rest of the decision requirements graph is not mechanical. Of 762 cells, 385 are groundable, under 80 columns; matching the columns to concepts gives 0 exact matches and 10 clear winners, leaving roughly 70% of groundable cells with no defensible automatic answer. So testCases/concept-grounding-worklist.csv lists up to three ranked candidates per column, with an empty confirmed_concept_id column for a modeller to fill in, and no grounding is applied from it: a wrong citation on a condition it does not govern is worse than none.


Open questions

  • Cross-export id stability. Cell URIs key off <inputEntry>/ <outputEntry> ids. Whether those ids survive a fresh export from the authoring tool is not yet established โ€” if they do not, republishing after a re-export would mint new cell URIs for unchanged logic.
  • ruleType / rulesetType on cell resources. Flagged by the CPRMV spec owner as still under consideration; cell resources currently carry neither.
  • CPRMV ReferenceMethod. CPRMV 0.4.2 introduces ReferenceMethod as a pluggable concept with JCI and ELI as known methods, rather than mandating one citation grammar. The emission here predates it and resolves JCI directly; aligning with ReferenceMethod is a future change. See External Standards.