From 2e4348b317a446516acacd47709d6158f676ba36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 18 Jul 2026 10:46:13 +0300 Subject: [PATCH 1/3] Add 1:N entity views via ldh:inverseView in the namespace ontology New app/ns.ttl (LDH app ontology served at {base}ns, northwind-traders style) defines five query-backed views: admin unit sub-units (inverse dct:isPartOf), current/former members (inverse org:organization through org:Membership), Seimas units (inverse org:unitOf) and party nominees (inverse ltlod:nominatedBy). app/import-ns.sh (chained into app/install.sh) resets and POSTs the ontology into the admin ontologies/namespace/ document and evicts the server-side cache. LDH matches views by exact rdf:type against the property's declared rdfs:range, so parties gain schema:PoliticalParty and the Seimas cv:PublicOrganisation (CPOV) in the seimas mappings; seimas datasets regenerated with the prod base (type triples are the only semantic diff). Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 24 +++- README.md | 12 +- app/.ldhignore | 1 + app/import-ns.sh | 56 ++++++++ app/install.sh | 8 +- app/ns.ttl | 187 +++++++++++++++++++++++++ app/patch-ontology.ru | 19 +++ datasets/current/seimas/org-units.trig | 3 +- datasets/current/seimas/parties.trig | 37 ++--- etl/ONTOLOGY-NOTES.md | 2 + etl/seimas/mappings/org-units.rq | 3 +- etl/seimas/mappings/parties.rq | 11 +- 12 files changed, 330 insertions(+), 33 deletions(-) create mode 100755 app/import-ns.sh create mode 100644 app/ns.ttl create mode 100644 app/patch-ontology.ru diff --git a/CLAUDE.md b/CLAUDE.md index 39b1b60..27c9d53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,8 +19,13 @@ uv run --project etl/tools ltlod-reconcile --input … --o make up # deploy LinkedDataHub at https://localhost:4443/ (root Makefile; # bootstraps secrets + server cert, then docker compose up -d) -make install # one-time: PUT app/ scaffolding (root + containers + taxonomy - # schemes) via LDH CLI; needs ../LinkedDataHub checkout (LDH_HOME=…) +make install # PUT app/ scaffolding (root + containers + taxonomy schemes) + # via LDH CLI + install app/ns.ttl (1:N views) into the admin + # ontologies/namespace/ doc; needs ../LinkedDataHub (LDH_HOME=…). + # Interactive, LinkedDataHub-Apps style: prompts for Base URL / + # cert / password / proxy, defaults = the local stack (Enter×4 + # or `printf '\n\n\n\n' | make install`); enter another Base URL + # + owner cert to install onto any LDH instance make load # bulk-load datasets/current/*/*.trig into fuseki-end-user TDB2; # regenerate with `make -C etl` first (committed data has prod base); # ends with `make public` (anonymous read, LDH make-public.sh equivalent) @@ -66,6 +71,16 @@ merge on load). Unmatched entities go to `cache/unmatched*.csv`, never force-mat container, PUT via LDH CLI by `make install`); each carries an `rdf:_1 <#select-children>` → `ldh:Object`/`ldh:ChildrenView` block, without which LDH renders no children listing at all. +- **1:N entity views**: cross-entity listings (county → municipalities, committee + → members, party → nominees) are `ldh:inverseView` definitions in `app/ns.ttl` + (the LDH namespace ontology, northwind-traders style): ` + ldh:inverseView ` + `spin:query` → `sp:Select` with `$about`. LDH + shows a view on every instance whose `rdf:type` matches the property's declared + `rdfs:range` — exact type match, no subsumption, so view targeting relies on + discriminating types in the data (`schema:PoliticalParty` for parties, + `cv:PublicOrganisation` for the Seimas). `app/import-ns.sh` (called by `make + install`) resets + POSTs the ontology into the admin `ontologies/namespace/` + document (served at `{base}ns`) and evicts the server-side ontology cache. - **Vocabulary cascade**: W3C specs first → domain-specific third-party vocabs (EU SEMIC, OP authority tables, FOAF) → schema.org as general fallback → custom (`http://linkeddata.lt/ns#`) last. Rationale per domain: `etl/ONTOLOGY-NOTES.md`. @@ -130,6 +145,11 @@ merge on load). Unmatched entities go to `cache/unmatched*.csv`, never force-mat `AuthorizationFilter` + `aclQuery` in LDH web.xml.) - `COMPOSE_PROJECT_NAME=ltlod` isolates container/volume names from other local LDH stacks, but ports 81/4443/5443 still clash — one stack at a time. +- **502 on all public endpoints after restarting backend containers** (fuseki, + varnish): nginx resolves upstream container IPs at startup — restart nginx + too. `fuseki-end-user` can be OOM-killed (exit 137) under memory pressure + when other Docker workloads run; `docker compose up -d fuseki-end-user` + revives it (LDH health recovers on its own). ## Verification diff --git a/README.md b/README.md index 12f7675..8d20dfe 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,8 @@ du Fuseki, Varnish kešai). ```shell make up # sugeneruoja slaptažodžius + serverio sertifikatą ir paleidžia LDH -make install # vienkartinis: sukuria konteinerių dokumentus per LDH CLI (reikia ../LinkedDataHub) +make install # sukuria konteinerių dokumentus ir vardų erdvės ontologiją per LDH CLI (reikia ../LinkedDataHub); + # interaktyvus: Enter×4 = lokali aplinka, kitas Base URL = bet kuri LDH instancija make -C etl # perkuria rinkinius su numatytąja baze https://localhost:4443/ make load # užkrauna datasets/current/*/*.trig tiesiai į triplestore ``` @@ -170,9 +171,12 @@ Duomenų struktūra kuriama dviem lygiais: schemos iš `app/` katalogo dokumentas po dokumento **per LDH CLI** (`put.sh`, kaip [LinkedDataHub-Apps](https://github.com/AtomGraph/LinkedDataHub-Apps) projektuose) — taip dokumentai gauna `ldh:ChildrenView` bloką, dėl kurio - konteinerių puslapiai rodo vaikų sąrašus. Reikia šalia išklonintos - [LinkedDataHub](https://github.com/AtomGraph/LinkedDataHub) repozitorijos - (`../LinkedDataHub`, keičiama per `make install LDH_HOME=…`). + konteinerių puslapiai rodo vaikų sąrašus. Kartu įdiegiama vardų erdvės + ontologija (`app/ns.ttl`) su 1:N rodiniais (`ldh:inverseView`): apskrities + puslapis rodo jos savivaldybes, komiteto — dabartinius ir buvusius narius, + Seimo — padalinius, partijos — jos iškeltus Seimo narius. Reikia šalia + išklonintos [LinkedDataHub](https://github.com/AtomGraph/LinkedDataHub) + repozitorijos (`../LinkedDataHub`, keičiama per `make install LDH_HOME=…`). - **Duomenys** (`make load`): ETL rinkiniai — vien `dh:Item` dokumentai su `sioc:has_container` nuorodomis į karkasą — rašomi **tiesiogiai į `fuseki-end-user` TDB2 saugyklą** (`tdb2.tdbloader` per vienkartinį diff --git a/app/.ldhignore b/app/.ldhignore index 462a04e..e278be7 100644 --- a/app/.ldhignore +++ b/app/.ldhignore @@ -1 +1,2 @@ root.ttl +ns.ttl diff --git a/app/import-ns.sh b/app/import-ns.sh new file mode 100755 index 0000000..8a3a805 --- /dev/null +++ b/app/import-ns.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Installs/updates the LTLOD namespace ontology (ns.ttl) into the admin +# dataspace's ontologies/namespace/ document, which LinkedDataHub serves at +# {base}ns. Mirrors LinkedDataHub-Apps demo/northwind-traders: +# 1. PATCH-reset the ontology document (drop everything except the document +# resource and its foaf:primaryTopic), +# 2. POST ns.ttl with a prepended @base <{base}ns> directive so its : prefix +# (<#>) resolves to the end-user namespace, +# 3. clear the ontology from server memory so it reloads fresh. +# Requires LinkedDataHub's bin/ subdirs on $PATH (patch.sh, post.sh, +# clear-ontology.sh) — `make install` in the root Makefile sets this up. +set -euo pipefail + +if [ "$#" -ne 3 ] && [ "$#" -ne 4 ]; then + echo "Usage: $0" '$base $cert_pem_file $cert_password [$proxy]' >&2 + echo "Example: $0" 'https://localhost:4443/ ./ssl/owner/cert.pem Password https://localhost:5443/' >&2 + echo "Note: special characters such as $ need to be escaped in passwords!" >&2 + exit 1 +fi + +base="$1" +cert_pem_file=$(realpath "$2") +cert_password="$3" +proxy="${4:-$base}" + +app_dir="$(cd "$(dirname "$0")" && pwd)" + +admin_uri() { + echo "$1" | sed 's|://|://admin.|' +} + +admin_base=$(admin_uri "$base") +admin_proxy=$(admin_uri "$proxy") + +printf "\n### Resetting namespace ontology document: %sontologies/namespace/\n" "$admin_base" +{ echo "BASE <${admin_base}ontologies/namespace/>"; cat "$app_dir/patch-ontology.ru"; } | patch.sh \ + -f "$cert_pem_file" \ + -p "$cert_password" \ + --proxy "$admin_proxy" \ + "${admin_base}ontologies/namespace/" + +printf "\n### Appending ns.ttl to the namespace ontology\n" +{ echo "@base <${base}ns> ."; cat "$app_dir/ns.ttl"; } | post.sh \ + -f "$cert_pem_file" \ + -p "$cert_password" \ + --proxy "$admin_proxy" \ + --content-type "text/turtle" \ + "${admin_base}ontologies/namespace/" + +printf "\n### Clearing ontology from server memory: %sns#\n" "$base" +clear-ontology.sh \ + -f "$cert_pem_file" \ + -p "$cert_password" \ + -b "$admin_base" \ + --proxy "$admin_proxy" \ + --ontology "${base}ns#" diff --git a/app/install.sh b/app/install.sh index 1445986..1a17515 100755 --- a/app/install.sh +++ b/app/install.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Creates/updates the LTLOD container scaffolding (root document + containers + -# taxonomy scheme containers) on a running LinkedDataHub instance via the LDH -# CLI. Requires LinkedDataHub's bin/ subdirs and Jena's bin/ (for `turtle`) on +# taxonomy scheme containers) and the namespace ontology (ns.ttl with 1:N +# entity views) on a running LinkedDataHub instance via the LDH CLI. Requires +# LinkedDataHub's bin/ subdirs and Jena's bin/ (for `turtle`) on # $PATH — `make install` in the root Makefile sets this up. # Deliberately does NOT call make-public.sh: `make load` ends with `make public`. set -euo pipefail @@ -30,3 +31,6 @@ turtle --base="$base" < "$app_dir/root.ttl" | put.sh \ printf "\n### Updating container documents\n" "$app_dir/update-folder.sh" "$base" "$cert_pem_file" "$cert_password" "$app_dir" "$app_dir" "$proxy" + +printf "\n### Updating namespace ontology\n" +"$app_dir/import-ns.sh" "$base" "$cert_pem_file" "$cert_password" "$proxy" diff --git a/app/ns.ttl b/app/ns.ttl new file mode 100644 index 0000000..3abf55e --- /dev/null +++ b/app/ns.ttl @@ -0,0 +1,187 @@ +# LTLOD namespace ontology — the LinkedDataHub app ontology served at {base}ns. +# app/import-ns.sh prepends "@base <{base}ns> ." at install time, so the : +# prefix (<#>) resolves to the end-user namespace. +# +# 1:N views: ldh:inverseView surfaces the View block on every +# instance whose rdf:type matches the property's declared rdfs:range; LDH +# replaces $about in the sp:text query with the instance URI at render time. +# Multiple rdfs:range values are LDH view-discovery metadata, not strict OWL +# semantics (same approach as the northwind-traders demo app). + +@prefix : <#> . +@prefix ldh: . +@prefix rdfs: . +@prefix owl: . +@prefix sp: . +@prefix spin: . +@prefix dct: . +@prefix cv: . +@prefix org: . +@prefix foaf: . +@prefix schema: . +@prefix ltlod: . + +: a owl:Ontology ; + rdfs:label "LTLOD namespace" . + +# classes + +cv:AdminUnit a owl:Class ; + rdfs:label "Administracinis vienetas"@lt, "Administrative unit"@en ; + rdfs:isDefinedBy : . + +cv:PublicOrganisation a owl:Class ; + rdfs:label "Viešojo sektoriaus organizacija"@lt, "Public organisation"@en ; + rdfs:isDefinedBy : . + +org:OrganizationalUnit a owl:Class ; + rdfs:label "Padalinys"@lt, "Organizational unit"@en ; + rdfs:isDefinedBy : . + +schema:PoliticalParty a owl:Class ; + rdfs:label "Politinė partija"@lt, "Political party"@en ; + rdfs:isDefinedBy : . + +foaf:Person a owl:Class ; + rdfs:label "Asmuo"@lt, "Person"@en ; + rdfs:isDefinedBy : . + +# properties + +dct:isPartOf a owl:ObjectProperty ; + rdfs:label "Priklauso vienetui"@lt, "Is part of"@en ; + rdfs:range cv:AdminUnit ; + ldh:inverseView :SubUnits ; + rdfs:isDefinedBy : . + +org:organization a owl:ObjectProperty ; + rdfs:label "Organizacija"@lt, "Organization"@en ; + rdfs:range org:OrganizationalUnit, cv:PublicOrganisation ; + ldh:inverseView :CurrentMembers, :FormerMembers ; + rdfs:isDefinedBy : . + +org:unitOf a owl:ObjectProperty ; + rdfs:label "Padalinys organizacijoje"@lt, "Unit of"@en ; + rdfs:range cv:PublicOrganisation ; + ldh:inverseView :Units ; + rdfs:isDefinedBy : . + +ltlod:nominatedBy a owl:ObjectProperty ; + rdfs:label "Iškelta partijos"@lt, "Nominated by"@en ; + rdfs:domain foaf:Person ; + rdfs:range schema:PoliticalParty ; + ldh:inverseView :NominatedMembers ; + rdfs:isDefinedBy : . + +# views +# Territorial hierarchy: children point up via dct:isPartOf, every parent is a +# cv:AdminUnit, so one view covers county→municipalities, municipality→ +# elderships, eldership→settlements and settlement→streets. + +:SubUnits a ldh:View ; + dct:title "Sudedamieji vienetai"@lt ; + spin:query :SelectSubUnits ; + rdfs:isDefinedBy : . + +:SelectSubUnits a sp:Select ; + rdfs:label "Select sub-units" ; + sp:text """ +PREFIX dct: + +SELECT DISTINCT ?unit +WHERE + { GRAPH ?graph + { ?unit dct:isPartOf $about } + } +ORDER BY ?unit +""" ; + rdfs:isDefinedBy : . + +# Memberships are n-ary org:Membership fragments in the person's graph; the +# interval lives in the same graph, so one GRAPH block suffices. Current +# membership = org:memberDuring interval without time:hasEnd. A person with an +# ended stint and a current one appears in both lists — accepted. + +:CurrentMembers a ldh:View ; + dct:title "Dabartiniai nariai"@lt ; + spin:query :SelectCurrentMembers ; + rdfs:isDefinedBy : . + +:SelectCurrentMembers a sp:Select ; + rdfs:label "Select current members" ; + sp:text """ +PREFIX org: +PREFIX time: + +SELECT DISTINCT ?person +WHERE + { GRAPH ?graph + { ?membership org:organization $about ; + org:member ?person ; + org:memberDuring ?interval + FILTER NOT EXISTS { ?interval time:hasEnd ?end } + } + } +ORDER BY ?person +""" ; + rdfs:isDefinedBy : . + +:FormerMembers a ldh:View ; + dct:title "Buvę nariai"@lt ; + spin:query :SelectFormerMembers ; + rdfs:isDefinedBy : . + +:SelectFormerMembers a sp:Select ; + rdfs:label "Select former members" ; + sp:text """ +PREFIX org: +PREFIX time: + +SELECT DISTINCT ?person +WHERE + { GRAPH ?graph + { ?membership org:organization $about ; + org:member ?person ; + org:memberDuring/time:hasEnd ?end } + } +ORDER BY ?person +""" ; + rdfs:isDefinedBy : . + +:Units a ldh:View ; + dct:title "Padaliniai"@lt ; + spin:query :SelectUnits ; + rdfs:isDefinedBy : . + +:SelectUnits a sp:Select ; + rdfs:label "Select organizational units" ; + sp:text """ +PREFIX org: + +SELECT DISTINCT ?unit +WHERE + { GRAPH ?graph + { ?unit org:unitOf $about } + } +ORDER BY ?unit +""" ; + rdfs:isDefinedBy : . + +:NominatedMembers a ldh:View ; + dct:title "Iškelti Seimo nariai"@lt ; + spin:query :SelectNominatedMembers ; + rdfs:isDefinedBy : . + +:SelectNominatedMembers a sp:Select ; + rdfs:label "Select persons nominated by party" ; + sp:text """ +PREFIX ltlod: + +SELECT DISTINCT ?person +WHERE + { GRAPH ?graph + { ?person ltlod:nominatedBy $about } + } +ORDER BY ?person +""" ; + rdfs:isDefinedBy : . diff --git a/app/patch-ontology.ru b/app/patch-ontology.ru new file mode 100644 index 0000000..efa41e4 --- /dev/null +++ b/app/patch-ontology.ru @@ -0,0 +1,19 @@ +PREFIX foaf: + +DELETE { + ?s ?p ?o +} +WHERE { + ?s ?p ?o + # Exclude triples about the main resource itself + MINUS { + <> ?p ?o + BIND(<> AS ?s) + } + # Exclude triples about the foaf:primaryTopic resource + MINUS { + <> foaf:primaryTopic ?primaryTopic . + ?primaryTopic ?p ?o + BIND(?primaryTopic AS ?s) + } +} diff --git a/datasets/current/seimas/org-units.trig b/datasets/current/seimas/org-units.trig index 1d93120..d4235ec 100644 --- a/datasets/current/seimas/org-units.trig +++ b/datasets/current/seimas/org-units.trig @@ -1,3 +1,4 @@ +PREFIX cv: PREFIX dct: PREFIX dh: PREFIX foaf: @@ -1242,7 +1243,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type cv:PublicOrganisation , org:FormalOrganization; skos:prefLabel "Seimas of the Republic of Lithuania"@en , "Lietuvos Respublikos Seimas"@lt; foaf:homepage . diff --git a/datasets/current/seimas/parties.trig b/datasets/current/seimas/parties.trig index 3aa0d43..6541025 100644 --- a/datasets/current/seimas/parties.trig +++ b/datasets/current/seimas/parties.trig @@ -1,14 +1,15 @@ -PREFIX dct: -PREFIX dh: -PREFIX foaf: -PREFIX org: -PREFIX rdf: -PREFIX sioc: -PREFIX skos: +PREFIX dct: +PREFIX dh: +PREFIX foaf: +PREFIX org: +PREFIX rdf: +PREFIX schema: +PREFIX sioc: +PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Lietuvos valstiečių ir žaliųjų sąjunga"@lt . @@ -20,7 +21,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Tėvynės sąjunga – Lietuvos krikščionys demokratai"@lt . @@ -32,7 +33,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Demokratų sąjunga „Vardan Lietuvos“"@lt . @@ -44,7 +45,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Tėvynės sąjunga-Lietuvos krikščionys demokratai"@lt . @@ -56,7 +57,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Nacionalinis susivienijimas"@lt . @@ -68,7 +69,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Politinė partija „Nemuno Aušra“"@lt . @@ -80,7 +81,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Išsikėlė pats"@lt . @@ -92,7 +93,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Liberalų sąjūdis"@lt . @@ -104,7 +105,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Lietuvos lenkų rinkimų akcija-Krikščioniškų šeimų sąjunga"@lt . @@ -116,7 +117,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Lietuvos socialdemokratų partija"@lt . @@ -128,7 +129,7 @@ PREFIX skos: { - rdf:type org:FormalOrganization; + rdf:type schema:PoliticalParty , org:FormalOrganization; skos:prefLabel "Partija „Laisvė ir teisingumas“"@lt . diff --git a/etl/ONTOLOGY-NOTES.md b/etl/ONTOLOGY-NOTES.md index 382d844..44348e2 100644 --- a/etl/ONTOLOGY-NOTES.md +++ b/etl/ONTOLOGY-NOTES.md @@ -14,6 +14,8 @@ The per-domain property/datatype/cardinality constraints implied by these choice | Org structure, positions, memberships | W3C **ORG**: `org:OrganizationalUnit`, `org:unitOf`, `org:Membership`, `org:member`, `org:organization`, `org:role`, `org:memberDuring` | Change over time = n-ary `org:Membership` + `time:Interval` (`time:hasBeginning/hasEnd` → `time:Instant` → `time:inXSDDate`). **No reification, no RDF-star.** Current state = memberships without `time:hasEnd`. | | Persons | `foaf:Person`, `foaf:givenName/familyName/name/mbox/gender/phone` | Core Person 2.0 compatible (it reuses foaf); revisit `person:` terms if birth data is added later. Phones are normalized to `tel:+370…` E.164 URIs in the mapping (bare 7-digit values are Vilnius landlines, area code 5) — reuse that BIND pattern in any future mapping with phone numbers. | | Taxonomies | **SKOS** | Concept-per-graph; schemes are containers. | +| Parties | `org:FormalOrganization` + `schema:PoliticalParty` | No W3C/SEMIC political-party class exists; schema.org (general-fallback cascade level) has one. The extra type also lets LinkedDataHub target party-only views (`ldh:inverseView` matches instance types against a property's declared `rdfs:range` — exact match, no subsumption). | +| The Seimas | `org:FormalOrganization` + `cv:PublicOrganisation` (SEMIC **CPOV**) | Distinguishes the parliament from parties and legal entities, which share `org:FormalOrganization`; used to target Seimas-only LinkedDataHub views (members, units). | | Validity dates | `schema:validFrom` / `schema:validThrough` (admin units), `schema:foundingDate` / `schema:dissolutionDate` (legal entities) | No EU RDF term for registry lifecycle dates (INSPIRE models them as `beginLifespanVersion` in GML only). | ## Legacy vocabulary disposition (2012 datasets) diff --git a/etl/seimas/mappings/org-units.rq b/etl/seimas/mappings/org-units.rq index a7783df..85fc1de 100644 --- a/etl/seimas/mappings/org-units.rq +++ b/etl/seimas/mappings/org-units.rq @@ -2,6 +2,7 @@ # groups as org:OrganizationalUnit, one per named graph; plus the Seimas itself. # Structural units come from the dedicated feeds; parliamentary groups are only # present in the members feed's position rows, so both normalized inputs are used. +PREFIX cv: PREFIX dct: PREFIX foaf: PREFIX org: @@ -18,7 +19,7 @@ CONSTRUCT sioc:has_container ?organizations ; dct:title "Lietuvos Respublikos Seimas"@lt ; foaf:primaryTopic ?seimas . - ?seimas a org:FormalOrganization ; + ?seimas a org:FormalOrganization, cv:PublicOrganisation ; skos:prefLabel "Lietuvos Respublikos Seimas"@lt, "Seimas of the Republic of Lithuania"@en ; foaf:homepage . } diff --git a/etl/seimas/mappings/parties.rq b/etl/seimas/mappings/parties.rq index 2d562a9..03a36dd 100644 --- a/etl/seimas/mappings/parties.rq +++ b/etl/seimas/mappings/parties.rq @@ -1,9 +1,10 @@ # Political parties referenced as nominating parties in the Seimas members feed. # Keyed by transliterated name slug for now; JAR codes + VRK data can enrich later. -PREFIX dct: -PREFIX foaf: -PREFIX org: -PREFIX skos: +PREFIX dct: +PREFIX foaf: +PREFIX org: +PREFIX skos: +PREFIX schema: PREFIX dh: PREFIX sioc: @@ -17,7 +18,7 @@ CONSTRUCT dct:title ?title ; foaf:primaryTopic ?party . - ?party a org:FormalOrganization ; + ?party a org:FormalOrganization, schema:PoliticalParty ; skos:prefLabel ?title . } } From 704c79156faa9a63cbe03ac8267875417daa883b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 18 Jul 2026 10:46:21 +0300 Subject: [PATCH 2/3] Make `make install` interactive, LinkedDataHub-Apps style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prompts for Base URL / certificate / password / proxy like the LinkedDataHub-Apps demo Makefiles, with defaults from the local docker-compose stack (.env, ssl/, secrets/) — Enter×4 installs locally, another Base URL + owner cert installs the app structure (containers + namespace ontology with views) onto any LDH instance. The docker health-wait now only runs when targeting the local stack. Co-Authored-By: Claude Fable 5 --- Makefile | 50 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 53b0205..b3d531f 100644 --- a/Makefile +++ b/Makefile @@ -44,21 +44,47 @@ stop: logs: docker compose logs -f linkeddatahub -# Create/update the container scaffolding (root + containers + taxonomy scheme -# docs from app/) via LDH CLI PUTs. Run once after `make up`; re-running is -# safe (PUT replaces). Order: make up -> make install -> make load. +# Install the app structure (root + containers + taxonomy schemes + the +# namespace ontology with 1:N views) onto a LinkedDataHub instance via LDH CLI +# PUTs. Interactive, LinkedDataHub-Apps style: prompts for the target instance +# with defaults from the local docker-compose stack (.env, ssl/, secrets/) — +# press Enter to install locally, or enter another Base URL + owner cert to +# install on any LDH instance. Re-running is safe (PUT replaces). Local order: +# make up -> make install -> make load. install: @[ -d "$(LDH_HOME)/bin" ] || \ { echo "ERROR: LDH CLI not found — clone https://github.com/AtomGraph/LinkedDataHub to $(LDH_HOME) or pass LDH_HOME=…"; exit 1; } - @[ -n "$$(docker compose ps -q linkeddatahub)" ] || \ - { echo "ERROR: linkeddatahub container not found — run 'make up' first."; exit 1; } - @echo "Waiting for LinkedDataHub health (first-boot seeding must finish)..." - @until [ "$$(docker inspect -f '{{.State.Health.Status}}' $$(docker compose ps -q linkeddatahub))" = "healthy" ]; do \ - sleep 5; echo " ...waiting"; \ - done - PATH="$$(find "$$(cd $(LDH_HOME) && pwd)/bin" -type d | tr '\n' ':')$(JENA_HOME)/bin:$$PATH" \ - ./app/install.sh "$(BASE_URI)" ssl/owner/cert.pem \ - "$$(cat secrets/owner_cert_password.txt)" "$(PROXY_URI)" + @read -p "Enter Base URL [$(BASE_URI)]: " BASE_URL; \ + BASE_URL=$${BASE_URL:-$(BASE_URI)}; \ + read -p "Enter Certificate Path [ssl/owner/cert.pem]: " CERT_PATH; \ + CERT_PATH=$${CERT_PATH:-ssl/owner/cert.pem}; \ + [ -f "$$CERT_PATH" ] || { echo "ERROR: certificate not found: $$CERT_PATH"; exit 1; }; \ + PW_DEFAULT=""; \ + [ -f secrets/owner_cert_password.txt ] && PW_DEFAULT="$$(cat secrets/owner_cert_password.txt)"; \ + if [ -n "$$PW_DEFAULT" ]; then \ + read -r -s -p "Enter Certificate Password [from secrets/owner_cert_password.txt]: " PASSWORD; \ + else \ + read -r -s -p "Enter Certificate Password (required): " PASSWORD; \ + fi; \ + echo ""; \ + PASSWORD=$${PASSWORD:-$$PW_DEFAULT}; \ + if [ -z "$$PASSWORD" ]; then echo "Password cannot be empty. Aborting."; exit 1; fi; \ + PROXY_DEFAULT=""; \ + [ "$$BASE_URL" = "$(BASE_URI)" ] && PROXY_DEFAULT="$(PROXY_URI)"; \ + read -p "Enter Proxy URL (optional) [$$PROXY_DEFAULT]: " PROXY_URL; \ + PROXY_URL=$${PROXY_URL:-$$PROXY_DEFAULT}; \ + if [ "$$BASE_URL" = "$(BASE_URI)" ] && [ -n "$$(docker compose ps -q linkeddatahub 2>/dev/null)" ]; then \ + echo "Waiting for LinkedDataHub health (first-boot seeding must finish)..."; \ + until [ "$$(docker inspect -f '{{.State.Health.Status}}' $$(docker compose ps -q linkeddatahub))" = "healthy" ]; do \ + sleep 5; echo " ...waiting"; \ + done; \ + fi; \ + export PATH="$$(find "$$(cd $(LDH_HOME) && pwd)/bin" -type d | tr '\n' ':')$(JENA_HOME)/bin:$$PATH"; \ + if [ -n "$$PROXY_URL" ]; then \ + ./app/install.sh "$$BASE_URL" "$$CERT_PATH" "$$PASSWORD" "$$PROXY_URL"; \ + else \ + ./app/install.sh "$$BASE_URL" "$$CERT_PATH" "$$PASSWORD"; \ + fi # Bulk-load datasets/current/*/*.trig into the end-user TDB2 store. APPEND-ONLY: # clean rebuild = `make down && rm -rf fuseki/end-user && make up && make load`. From 21adb2278201737fd48ccfbb0ab1b464023acba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 18 Jul 2026 10:49:11 +0300 Subject: [PATCH 3/3] Make the dataspace public during install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app/install.sh now starts with the LDH CLI make-public.sh (northwind position), granting the same class-based read access as `make public` but through LDH's HTTP API, so it also works against remote instances. `make load` still ends with `make public` — both are idempotent. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 10 ++++++---- README.md | 3 ++- app/install.sh | 17 +++++++++++------ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 27c9d53..ca1584e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,9 +19,10 @@ uv run --project etl/tools ltlod-reconcile --input … --o make up # deploy LinkedDataHub at https://localhost:4443/ (root Makefile; # bootstraps secrets + server cert, then docker compose up -d) -make install # PUT app/ scaffolding (root + containers + taxonomy schemes) - # via LDH CLI + install app/ns.ttl (1:N views) into the admin - # ontologies/namespace/ doc; needs ../LinkedDataHub (LDH_HOME=…). +make install # set up the dataspace via LDH CLI: make it public + PUT app/ + # scaffolding (root + containers + taxonomy schemes) + install + # app/ns.ttl (1:N views) into the admin ontologies/namespace/ + # doc; needs ../LinkedDataHub (LDH_HOME=…). # Interactive, LinkedDataHub-Apps style: prompts for Base URL / # cert / password / proxy, defaults = the local stack (Enter×4 # or `printf '\n\n\n\n' | make install`); enter another Base URL @@ -137,7 +138,8 @@ merge on load). Unmatched entities go to `cache/unmatched*.csv`, never force-mat adds `dh:Item` + `sioc:has_container` otherwise, plus `dct:created`/ `acl:owner`) — never put sioc triples in `app/*.ttl`. `make install` is idempotent: PUT replaces the whole named graph. -- **Public read access is class-based**: `make public` grants +- **Public read access is class-based**: `make public` (direct-to-fuseki) and + `make install` (LDH CLI `make-public.sh`, works remotely) grant the same `acl:accessToClass def:Root, dh:Container, dh:Item, nfo:FileDataObject` — ETL documents match because mappings type them `dh:Item`/`dh:Container`. (Untyped docs would also pass: LDH's ACL query leaves `$Type` unbound when diff --git a/README.md b/README.md index 8d20dfe..9b00279 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,8 @@ du Fuseki, Varnish kešai). ```shell make up # sugeneruoja slaptažodžius + serverio sertifikatą ir paleidžia LDH -make install # sukuria konteinerių dokumentus ir vardų erdvės ontologiją per LDH CLI (reikia ../LinkedDataHub); +make install # suteikia viešą skaitymo prieigą, sukuria konteinerių dokumentus ir vardų erdvės + # ontologiją per LDH CLI (reikia ../LinkedDataHub); # interaktyvus: Enter×4 = lokali aplinka, kitas Base URL = bet kuri LDH instancija make -C etl # perkuria rinkinius su numatytąja baze https://localhost:4443/ make load # užkrauna datasets/current/*/*.trig tiesiai į triplestore diff --git a/app/install.sh b/app/install.sh index 1a17515..2a11452 100755 --- a/app/install.sh +++ b/app/install.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash -# Creates/updates the LTLOD container scaffolding (root document + containers + -# taxonomy scheme containers) and the namespace ontology (ns.ttl with 1:N -# entity views) on a running LinkedDataHub instance via the LDH CLI. Requires -# LinkedDataHub's bin/ subdirs and Jena's bin/ (for `turtle`) on -# $PATH — `make install` in the root Makefile sets this up. -# Deliberately does NOT call make-public.sh: `make load` ends with `make public`. +# Sets up the LTLOD dataspace on a running LinkedDataHub instance via the LDH +# CLI: makes it publicly readable, then creates/updates the container +# scaffolding (root document + containers + taxonomy scheme containers) and the +# namespace ontology (ns.ttl with 1:N entity views). Requires LinkedDataHub's +# bin/ subdirs and Jena's bin/ (for `turtle`) on $PATH — `make install` in the +# root Makefile sets this up. The public grant equals `make public` +# (bin/make-public.sh) but goes through LDH's HTTP API, so it also works +# against remote instances; both are idempotent. set -euo pipefail if [ "$#" -ne 3 ] && [ "$#" -ne 4 ]; then @@ -21,6 +23,9 @@ proxy="${4:-$base}" app_dir="$(cd "$(dirname "$0")" && pwd)" +printf "\n### Creating authorization to make the dataspace public\n" +make-public.sh -b "$base" -f "$cert_pem_file" -p "$cert_password" --proxy "$proxy" + printf "\n### Updating root document: %s\n" "$base" turtle --base="$base" < "$app_dir/root.ttl" | put.sh \ -f "$cert_pem_file" \