Repository navigation
Add the first cut of the LinkML schema for one MHPKG slice - #54
Merged
Merged
Conversation
Covers a municipal heat plan, its target scenario and one final energy consumption value: deep enough to test the LinkML-authors-SHACL decision against real MHPO and OEO terms, small enough to finish. The decision holds. gen-shacl puts class_uri straight into sh:targetClass, so the generated shapes constrain MHPO and OEO IRIs directly rather than a LinkML-shaped shadow of them. MHPO's role reification did not bite here, because OEO's quantity value already reifies the same way LinkML would — but that is a deferral, not a clearance: the areas-and-roles subtree is three quarters of MHPO and this slice does not enter it. Enums are the pattern to reuse. OEO models units, carriers and sectors as classes while a statistic is about the carrier type, so pointing at a class IRI is unavoidable punning. An enum whose every meaning: is an OEO IRI generates sh:in, a real value-set constraint instead of sh:nodeKind sh:IRI. oekg/shapes/oekg_shapes.ttl already does this by hand for ~120 IRIs. Four things that will shape later tickets: * The IRI policy cannot be generated. SHACL constrains a node's own IRI with sh:pattern at node level and LinkML cannot emit that; a type-level pattern is dropped (silently with uri: set, otherwise logged as ERROR and still exit 0), and on an identifier slot it lands on an invented property that no triple uses, so it can never fire. Hence the hand-written companion file — the first artifact here that is not generated. * Enum membership cannot be checked by anything in this repository. OEO's energy carrier has four asserted subclasses, all abstract; natural gas qualifies only by inference. Confirming membership needs a reasoner and the toolchain deliberately has none. * MH-02 §6 and MHPO collide over the municipality. The policy reuses municipality/AGS_06611000 for the commissioning body, but the only class for it is municipality area — a spatial region cannot commission a plan, and the two IRI shapes cannot both be satisfied. Needs a human. * pyshacl accepts repeated -s and silently uses only the last one. The first validation run reported Conforms: True against both shape files while consulting neither, and still passed with a required label deleted. validate.py merges the shapes into one graph for that reason. Also recorded: closed shapes reject provenance triples; German labels must be plain literals because @de is rdf:langString and fails sh:datatype xsd:string; and Turtle cannot abbreviate these IRIs at all, since a prefixed name may not contain an unescaped slash. Five term requests are left as commented blocks at the position they would occupy, so the holes are visible in the artifact. No term is minted here and gen-owl is never run. Verified with linkml 1.11.1, pyshacl 0.40.1, rdflib 7.6.0 against MHPO pin 34776b6 and OEO 2.13.0. validate.py asserts all 14 negative-control violations are caught and exits 1 when the IRI shapes are removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8 tasks
`gen-shacl` output is not byte-stable across runs. Every sh:property is a blank node and rdflib mints fresh blank-node ids per process, so the property blocks come out in a different order every time: three consecutive runs of the same unchanged schema gave three different SHA-256 sums, and PYTHONHASHSEED=0 does not help (three more runs, three more sums). So this is blank-node id generation, not dict ordering. That matters because the obvious drift check is a diff — it is what mhpkg/mhpo/extract_terms.py --check does for terms.csv — and here it would fail on every run while nothing was wrong. A check that cries wolf every time is worse than no check, because people learn to ignore it. check_generated.py compares the committed file to a fresh generation graph-isomorphically via rdflib.compare.to_isomorphic, and confirms the two are equal today at 206 triples. On failure it reports the concrete constraint that differs rather than the raw set difference, which after canonicalisation relabels every blank node and names nothing anyone can act on: a planted `sh:minInclusive 1999` is now reported as exactly that. It also fails when the generator logs an error while still succeeding. gen-shacl drops a type-level pattern and exits 0, reporting `No URI for type X` only through the logger, so the generator runs in-process here and its log records are treated as part of the result. Verified with a type-level pattern on a type an actual slot uses — the earlier attempt put it on an unused type, which never gets resolved and so never logs. Kept separate from validate.py on purpose: this needs the `schema` group because it regenerates, while validate.py needs only `graph`. That split is MH-04's and it exists because linkml is 88 of the 95 packages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four files said things that stopped being true when mhpkg/schema/ landed. The rule applied is the one MH-04 used: fix what your own change falsified, and leave the rest to the ticket that owns it. docs/mhpkg/index.md said "SHACL shapes: none yet" and described the model as one Termboard draft. It also carried the provisional-name warning telling readers not to bake `mhpkg` into IRIs or namespace prefixes — which the committed schema now does, so a reader following the published docs would have refused the namespace the code depends on. That is the worst of these: not stale, but contradicted by shipped code. docs/tech-stack.md said the schema does not exist yet and that gen-shacl "has nothing to point at", and its workflow diagram still marked the model source of truth UNDECIDED. The diagram now distinguishes what is built from what is not, and records the two limits that constrain the generated-artifacts question: the IRI policy cannot be generated, and generated SHACL is not byte-stable so a drift check must compare graphs. Its "what validates what" section needed care rather than deletion. "No SHACL file here validates a live graph" is still true — the new shapes validate example files, not the store — so it is kept and sharpened, split into what was authored against a dump and what is now exercised on every validate.py run. Added while checking: the old OEKG shapes cannot match the live graph even in principle, because they use a hyphenated http OEO namespace and property names OEO does not define. That namespace redirects, but an IRI is identity rather than an address. mhpkg/README.md claimed only one directory exists, and listed the model source of truth and the Fuseki dataset as open questions. Both are now decided; the internal layout is still open and stays listed. CONTRIBUTING.md trap 4 carried the same superseded provisional-name warning. The map had assigned that edit elsewhere, but leaving one of three copies of a contradicted warning is worse than fixing all three, and this change is what contradicts it. Rewritten in the style trap 5 already uses for a corrected claim, so anyone who read the old text sees what changed. Not touched: the graph/table boundary, still genuinely undecided, and the absence of a SHACL CI job, which remains true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 of the discussion
Covers a municipal heat plan, its target scenario and one final energy consumption value: deep enough to test the LinkML-authors-SHACL decision against real MHPO and OEO terms, small enough to finish.
The decision holds. gen-shacl puts class_uri straight into sh:targetClass, so the generated shapes constrain MHPO and OEO IRIs directly rather than a LinkML-shaped shadow of them. MHPO's role reification did not bite here, because OEO's quantity value already reifies the same way LinkML would — but that is a deferral, not a clearance: the areas-and-roles subtree is three quarters of MHPO and this slice does not enter it.
Enums are the pattern to reuse. OEO models units, carriers and sectors as classes while a statistic is about the carrier type, so pointing at a class IRI is unavoidable punning. An enum whose every meaning: is an OEO IRI generates sh:in, a real value-set constraint instead of sh:nodeKind sh:IRI. oekg/shapes/oekg_shapes.ttl already does this by hand for ~120 IRIs.
Four things that will shape later tickets:
Also recorded: closed shapes reject provenance triples; German labels must be plain literals because @de is rdf:langString and fails sh:datatype xsd:string; and Turtle cannot abbreviate these IRIs at all, since a prefixed name may not contain an unescaped slash.
Five term requests are left as commented blocks at the position they would occupy, so the holes are visible in the artifact. No term is minted here and gen-owl is never run.
Verified with linkml 1.11.1, pyshacl 0.40.1, rdflib 7.6.0 against MHPO pin 34776b6 and OEO 2.13.0. validate.py asserts all 14 negative-control violations are caught and exits 1 when the IRI shapes are removed.
Type of change (CHANGELOG.md)
Added
mhpkg/schema/: first cut of the MHPKG data shape — a LinkML schema for one slice, the SHACLgenerated from it, a hand-written companion enforcing the IRI policy, and a passing plus a
deliberately failing example (#54)
Updated
pyproject.tomlanduv.lockare deliberately untouched, sinceMH-04's
schemaandgraphdependency groups already carried everything needed.Removed
Workflow checklist
Automation
Closes #55
PR-Assignee
Reviewer