feat(connector): DCAT-AP 3.0 transformer implementation - dataset/series building, license & language resolution, dual-format caching - #208
Open
Vishmayraj wants to merge 72 commits into
Conversation
… currently just HARVEST_INTERVAL_MINUTES
…iting a base harvester error class
…we have dataclass in harvester.py
…catalog from initial HTTP arch
… and including them in main api.py
… with a /connector endpoint
…ce transformation time
…but using a seperate connection for lock and harvest
…as already upstream
… and .ttl as turtle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements
dcat_transformer.py, the DCAT-AP 3.0 counterpart to the STAC transformer from #195, scoped and stubbed in #204. It builds SHACL-conformantdcat:Catalog/dcat:DatasetSeries/dcat:Dataset/dcat:Distributiongraphs from the same harvested Postgres data STAC consumes, wires the DCAT side of the master-switch system, adds license/language URI resolution, and serves the result through Redis-cached JSON-LD and Turtle endpoints.This closes out the DCAT-AP mapping work scoped as "next PR" in #204 (weeks 8–10, wrapped in the first half of week 10); automated test coverage for the connector package is next.
What's in this PR
DCAT-AP 3.0 transformer (
dcat_transformer.py)Datastream -> dcat:Dataset,Thing -> dcat:DatasetSeries, STA root -> dual-typeddcat:Catalog+dcat:DataService,Network -> dcat:Catalogsub-catalog.build_dcat_catalog()for the single-scope (NETWORK=0) case andbuild_dcat_catalog_with_networks()for the scoped case, returning one independentrdflib.Graphper scope (root, orphan, per-Network) rather than a merged quad store, so each scope serializes and caches on its own.NETWORK=1is deliberately structural-only (Catalog+DataService+ Agents +dct:hasPart), with orphan and each Network carrying their ownDataset/DatasetSeriescontent.dcat:Datasetnodes carry spatial (dct:spatial->dct:Location->locn:Geometry->geosparql:asGeoJSON), temporal (dct:temporal->dct:PeriodOfTime), theme/subject, license, access rights, contact point (vcard), and threedcat:Distributions per Dataset mirroring STAC's Item Assets (JSON observations, CSV export, Datastream entity metadata).dcat:DatasetSeriesextent (bbox/temporal) is computed per-scope from its own successfully-built Datasets, with a fallback to the Thing's own Location geometry, same fallback chain as STAC's Collection extent, kept independent per scope.DatasetSeriesis deliberately not linked viadcat:dataset(DCAT-AP has no clean "Catalog contains this Series" predicate because of an open gap, see DatasetSeries subClassOf Resource instead of Dataset SEMICeu/DCAT-AP#289); still, reachability is preserved one hop either direction viadcat:inSeries/dct:isPartOf.License & language resolution (
config.py)resolve_license_uri(): normalizes a license value (URI passthrough, SPDX-id lookup viaSPDX_LICENSE_URIS, or best-effortspdx.orgguess with a warning) into something safe to wrap inURIRef, shared by everydct:licensetriple across catalog, dataset, and distribution nodes.resolve_language_uri(): maps a BCP-47 tag to its EU Publications Office NAL URI (EU_LANGUAGE_AUTHORITY_URIS) fordct:language, since DCAT-AP 3.0 requires askos:Concept/dct:LinguisticSystemURI here, not a bare literal; thus, distinct from thelang=tag used ondct:title/dct:description/dcat:keywordliterals, which is untouched.Settingsmethods) sodcat_transformer.pycan import them directly without needing an instance.Master-switch system
STAC_TRANSFORMER/DCAT_TRANSFORMERenv-var flags, parsed once at import time inconfig.pyvia a dedicated_env_flag()helper (explicit"1"opt-in, not truthy-string coercion) so both the scheduler andapi.pyread the same parsed value instead of two independently-parsed copies drifting apart.api.pygates every DCAT route behind@_require_enabled(DCAT_TRANSFORMER, "DCAT_TRANSFORMER"), returning a 404 with an explicit "set this env var to enable it" message, distinct from the 503 used for "enabled but not harvested yet."Dual-format caching (
cache.py)dcat:graph:{scope}/dcat:graph:{scope}:jsonldkeys using oneGraph.serialize()call per format at write time, so reads are a flat RedisGET, never a per-request re-serialization.write_dcat_catalog()/write_dcat_catalog_with_networks()purge staledcat:*keys before writing (sameSCAN-then-delete pattern as the STAC cache writer), so a reader hitting mid-write sees a temporary miss rather than a mixed old/new graph.dcat:meta:*vsstac:meta:*), so one transformer failing doesn't get reported as if both did.API layer (
api.py)/connector/dcat/root,/connector/dcat/orphan,/connector/dcat/{network_id}routes, each with a.ttlsibling for Turtle whilst JSON-LD is the unsuffixed default as the more broadly consumed format./dcat/rootand/dcat/orphanroutes are registered ahead of the dynamic/dcat/{network_id}route so the path parameter can't swallow them./connectorroot now reportsdcat_enabled,dcat_mandatory_fields_set, and DCAT availability/last-fetch alongside the existing STAC fields.Compliance fixes (last few commits)
dct:languagecorrected to point at a URI (EU NAL concept), not a literal string.skos:Concepttyping and several other RDF-shape fixes (rdfs:ResourceonaccessURL/endpointURLtargets,dct:Standardtyping onconformsTotargets,dct:LicenseDocument/dct:RightsStatement/dct:LinguisticSystemtyping) surfaced by running the output against the official DCAT-AP SHACL shapes.Notes
pyshaclagainst the official DCAT-AP 3.0 SHACL shapes end-to-end - all scopes (root, orphan, per-Network) validate clean with zero violations.Testing
Validated against the
dummy_datamodule's generated dataset, both underNETWORK=0andNETWORK=1. RDFLib parse-back andpyshaclSHACL validation confirm zero dangling links and full DCAT-AP 3.0 conformance across all generated scopes.