a v0.2.0-ish version

This commit is contained in:
felix
2026-09-26 23:24:13 +02:00
parent 2953105d30
commit ab5de759ad
6 changed files with 79 additions and 20 deletions

View File

@@ -29,7 +29,7 @@
@prefix person: <http://www.w3.org/ns/person#> .
@prefix mandaat: <http://data.vlaanderen.be/ns/mandaat#> .
@prefix besluit: <http://data.vlaanderen.be/ns/besluit#> .
@prefix generiek: <http://data.vlaanderen.be/ns/generiek#> .
@prefix generiek: <https://data.vlaanderen.be/ns/generiek#> .
@prefix lblodlg: <http://data.lblod.info/vocabularies/leidinggevenden/> .
@prefix ch: <http://data.lblod.info/vocabularies/contacthub/> .
@prefix ere: <http://data.lblod.info/vocabularies/erediensten/> .
@@ -58,7 +58,7 @@
sh:declare [ sh:prefix "ext" ; sh:namespace "http://mu.semte.ch/vocabularies/ext/"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "regorg" ; sh:namespace "http://www.w3.org/ns/regorg#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "geo" ; sh:namespace "http://www.opengis.net/ont/geosparql#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "generiek" ; sh:namespace "http://data.vlaanderen.be/ns/generiek#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "generiek" ; sh:namespace "https://data.vlaanderen.be/ns/generiek#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "lblodlg" ; sh:namespace "http://data.lblod.info/vocabularies/leidinggevenden/"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "dct" ; sh:namespace "http://purl.org/dc/terms/"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "rdfs" ; sh:namespace "http://www.w3.org/2000/01/rdf-schema#"^^xsd:anyURI ] ;

View File

@@ -39,7 +39,7 @@
@prefix person: <http://www.w3.org/ns/person#> .
@prefix mandaat: <http://data.vlaanderen.be/ns/mandaat#> .
@prefix besluit: <http://data.vlaanderen.be/ns/besluit#> .
@prefix generiek: <http://data.vlaanderen.be/ns/generiek#> .
@prefix generiek: <https://data.vlaanderen.be/ns/generiek#> .
@prefix lblodlg: <http://data.lblod.info/vocabularies/leidinggevenden/> .
@prefix ch: <http://data.lblod.info/vocabularies/contacthub/> .
@prefix code: <http://lblod.data.gift/vocabularies/organisatie/> .
@@ -206,7 +206,7 @@ ent:LeidinggevendeBestuursfunctie
sh:node ent:ContactPunt ] ;
sh:property [ sh:path [ sh:inversePath lblodlg:heeftBestuursfunctie ] ; sh:name "in bestuursorgaan" ;
sh:description "Het bestuursorgaan waar deze functie bij hoort" ;
sh:node ent:Bestuursorgaan ] .
sh:node ent:BestuursorgaanInTijd ] .
ent:Functionaris
a sh:NodeShape ;

View File

@@ -37,7 +37,7 @@
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix schema: <http://schema.org/> .
@prefix organisatie: <https://data.vlaanderen.be/ns/organisatie#> .
@prefix generiek: <http://data.vlaanderen.be/ns/generiek#> .
@prefix generiek: <https://data.vlaanderen.be/ns/generiek#> .
@prefix lblodgeneriek: <https://data.lblod.info/vocabularies/generiek/> .
@prefix code: <http://lblod.data.gift/vocabularies/organisatie/> .
@prefix ext: <http://mu.semte.ch/vocabularies/ext/> .
@@ -66,7 +66,7 @@
sh:declare [ sh:prefix "prov" ; sh:namespace "http://www.w3.org/ns/prov#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "schema" ; sh:namespace "http://schema.org/"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "organisatie" ; sh:namespace "https://data.vlaanderen.be/ns/organisatie#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "generiek" ; sh:namespace "http://data.vlaanderen.be/ns/generiek#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "generiek" ; sh:namespace "https://data.vlaanderen.be/ns/generiek#"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "lblodgeneriek" ; sh:namespace "https://data.lblod.info/vocabularies/generiek/"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "code" ; sh:namespace "http://lblod.data.gift/vocabularies/organisatie/"^^xsd:anyURI ] ;
sh:declare [ sh:prefix "ext" ; sh:namespace "http://mu.semte.ch/vocabularies/ext/"^^xsd:anyURI ] ;

View File

@@ -25,18 +25,55 @@ One `sh:NodeShape` per term:
condition (`sh:hasValue`, `sh:in`, `sh:minCount`/`sh:maxCount`,
`sh:min/maxInclusive`/`sh:min/maxExclusive`, `sh:pattern` + `sh:flags`,
`rep:anyOf`). A path with no condition is refused by the validator.
Optional `rep:where` (see below).
- **Column** — a list item: `sh:path` (hops, or `rep:self` for the row's own
subject), `rdfs:label` (unique, required), optional `rep:collect`
(`sh:groupConcat` default, `rep:row`, `sh:min`, `sh:max`), optional
`sh:separator`. No constraints on a column — `sh:hasValue` and friends
belong in filters.
`sh:separator`, optional `sh:nodeKind sh:IRI` (the cell shows the URI of
the node the path ends on; only then may the path end on a link). No
constraints on a column — `sh:hasValue` and friends belong in filters, or
in the column's `rep:where`.
## Conditions on the way: rep:where
A filter tests only the end of its own path, and two filters never share a
node. `rep:where` tests a node in the middle of a path. It is written like a
filter, with its path from the row. The steps it has in common with its
filter or column are the same nodes; its condition tests where its own path
ends. On a filter it drops rows; on a column it drops values from the cell,
never rows. Several `rep:where` on one filter or column all apply.
```turtle
[ sh:path ( org:holds [ sh:inversePath lblodlg:heeftBestuursfunctie ]
generiek:isTijdspecialisatieVan besluit:bestuurt
[ sh:inversePath besluit:bestuurt ] [ sh:inversePath generiek:isTijdspecialisatieVan ]
org:hasPost [ sh:inversePath org:holds ]
mandaat:isBestuurlijkeAliasVan foaf:familyName ) ;
rdfs:label "Burgemeester" ;
rep:where [ sh:path ( org:holds [ sh:inversePath lblodlg:heeftBestuursfunctie ]
generiek:isTijdspecialisatieVan besluit:bestuurt
[ sh:inversePath besluit:bestuurt ] [ sh:inversePath generiek:isTijdspecialisatieVan ]
org:hasPost org:role ) ;
sh:hasValue <http://data.vlaanderen.be/id/concept/BestuursfunctieCode/5ab0e9b8a3b2ca7c5e000013> ] ]
```
Per leidinggevende: the names of the mandatarissen whose mandate, in an
organ of the same bestuurseenheid, has the role Burgemeester. The condition
shares seven steps with the column, so it tests the mandate. `validate_spec`
says so: `rep:where 1 on column "Burgemeester" applies at "mandaat", after
step 7`.
On a filter with `sh:maxCount 0`, the filter and its `rep:where` together
become "none such".
## What it cannot say (and the runner enforces anyway)
A shapes graph cannot express "a column must end on a value field" (that
needs the profile), "a path resolves hop by hop" (needs the profile walk),
A shapes graph cannot express "a column must end on a value field, or on a
link with `sh:nodeKind sh:IRI`" (that needs the profile), "a path resolves hop by hop" (needs the profile walk),
"at most 24 anyOf terms" (cardinality over list contents), or "anyOf takes
words, not URIs" (term typing). Those live in the service's `src/runner/check.js`.
words, not URIs" (term typing), or "a rep:where shares at least one step with
its filter or column", or "a date or number condition ends on a value, not on
a link". Those live in the service's `src/runner/check.js`.
## Not yet
@@ -62,6 +99,6 @@ graph must match).
## Validating a spec against these shapes
The shapes are self-describing documentation. `validate_spec` (the MCP tool)
The shapes are self-describing documentation. `validate_spec` (the conversion tool)
does more: it walks every path through the mounted profile and refuses
what a shapes graph cannot see. Both together are the spec contract.
what a shapes graph cannot see. Both together are the spec contract.

