Semantic Data & SPARQL¶
Der Semantic-Data-Dienst überführt hochgeladene Datensätze in Linked Open Data: Aus einer CSV-Datei und ihrem Frictionless-Schema entsteht eine RDF-Repräsentation, die in den Triple Store (Apache Jena Fuseki) geschrieben und dort per SPARQL abfragbar wird. Diese Seite beschreibt den Ablauf, die beteiligten Komponenten, ihre Konfiguration und typische Abfragen.
Die Einordnung in das Gesamtsystem zeigt die Architektur-Übersicht; als Teilschritt des CSV-Uploads ist die RDF-Erzeugung in Prozess: Datensatz hochladen beschrieben.
Ablauf¶
Die RDF-Erzeugung ist ein asynchroner Teilschritt des CSV-Uploads. Das Staging-Backend stößt sie an, wartet aber nicht auf ihr Ergebnis: Schlägt sie fehl, wird der Datensatz trotzdem veröffentlicht und die RDF-Distribution später ergänzt.
1. Auslöser im Staging-Backend¶
RDF entsteht überall dort, wo das Backend die Repräsentationen eines Datensatzes (neu) aufbaut – nicht nur beim Vervollständigen:
| Vorgang | Endpunkt | RDF |
|---|---|---|
| Neuupload | POST /upload/csv |
wird erzeugt |
| Fortführung (Datenserie) | POST /upload/csv/append |
wird erzeugt (neuer Datensatz je Fortführung) |
| Überschreiben | PUT /upload/csv/overwrite/{datasetId} |
wird ersetzt bzw. erzeugt |
| Vervollständigen | PUT /upload/csv/complete/{datasetId} |
wird erzeugt, sofern noch keine RDF-Distribution existiert |
Alle Wege laufen über denselben RDF-Client des Backends und dieselbe Kette aus Scheduler, Queue und Worker. Rein geografische Uploads (GeoJSON) lösen keine RDF-Erzeugung aus.
Das Backend übergibt dem Auftrag alle Angaben, die der Worker zur Verarbeitung braucht – insbesondere die Quell-URL der Tabular Data Resource, das Ziel im Triple Store sowie Ziel und Token für den Datenkatalog. Der Worker selbst hält dafür keine eigene Konfiguration vor.
2. Einreihen und Verarbeiten¶
Der Scheduler nimmt den Auftrag über POST /api/v2/jobs/ an und legt ihn in die Redis-Queue. Der
Worker entnimmt ihn und arbeitet ihn ab: Er konvertiert die Tabellendaten anhand des Schemas nach
RDF, leitet die Ziel-IRI des Named Graph ab, lädt die Triples per Graph Store Protocol
(PUT …?graph=<IRI>) in den Triple Store und legt die serialisierte RDF-Datei zusätzlich als
Distribution am Datensatz im Datenkatalog ab.
Komponenten¶
| Baustein | Repository | Namespace | Aufgabe |
|---|---|---|---|
| inference-scheduler | (Inference-Framework) | odi-triple-converter |
Nimmt RDF-Aufträge unter /api/v2/jobs/ entgegen und reiht sie in die Queue ein; Python/FastAPI |
| Redis | – | odi-triple-converter |
Auftrags-Queue zwischen Scheduler und Worker |
| triple-converter-worker | triple-converter-worker |
odi-triple-converter |
Konvertiert CSV nach RDF und lädt das Ergebnis in Triple Store und Datenkatalog; Python |
| Apache Jena Fuseki | odi-triple-jena-fuseki-docker-image |
odi-triple-store |
Triple Store (Dataset ds, GeoSPARQL); stellt den SPARQL-Endpunkt bereit |
| SPARQL-Frontend | odi-sparql-frontend |
odi-triple-store |
Web-Oberfläche für interaktive SPARQL-Abfragen |
Konfiguration¶
Staging-Backend¶
Das Backend spricht Scheduler, Triple Store und Datenkatalog cluster-intern an
(*.svc.cluster.local). Das ist Absicht: Der RDF-Client sendet keine Authentifizierungs-Header,
die extern erreichbaren Ingresse sind dagegen mit Basic-Auth geschützt. Über die internen Adressen
entfällt die Auth-Schicht.
| Umgebungsvariable | Wert (Produktion, cluster-intern) |
|---|---|
RDF_SERVICE_URL |
http://inference-scheduler-service.odi-triple-converter.svc.cluster.local:7535/api/v2 |
TRIPLE_STORE_URL |
http://apache-jena-fuseki-service.odi-triple-store.svc.cluster.local:3030/ds/ |
PIVEAU_SERVICE_URL |
http://ckan-service-service.odi-staging.svc.cluster.local:3000 |
Fehlt eine dieser Variablen, greift im Code ein nicht auflösbarer *.example.com-Default, und der
Vervollständigungs-Schritt bricht mit einem DNS-Fehler ab. Die Werte gehören also ins Deployment.
Scheduler und Worker¶
Scheduler und Worker brauchen nur die Anbindung an die Queue:
| Umgebungsvariable | Wert |
|---|---|
INFERENCE_REDIS__HOST |
redis-service |
Die auftragsspezifischen Ziele (Triple Store, Datenkatalog, Token) kommen aus dem Auftrag des Backends, nicht aus Umgebungsvariablen des Workers.
Graph-IRI¶
Jeder Datensatz landet in einem eigenen benannten Graphen. Die IRI leitet sich aus dem Schema ab,
Basis ist https://ld.schleswig-holstein.de/:
| Fall | Graph-IRI |
|---|---|
Einzeldatensatz ohne subject_uri_prefix |
https://ld.schleswig-holstein.de/{resource_name} |
| Datenserie ohne Prefix | https://ld.schleswig-holstein.de/{serienname}/{version} |
Schema mit gesetztem subject_uri_prefix |
genau dieser Prefix |
Beim Ableiten aus dem Namen werden alle Zeichen außer A–Z, a–z und 0–9 durch - ersetzt.
Beispiele: Resource-Name „epayment" ergibt …/epayment, „Ladesäulen SH" ergibt
…/Lades-ulen-SH, die Datenserie „windkraftanlagen" in Version 1.0.0 ergibt
…/windkraftanlagen/1.0.0.
RDF-Annotationen im Schema¶
Wie die Tabellendaten in Triples übersetzt werden, steuern Annotationen im Frictionless-Schema. Maßgeblich ist dabei der triple-converter: Er liest die folgenden Angaben und bildet daraus je Zeile ein Subjekt mit Prädikat-Objekt-Paaren und Typ-Angaben.
Aus jeder Zeile entsteht ein Subjekt (eine URI), an das pro Spalte ein
Prädikat → Objekt gehängt wird, dazu ein oder mehrere rdf:type.
| Annotation | Ebene | Wirkung |
|---|---|---|
subjectUriPrefix |
Schema (lod) |
Basis-Namespace der Subjekt-URIs. Ohne Angabe wird er aus dem Ressourcennamen abgeleitet. |
rowRdfType |
Schema (lod) |
Setzt rdf:type jeder Zeile (eine oder mehrere Vokabular-URIs), z. B. https://schema.org/Place. |
propertyUrl |
Feld | Prädikat der Spalte. Ohne Angabe wird als Prädikat der Feldname in CamelCase unter dem Property-Namespace verwendet. |
resourceUriPrefix |
Feld | Der Zellwert wird zu einer verlinkten URI (resourceUriPrefix + Wert) statt zu einem Literal. |
Subjekt-URI: Der lokale Teil hinter dem subjectUriPrefix bildet sich aus den
Primärschlüssel-Werten (mit - verbunden und bereinigt). Ist kein Primärschlüssel definiert,
wird stattdessen eine UUID erzeugt.
Objekt-Datentyp: Ohne resourceUriPrefix wird der Wert zu einem typisierten Literal. Der
XSD-Typ ergibt sich aus dem Feld-type:
Feld-type |
XSD-Typ |
|---|---|
string |
xsd:string |
number |
xsd:double |
integer |
xsd:integer |
boolean |
xsd:boolean |
date · datetime · time |
xsd:date · xsd:dateTime · xsd:time |
year · yearmonth |
xsd:gYear · xsd:gYearMonth |
wkt |
geo:wktLiteral |
Beispiel¶
Schema-Ausschnitt:
{
"lod": {
"subjectUriPrefix": "https://ld.schleswig-holstein.de/id/wea/",
"rowRdfType": ["https://schema.org/Place"]
},
"primaryKey": ["ID"],
"fields": [
{ "name": "ID", "type": "string" },
{ "name": "NABENHOEHE", "type": "number", "propertyUrl": "https://schema.org/height" },
{ "name": "GEMEINDESCHL", "type": "string", "resourceUriPrefix": "https://www.dcat-ap.de/def/politicalGeocoding/municipalityKey/" }
]
}
Für die Zeile ID=42, NABENHOEHE=140, GEMEINDESCHL=01001 entstehen die Triples:
<https://ld.schleswig-holstein.de/id/wea/42>
a <https://schema.org/Place> ;
<https://schema.org/height> "140"^^xsd:double ;
<…/wea#gemeindeschl> <https://www.dcat-ap.de/def/politicalGeocoding/municipalityKey/01001> .
Damit lässt sich gezielt über die vergebenen Typen und Prädikate abfragen:
SELECT ?wea ?hoehe WHERE {
GRAPH ?g {
?wea a <https://schema.org/Place> ;
<https://schema.org/height> ?hoehe .
}
}
SPARQL-Abfragen¶
Zugänge¶
| Zweck | Adresse |
|---|---|
| SPARQL-UI (interaktiv) | https://sparql.odi.schleswig-holstein.de |
| Query-Endpunkt | https://jena-fuseki.triple-store.odi.schleswig-holstein.de/ds/sparql |
| Fuseki-Admin-UI | https://jena-fuseki.triple-store.odi.schleswig-holstein.de/ |
| Cluster-intern | http://apache-jena-fuseki-service.odi-triple-store.svc.cluster.local:3030/ds/sparql |
Die SPARQL-UI ist fest auf den Query-Endpunkt verdrahtet – Abfrage eintippen und ausführen genügt.
Vorhandene Graphen auflisten¶
Weil jeder Datensatz in einem eigenen Graphen liegt, verschafft diese Abfrage einen Überblick samt Anzahl der Triples je Graph:
SELECT DISTINCT ?g (COUNT(*) AS ?triples)
WHERE { GRAPH ?g { ?s ?p ?o } }
GROUP BY ?g ORDER BY DESC(?triples)
Einen Datensatz abfragen¶
Die passende IRI aus der Liste einsetzen:
Benannter Graph statt Default-Graph
Die Daten liegen in benannten Graphen. Eine Abfrage ohne GRAPH
(SELECT * WHERE { ?s ?p ?o }) liefert sie nur, wenn im Dataset der Union Default Graph aktiv
ist. Verlässlich ist immer die Form mit explizitem GRAPH ?g { … } oder FROM <graph>.