Tech stack#
What these two knowledge graphs are built with — and, just as importantly, which parts are not decided yet. Anything below marked open is genuinely open; nothing here should be read as a commitment that has not been made.
Settled#
| Concern | Choice | Notes |
|---|---|---|
| Data model | RDF | both graphs |
| Serialisation | Turtle for the OEKG, RDF/XML for the MHP draft | the MHP draft's format is an artefact of its export tool, not a decision |
| Triple store | Apache Jena Fuseki | the OEKG is served from a Fuseki dataset |
| Query protocol | SPARQL | see the endpoint |
| OEKG schema | Open Energy Ontology (OEO) | |
| MHP schema | MHPO | imports from and aligns to OEO |
| Graph manipulation | rdflib |
also how the platform writes to the store |
| SHACL validation | pyshacl |
works; but see the warning below about what it validates |
| Documentation | mkdocs + mkdocs-material | this site |
The repositories#
Four repositories are in play, and knowing which owns what saves a lot of confusion:
| Repository | Owns |
|---|---|
oekg (this one) |
both graphs' data, shapes and documentation |
ontology |
the Open Energy Ontology |
municipal-heat-planning-ontology |
MHPO |
municipal-heat-planning-pdf-processing |
the RAG pipeline over published heat plans |
oeplatform |
the Open Energy Platform — consumer and writer of the OEKG, via factsheets |
Note the direction of that last row: oeplatform writes the OEKG over SPARQL. It does not read
files from this repository. See how the OEKG is populated.
Versions#
| OEO release vendored in the archive | v2.8.0 (oekg/archive/madbkr_ba/oekg_rework/oeo-full.owl) |
| OEO release the live graph targets | tracks the current OEO; not pinned here |
The vendored copy exists to keep the archived thesis self-contained. It is not the version to
work from — take OEO releases from the
ontology repository.
Dependencies and environment#
Managed with uv. pyproject.toml declares the dependency
groups, uv.lock pins the exact resolved versions, and .python-version pins the interpreter —
which uv downloads itself, so no system Python is required.
| File | Role |
|---|---|
pyproject.toml |
dependency groups; package = false (this repo is not an importable package) |
uv.lock |
committed — the exact resolved set, so CI installs what you have locally |
.python-version |
the interpreter uv fetches (3.13); requires-python is >=3.11 |
The commands a contributor runs live in
CONTRIBUTING.md,
deliberately in one place rather than copied here — two copies of setup instructions diverge
within months. That page also lists the traps, including the fact that the dev server does not
serve at /.
There are three groups: docs (the documentation build), schema (LinkML, which generates
the SHACL shapes) and graph (pyshacl and rdflib). schema and graph are separate
because linkml accounts for 88 of the 95 packages they resolve to between them, and a job that
only validates has no reason to install a generator.
Pull-request checks run uv lock --check, which fails if uv.lock is out of step with
pyproject.toml, then install every group and build the documentation with --strict. The deploy
workflow installs with --locked, which fails the same way.
--frozen does not assert anything — corrected 2026-08-07
This page and CONTRIBUTING.md both used to state that CI enforced the lockfile with
--frozen. That was wrong, and the guarantee it described did not exist: --frozen means
"sync without updating the lockfile", so it accepts a stale lock and exits 0. Verified by
running uv sync --group docs --frozen against a pyproject.toml carrying a dependency group
absent from uv.lock — it passed. The flags that actually assert are --locked and
uv lock --check, and both are now in use. Compounding it, the deploy workflow does not run on
pull requests at all, so nothing checked anything before merge; that is what checks.yml is
for.
This diverges from the rest of the Open Energy Family
Other OEP repositories use a plain requirements.txt with pip. This one deliberately does
not. The trade was made knowingly: a single source of truth plus a real lockfile was judged
worth more than byte-for-byte consistency with a 15-line workflow. If you maintain other family
repos, this is the one place this repo will surprise you.
The archived thesis scripts are still not reproducible
oekg/archive/madbkr_ba/scripts/ was written against unrecorded versions of rdflib and
owlready2, needs a Java toolchain for sync_reasoner(), and one script raises TypeError on
every invocation. It is closed work; the archive makes no reproducibility claim. The
graph group now locks an rdflib — but it locks it for new work, not for these scripts,
and it does not lock owlready2 or provide Java. Nothing here resurrects them.
What is still open#
Model-design questions rather than repository-structure ones, worked as a separate effort. The first of them has since been decided and is kept here, marked as such, so that anyone who read the old text sees what changed rather than finding the section quietly gone.
The model source of truth#
Decided (2026-08-07), and no longer "under consideration". LinkML is the authoring layer: one schema definition generates the SHACL shapes that validate the data. LinkML never mints domain terms — every class and slot points at an IRI owned by the municipal heat planning ontology (MHPO) or by OEO — and OWL generation stays off, so it never competes with MHPO as a source of terms.
The toolchain for this is now installed and locked, in the schema and graph groups above.
The schema itself does not exist yet, so nothing in this repository generates shapes today —
gen-shacl works but has nothing to point at. Authoring it is the next step, and it is being
worked as a separate effort.
What validates what#
⚠️ No SHACL file in this repository validates any live graph. Every one of them was authored against a dump:
oekg/shapes/— the most developed shapes available, written against the thesis-reworked graphoekg/eval/oekg_shacl.txt— evaluation shapes for the same era- the archive's own copies — thesis provenance
So pyshacl "working" does not mean there is a validation pipeline. There is not one yet.
Namespace migration#
The project has moved from http://openenergy-platform.org/… to
https://openenergyplatform.org/…. The live graph has been updated. Some files here have not —
notably oekg/shapes/ and the competency-question SPARQL in oekg/eval/.
This matters practically: a query or shapes file copied from those locations may silently return
nothing against the live graph, because a zero-result SPARQL query is indistinguishable from a
correct query about absent data. Files under oekg/legacy/ and oekg/archive/ use the old form
correctly — they are dated artefacts and should not be changed.
Where the heat-planning graph will be hosted#
Undecided. Either a second dataset in the Fuseki store that already serves the OEKG, or a separate instance. Access, backup and governance ride on the answer.
The graph / table boundary#
Undecided. Which extracted heat-plan data belongs in the graph as triples, and which is better served as a table on the Open Energy Platform. See Workflow.
The modelling and build workflow — planned, not built#
This diagram describes an intention, not reality
None of the pipeline below exists today. It is drawn to make the open decisions visible and to give the shapes-first work a starting point to argue with — not to record an agreed design. The boxes marked undecided are the actual open questions listed above.
Do not treat this as the agreed pipeline. When a decision is made, the marker in this diagram should be replaced by the answer.
flowchart LR
SRC["Model source of truth<br/>SHACL-first or LinkML<br/>UNDECIDED"]
GEN["Generated artifacts<br/>what exactly: UNDECIDED"]
SHAPES["SHACL shapes"]
DATA["Graph data"]
VAL["Validation<br/>pyshacl"]
LOAD["Load into Fuseki"]
SRC --> GEN
SRC --> SHAPES
SHAPES --> VAL
DATA --> VAL
VAL -- passes --> LOAD
VAL -- fails --> SRC
The shape of it is not controversial — author a model, generate from it, validate data against it, load what passes. What is undecided is what the leftmost box actually is, and that determines everything downstream.
Documentation and CI#
| Site generator | mkdocs 1.x with mkdocs-material |
| Diagrams | mermaid, via Material's pymdownx.superfences custom fence |
| Build strictness | strict: true plus an explicit validation: block |
| Hosting | GitHub Pages |
The mkdocs~=1.6 pin is deliberate — do not relax it to allow 2.0
The Material for MkDocs team warns that MkDocs 2.0 introduces backward-incompatible changes to the framework Material is built on: the plugin system is removed (all plugins stop working), the theming system is rewritten (all overrides break), no migration path exists, the contribution model is closed, and it is currently unlicensed — unsuitable for production use. Material itself is not deprecated; it is actively maintained and Production/Stable.
mkdocs~=1.6 resolves to >=1.6, ==1.*, which permits 1.9 but blocks 2.0 — verified. Keep
it that way until the situation upstream resolves. See the
Material team's analysis.
Why the validation: block exists#
strict: true alone is not enough. mkdocs 1.6 does not check #anchors by default, so a
strict build passes happily while cross-page anchor links rot. These pages carry several such
links, so the config adds:
validation:
anchors: warn
unrecognized_links: warn
absolute_links: warn
With strict: true, a warning becomes a build failure. This was verified with a negative control —
an anchor was deliberately broken and the build aborted with exit 1 — so the passing build is a
real result and not a disabled check.
Documentation is the only thing this repository has CI for. There is no test suite and no validation job; adding SHACL validation to CI is an obvious future step, but it depends on the model source of truth above being decided first.