View File

@@ -2,7 +2,8 @@
# Reference definition of the spec format: a machine-readable
# contract that doubles as documentation. validate_spec does more than this
# graph can express: it walks every path through the mounted profile and
# refuses what shapes alone cannot see (columns ending on a link, unresolved
# refuses what shapes alone cannot see (columns ending on a link without
# sh:nodeKind sh:IRI, unresolved
# hops, anyOf term limits, filters without a condition).
#
# Not yet: sh:count, sh:sum, sh:orderBy, sh:limit, sh:offset, rep:graph.
@@ -139,6 +140,15 @@ rep:FilterShape
sh:name "een van deze woorden" ;
sh:maxCount 1 ;
] ;
# rep:where: a condition on a node this filter walks through, written as a
# filter from the row. The steps it shares with this filter are the same
# nodes. check.js refuses one that shares no step, and one nested in
# another (whereConditions).
sh:property [
sh:path rep:where ;
sh:name "voorwaarde onderweg" ;
sh:node rep:FilterShape ;
] ;
sh:property [
sh:path rdfs:label ;
sh:name "label" ;
@@ -146,7 +156,11 @@ rep:FilterShape
] .
# A column: path, label, collect mode, separator. No constraints here;
# check.js refuses a constraint inside rep:columns (columnPure).
# check.js refuses a constraint inside rep:columns (columnPure). The one
# exception is sh:nodeKind sh:IRI: the cell shows the URI of the node the
# path ends on, and only then may the path end on a link. A condition
# on a node the column walks through goes in rep:where; it drops values from
# the cell, never rows.
rep:ColumnShape
a sh:NodeShape ;
sh:property [
@@ -173,6 +187,17 @@ rep:ColumnShape
sh:name "scheidingsteken" ;
sh:datatype xsd:string ;
sh:maxCount 1 ;
] ;
sh:property [
sh:path sh:nodeKind ;
sh:name "toon de URI" ;
sh:in ( sh:IRI ) ;
sh:maxCount 1 ;
] ;
sh:property [
sh:path rep:where ;
sh:name "voorwaarde onderweg" ;
sh:node rep:FilterShape ;
] .
# rep:collect takes exactly these values (SHACL Core groupConcat is the

View File

@@ -688,7 +688,7 @@ services:
# CHAT / NATURAL LANGUAGE REPORTS
################################################################################
natural-language-report:
image: lblod/natural-language-report-service:0.1.0
image: lblod/natural-language-report-service:0.2.0
build: ../natural-language-report-service/
volumes:
- ./data/files:/share
@@ -698,10 +698,7 @@ services:
CSV_SEPARATOR: ";"
SUBJECT_CHUNK_SIZE: "100"
ROW_LIMIT: "200000"
MAX_PATH_DEPTH: "8"
# The chat's turn answers in the background; full-organisation runs
# easily outgrow a minute.
RUN_TIMEOUT: "300"
MAX_PATH_DEPTH: "12"
INLINE_VALUES_MAX: "50"
VALUES_TTL: "3600"
labels: