Zum Inhalt

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; den auslösenden Upload beschreibt Prozess: Datensatz hochladen.

Ablauf

Die RDF-Erzeugung ist ein asynchroner Teilschritt beim Abschließen eines Datensatzes. Das Staging-Backend stößt sie an, wartet aber nicht auf ihr Ergebnis: Schlägt sie fehl, wird der Datensatz trotzdem abgeschlossen und die RDF-Distribution später ergänzt.

Datensatz abschließen (Staging-Backend, PUT /upload/csv/complete/{datasetId})
   └─ completeRdfFile ── POST /api/v2/jobs/ ──►  inference-scheduler
                                                     │  enqueue
                                                 Redis-Queue
                                                     │  dequeue
                                             triple-converter-worker
                                                     ├─ CSV → RDF (RDF/XML)
                                                     ├─ Graph-IRI aus dem Schema ableiten
                                                     ├─ PUT {triplestore}/ds?graph=<IRI>   (Triple Store)
                                                     └─ RDF-Datei als Distribution ablegen (CKAN/Piveau)

Ist bereits eine RDF-Distribution am Datensatz vorhanden, wird der Schritt übersprungen.

1. Auftrag im Staging-Backend

Beim Abschließen erzeugt das Backend einen RDF-Auftrag und schickt ihn an den Scheduler. Alle Angaben, die der Worker zur Verarbeitung braucht, stecken im Auftrag – insbesondere die Quell-URL der Tabular Data Resource, das Ziel im Triple Store und Ziel plus 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 Abschluss-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.

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:

SELECT * WHERE {
  GRAPH <https://ld.schleswig-holstein.de/epayment> {
    ?s ?p ?o
  }
} LIMIT 100

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>.