diff --git a/config/report-profiles/mandaten.ttl b/config/report-profiles/mandaten.ttl index 464313d..6457be1 100644 --- a/config/report-profiles/mandaten.ttl +++ b/config/report-profiles/mandaten.ttl @@ -15,16 +15,32 @@ # mandaat:bindingStart/-Einde) -> mandaat (org:hasPost) -> mandataris # (org:holds) -> persoon (mandaat:isBestuurlijkeAliasVan). # -# Bestuursorgaan en bestuursorgaanInTijd delen besluit:Bestuursorgaan als -# sh:targetClass; rep:discriminator houdt ze in de seed uit elkaar (een -# orgaan in tijd heeft generiek:isTijdspecialisatieVan, een vast orgaan -# heeft die niet). Een spec kiest er één met rep:entity. +# Keten leidinggevenden: functionaris -> bestuursfunctie (org:holds) <- +# leidinggevend orgaan in tijd (lblodlg:heeftBestuursfunctie) -> +# leidinggevend bestuursorgaan (generiek:isTijdspecialisatieVan) -> +# bestuurseenheid (besluit:bestuurt). Elke leidinggevende functie heeft een +# eigen orgaan per eenheid (bv. "Algemeen directeur Gemeente Lille"), zonder +# mandaten. Naar de mandatarissen van dezelfde eenheid gaat het dus via de +# eenheid terug naar haar politieke organen. +# +# Vier entiteiten delen besluit:Bestuursorgaan als sh:targetClass; +# rep:discriminator houdt ze in de seed uit elkaar (alle voorwaarden gelden +# samen): een orgaan in tijd heeft generiek:isTijdspecialisatieVan, een vast +# orgaan niet; een leidinggevend orgaan heeft een classificatie uit +# ent:LeidinggevendeOrgaanClassificaties, een orgaan in tijd via zijn vaste +# orgaan. De classificatie beslist, niet of er een functie aan hangt: zo +# blijft een leidinggevend orgaan zonder functie zichtbaar als fout in de +# data. Een spec kiest er één met rep:entity. Let op: in zuivere SHACL moet +# elk besluit:Bestuursorgaan aan alle vier de shapes voldoen; +# rep:discriminator (een eigen uitbreiding) beperkt sh:targetClass. Zie de +# README van natural-language-report-service. # # Niet opgenomen (geen rapportvelden): harvest-velden en vendor-relaties. # Een generiek org:Post (posts) deelt org:role met het mandaat; hier wint # de lezing van het mandaat (bestuursfunctie). De eredienst-varianten # (EredienstMandataris) staan in erediensten.ttl. @prefix sh: . +@prefix rdf: . @prefix rdfs: . @prefix owl: . @prefix xsd: . @@ -71,11 +87,26 @@ sh:declare [ sh:prefix "xsd" ; sh:namespace "http://www.w3.org/2001/XMLSchema#"^^xsd:anyURI ] ; ] . +# De classificaties van leidinggevende organen, zoals de migraties ze +# gebruiken (bv. 2024/20241028125621). Eén lijst voor de rep:discriminator +# van de vier organen hieronder. +ent:LeidinggevendeOrgaanClassificaties + rdf:first ; # Algemeen directeur + rdf:rest ( + # Adjunct-algemeen directeur + # Financieel directeur + # Adjunct-financieel directeur + # Financieel beheerder + # Leidend ambtenaar + # Griffier + ) . + ent:Bestuursorgaan a sh:NodeShape ; rdfs:label "bestuursorgaan" ; sh:targetClass besluit:Bestuursorgaan ; rep:discriminator [ sh:path generiek:isTijdspecialisatieVan ; sh:maxCount 0 ] ; + rep:discriminator [ sh:path org:classification ; sh:in ent:LeidinggevendeOrgaanClassificaties ; sh:maxCount 0 ] ; sh:property [ sh:path besluit:bestuurt ; sh:name "bestuurt" ; sh:description "De bestuurseenheid die door dit orgaan bestuurd wordt" ; sh:node ent:Bestuurseenheid ] ; @@ -86,11 +117,28 @@ ent:Bestuursorgaan sh:description "De organen in tijd die bij dit vaste orgaan horen" ; sh:node ent:BestuursorgaanInTijd ] . +ent:LeidinggevendBestuursorgaan + a sh:NodeShape ; + rdfs:label "leidinggevend bestuursorgaan" ; + sh:targetClass besluit:Bestuursorgaan ; + rep:discriminator [ sh:path generiek:isTijdspecialisatieVan ; sh:maxCount 0 ] ; + rep:discriminator [ sh:path org:classification ; sh:in ent:LeidinggevendeOrgaanClassificaties ] ; + sh:property [ sh:path besluit:bestuurt ; sh:name "bestuurt" ; + sh:description "De bestuurseenheid waar deze leidinggevende functie bij hoort" ; + sh:node ent:Bestuurseenheid ] ; + sh:property [ sh:path org:classification ; sh:name "soort bestuursorgaan" ; + sh:description "Bv. algemeen directeur, financieel directeur" ; + sh:node ent:BestuursorgaanClassificatie ] ; + sh:property [ sh:path [ sh:inversePath generiek:isTijdspecialisatieVan ] ; sh:name "heeft tijdsspecialisaties" ; + sh:description "De leidinggevende organen in tijd die bij dit vaste orgaan horen" ; + sh:node ent:LeidinggevendBestuursorgaanInTijd ] . + ent:BestuursorgaanInTijd a sh:NodeShape ; rdfs:label "bestuursorgaanInTijd" ; sh:targetClass besluit:Bestuursorgaan ; rep:discriminator [ sh:path generiek:isTijdspecialisatieVan ; sh:minCount 1 ] ; + rep:discriminator [ sh:path ( generiek:isTijdspecialisatieVan org:classification ) ; sh:in ent:LeidinggevendeOrgaanClassificaties ; sh:maxCount 0 ] ; sh:property [ sh:path mandaat:bindingStart ; sh:name "binding start" ; sh:description "Start van de bestuursperiode; orgaan in tijd" ; sh:datatype xsd:dateTime ] ; @@ -102,7 +150,22 @@ ent:BestuursorgaanInTijd sh:node ent:Bestuursorgaan ] ; sh:property [ sh:path org:hasPost ; sh:name "bevat mandaat" ; sh:description "De mandaten die dit orgaan bevat" ; - sh:node ent:Mandaat ] ; + sh:node ent:Mandaat ] . + +ent:LeidinggevendBestuursorgaanInTijd + a sh:NodeShape ; + rdfs:label "leidinggevend bestuursorgaanInTijd" ; + sh:targetClass besluit:Bestuursorgaan ; + rep:discriminator [ sh:path ( generiek:isTijdspecialisatieVan org:classification ) ; sh:in ent:LeidinggevendeOrgaanClassificaties ] ; + sh:property [ sh:path mandaat:bindingStart ; sh:name "binding start" ; + sh:description "Start van de periode; leidinggevend orgaan in tijd" ; + sh:datatype xsd:dateTime ] ; + sh:property [ sh:path mandaat:bindingEinde ; sh:name "binding einde" ; + sh:description "Einde van de periode; leidinggevend orgaan in tijd" ; + sh:datatype xsd:dateTime ] ; + sh:property [ sh:path ; sh:name "is tijdspecialisatie van" ; + sh:description "Het leidinggevend vaste orgaan waarvan dit orgaan in tijd een periode is" ; + sh:node ent:LeidinggevendBestuursorgaan ] ; sh:property [ sh:path lblodlg:heeftBestuursfunctie ; sh:name "bevat bestuursfunctie" ; sh:description "De leidinggevende functies van dit orgaan, bv. secretaris" ; sh:node ent:LeidinggevendeBestuursfunctie ] . @@ -136,8 +199,8 @@ ent:Bestuurseenheid sh:description "De hoofdvestiging van de bestuurseenheid" ; sh:node ent:Vestiging ] ; sh:property [ sh:path [ sh:inversePath besluit:bestuurt ] ; sh:name "bestuursorganen" ; - sh:description "De bestuursorganen van deze bestuurseenheid" ; - sh:node ent:Bestuursorgaan ]. + sh:description "De bestuursorganen van deze bestuurseenheid: politiek of leidinggevend" ; + sh:or ( [ sh:node ent:Bestuursorgaan ] [ sh:node ent:LeidinggevendBestuursorgaan ] ) ]. ent:Mandaat a sh:NodeShape ; @@ -205,8 +268,11 @@ ent:LeidinggevendeBestuursfunctie sh:property [ sh:path schema:contactPoint ; sh:name "contact" ; 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:BestuursorgaanInTijd ] . + sh:description "Het leidinggevend orgaan in tijd waar deze functie bij hoort" ; + sh:node ent:LeidinggevendBestuursorgaanInTijd ] ; + sh:property [ sh:path [ sh:inversePath org:holds ] ; sh:name "bekleders" ; + sh:description "De functionarissen die deze functie bekleden" ; + sh:node ent:Functionaris ] . ent:Functionaris a sh:NodeShape ; @@ -244,12 +310,9 @@ ent:Persoon sh:datatype xsd:string ] ; sh:property [ sh:path persoon:gebruikteVoornaam ; sh:name "gebruikte voornaam" ; sh:datatype xsd:string ] ; - sh:property [ sh:path [ sh:inversePath mandaat:isBestuurlijkeAliasVan ] ; sh:name "mandatarissen" ; - sh:description "De mandatarissen van deze persoon" ; - sh:node ent:Mandataris ] ; - sh:property [ sh:path [ sh:inversePath mandaat:isBestuurlijkeAliasVan ] ; sh:name "functionarissen" ; - sh:description "De leidinggevende functies van deze persoon" ; - sh:node ent:Functionaris ] ; + sh:property [ sh:path [ sh:inversePath mandaat:isBestuurlijkeAliasVan ] ; sh:name "mandatarissen en functionarissen" ; + sh:description "De mandaten en de leidinggevende functies van deze persoon" ; + sh:or ( [ sh:node ent:Mandataris ] [ sh:node ent:Functionaris ] ) ] ; sh:property [ sh:path [ sh:inversePath org:heldBy ] ; sh:name "posities" ; sh:description "De posities (agent-in-positie) van deze persoon" ; sh:node ent:AgentInPositie ] . diff --git a/config/report-profiles/organisaties.ttl b/config/report-profiles/organisaties.ttl index b33e557..e67b757 100644 --- a/config/report-profiles/organisaties.ttl +++ b/config/report-profiles/organisaties.ttl @@ -389,10 +389,8 @@ ent:Werkingsgebied sh:property [ sh:path geo:sfWithin ; sh:name "ligt in" ; sh:node ent:Werkingsgebied ] ; sh:property [ sh:path [ sh:inversePath geo:sfWithin ] ; sh:name "bevat" ; - sh:node ent:Werkingsgebied ] ; - sh:property [ sh:path [ sh:inversePath geo:sfWithin ] ; sh:name "bestuurseenheden" ; - sh:description "De bestuurseenheden die in dit gebied liggen" ; - sh:node ent:Bestuurseenheid ] ; + sh:description "De gebieden en de bestuurseenheden die in dit gebied liggen" ; + sh:or ( [ sh:node ent:Werkingsgebied ] [ sh:node ent:Bestuurseenheid ] ) ] ; sh:property [ sh:path skos:exactMatch ; sh:name "exacte match" ; sh:node ent:Concept ] . diff --git a/config/report-profiles/report-spec-readme.md b/config/report-profiles/report-spec-readme.md deleted file mode 100644 index 7d4837d..0000000 --- a/config/report-profiles/report-spec-readme.md +++ /dev/null @@ -1,104 +0,0 @@ -# The report spec, as SHACL - -A spec is `rep:ReportSpec` + `sh:NodeShape`. The shape half selects -the subjects and is real SHACL; the `rep:` half says what the CSV holds, -which SHACL has no word for. This file is the spec format's own shapes graph: -a machine-readable definition that doubles as reference documentation. - -The namespace: - -``` -rep: → http://mu.semte.ch/vocabularies/reporting/ -``` - -File: `config/report-profiles/report-spec-shapes.ttl`. - -## What it defines - -One `sh:NodeShape` per term: - -- **ReportSpec** — the document. `dct:title`, `rep:profile` (which profile it - was written against), `sh:targetClass`, `rep:entity` (optional; picks the - shape when two entities share one `sh:targetClass`), `sh:property` (the - filters), `rep:columns` (the CSV, in order, an rdf:List). -- **Filter** — a `sh:property` on the ReportSpec: `sh:path` plus exactly one - 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`, 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 ] ] -``` - -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, 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), 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 - -`sh:count`, `sh:sum`, `sh:orderBy`, `sh:limit`, `sh:offset` (SHACL-AF names for when counting and ordering arrive), `rep:graph` (regex the subject's -graph must match). - -## Example - -```turtle - a rep:ReportSpec , sh:NodeShape ; - dct:title "Meldingen met bijlagen" ; - rep:profile ; - sh:targetClass meb:Submission ; - sh:property [ sh:path ( pav:createdBy skos:prefLabel ) ; - sh:hasValue "West-Vlaanderen" ] ; - rep:columns ( - [ sh:path rep:self ; rdfs:label "melding" ] - [ sh:path nmo:sentDate ; rdfs:label "datumVerstuurd" ] - [ sh:path ( prov:generated dct:hasPart nfo:fileName ) ; - rdfs:label "bestandsnamen" ; rep:collect rep:row ] - ) . -``` - -## Validating a spec against these shapes - -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. diff --git a/config/report-profiles/report-spec-shapes.ttl b/config/report-profiles/report-spec-shapes.ttl deleted file mode 100644 index 6497d2b..0000000 --- a/config/report-profiles/report-spec-shapes.ttl +++ /dev/null @@ -1,213 +0,0 @@ -# Shapes for the report spec itself (rep:ReportSpec). -# 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 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. -@prefix sh: . -@prefix rdf: . -@prefix rdfs: . -@prefix xsd: . -@prefix owl: . -@prefix dct: . -@prefix rep: . - - - a owl:Ontology ; - dct:title "Report spec shapes" ; - sh:prefixes [ - sh:declare [ sh:prefix "rep" ; sh:namespace "http://mu.semte.ch/vocabularies/reporting/"^^xsd:anyURI ] ; - sh:declare [ sh:prefix "sh" ; sh:namespace "http://www.w3.org/ns/shacl#"^^xsd:anyURI ] ; - sh:declare [ sh:prefix "rdf" ; sh:namespace "http://www.w3.org/1999/02/22-rdf-syntax-ns#"^^xsd:anyURI ] ; - sh:declare [ sh:prefix "rdfs"; sh:namespace "http://www.w3.org/2000/01/rdf-schema#"^^xsd:anyURI ] ; - sh:declare [ sh:prefix "dct" ; sh:namespace "http://purl.org/dc/terms/"^^xsd:anyURI ] ; - ] . - -rep:ReportSpecShape - a sh:NodeShape ; - rdfs:label "rapportspec" ; - sh:targetClass rep:ReportSpec ; - sh:property [ - sh:path dct:title ; - sh:name "titel" ; - sh:datatype xsd:string ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path rep:profile ; - sh:name "profiel" ; - sh:nodeKind sh:IRI ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:targetClass ; - sh:name "klasse" ; - sh:nodeKind sh:IRI ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] ; - # rep:entity picks the shape when two entities share one sh:targetClass - # (the profile lists the entity URI per entity). - sh:property [ - sh:path rep:entity ; - sh:name "entiteit" ; - sh:nodeKind sh:IRI ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:property ; - sh:name "filter" ; - sh:node rep:FilterShape ; - ] ; - sh:property [ - sh:path rep:columns ; - sh:name "kolommen" ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] . - -# A filter: a path plus exactly one condition. A path without a condition -# selects everything and is refused by check.js (filterConstraint). -rep:FilterShape - a sh:NodeShape ; - sh:property [ - sh:path sh:path ; - sh:name "pad" ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:hasValue ; - sh:name "gelijk aan" ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:in ; - sh:name "in lijst" ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:minInclusive ; - sh:name "vanaf" ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:maxInclusive ; - sh:name "tot en met" ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:minExclusive ; - sh:name "na" ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:maxExclusive ; - sh:name "voor" ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:pattern ; - sh:name "regex" ; - sh:datatype xsd:string ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:flags ; - sh:name "regex-vlaggen" ; - sh:datatype xsd:string ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:minCount ; - sh:name "minimaal aantal" ; - sh:datatype xsd:integer ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:maxCount ; - sh:name "maximaal aantal" ; - sh:datatype xsd:integer ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path rep:anyOf ; - 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" ; - sh:maxCount 0 ; - ] . - -# A column: path, label, collect mode, separator. No constraints here; -# 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 [ - sh:path sh:path ; - sh:name "pad" ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path rdfs:label ; - sh:name "kolomnaam" ; - sh:datatype xsd:string ; - sh:minCount 1 ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path rep:collect ; - sh:name "verzamelwijze" ; - sh:nodeKind sh:IRI ; - sh:maxCount 1 ; - ] ; - sh:property [ - sh:path sh:separator ; - 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 -# default when absent; min/max are SHACL-AF names). A shapes graph cannot -# enumerate an IRI value set without a class; check.js enforces it (columnPure). -rep:RowShape - a sh:NodeShape ; rdfs:comment "rep:collect value: one CSV line per value" . - -# rep:self as sh:path marks the row's own subject URI: nothing to walk, -# the runner takes it from the seed. -rep:self - a rdf:Property ; - rdfs:comment "in a column's sh:path slot: the row's own subject URI" . \ No newline at end of file