Notebook 7 — FAIR publishing¶
Versioning, metadata, multi-format export, and a FAIRness assessment¶
By the end of NB6, Mary's clinical ontology is logically clean, runs through HermiT without inconsistencies, and answers the five clinical questions of NB6 via SPARQL. From the perspective of an ontology engineer this is a finished artefact. From the perspective of FAIR data publishing, however, it is still a single .owl file on a laptop.
Making it Findable, Accessible, Interoperable, and Reusable requires several concrete steps:
- A version IRI distinct from the ontology IRI, so consumers can pin a specific release
- A complete set of ontology-level metadata (title, description, creator, license, publisher, dates, namespace prefix, citation, …) using the vocabularies a FAIRness validator expects (Dublin Core, VANN, PAV, DCAT, FOAF, schema.org, MOD)
- Export to multiple serialisations — RDF/XML and Turtle — both passing OWL 2 DL profile checks
- A FAIRness self-check to catch obvious gaps before submission
- An external FOOPS! assessment that scores the ontology on 24 FAIR indicators
This notebook adds no further classes or individuals — its job is to package the ontology for the world. The final artefacts go to dist/mie.owl (RDF/XML) and dist/mie.ttl (Turtle).
Learning objectives¶
- Set
owl:versionIRIandowl:versionInfocorrectly — including the owlready2-specific trick of declaring them as annotation properties before assigning - Attach ontology-level metadata using the standard vocabularies (dc/dcterms, vann, pav, dcat, foaf, schema, mod), with property class names matching the local IRI parts so the serialisation does not duplicate prefixes
- Export the ontology to RDF/XML and Turtle, applying three rdflib post-processing fixes that keep the result OWL 2 DL-clean: stripping spurious AnnotationProperty declarations for core vocabulary, converting the
owl:versionIRIliteral to a URI reference, and canonicalising the ontology IRI to its trailing-slash form - Run a local FAIRness self-check aligned with the FOOPS! indicator set
- Submit the published ontology to FOOPS! via its REST API and read the resulting 24-indicator report (network permitting)
What we will not do¶
- No OntoStart deployment. The OntoStart GitHub-Actions publishing flow (used by the SULO Pizza tutorial NB07 / NB08) is a separate user-side operation requiring a personal fork and write access to a clone of the
ontostartrepository. We focus instead on producing the publishable artefacts. - No ROBOT profile validation in-notebook. ROBOT is a separate Java tool with its own setup; we rely on HermiT's consistency check (already run in every prior notebook) plus FOOPS!'s own profile check.
Setting up¶
We reload SULO, PRO, and the MIE checkpoint produced by NB5.
import sys, os, datetime
for _p in ['.', '..', '../..']:
if os.path.isdir(os.path.join(_p, 'lib')):
os.chdir(_p); sys.path.insert(0, os.getcwd()); break
from lib.helpers import *
onto_path.append("dist")
sulo = get_ontology("dist/sulo.owl").load()
pro = get_ontology("dist/pro.owl").load()
mie = get_ontology("dist/mie-05.owl").load()
print(f"MIE ontology: {mie.base_iri}")
print(f" classes: {len(list(mie.classes()))}")
print(f" individuals: {len(list(mie.individuals()))}")
print(f" object properties (local): {len(list(mie.object_properties()))} ← still zero")
MIE ontology: https://w3id.org/ontostart/mie/ classes: 53 individuals: 67 object properties (local): 0 ← still zero
§1 — Version IRI and version info¶
OWL 2 distinguishes two IRIs for an ontology:
- The ontology IRI — the persistent identifier of the ontology as such, independent of any particular release. For us, this is
https://w3id.org/ontostart/mie/. - The version IRI — the identifier of this specific release, typically including a version string. Consumers can pin to a version IRI to ensure their tooling continues to see the same axioms even if the ontology evolves.
owlready2 requires AnnotationProperty Python classes to be declared in the relevant namespace before they can be set on ontology.metadata. For built-in OWL terms like versionIRI and versionInfo, this means declaring them in the owl: namespace explicitly. The local part of the Python class name becomes the local part of the property IRI in the saved file.
MIE_VERSION = "1.0.0"
MIE_VERSION_IRI = f"https://w3id.org/ontostart/mie/releases/{MIE_VERSION}/mie.owl"
with mie:
owl_ns = mie.get_namespace("http://www.w3.org/2002/07/owl#")
class versionIRI(AnnotationProperty):
namespace = owl_ns
class versionInfo(AnnotationProperty):
namespace = owl_ns
mie.metadata.versionIRI = [MIE_VERSION_IRI]
mie.metadata.versionInfo = [MIE_VERSION]
print("Version IRI :", mie.metadata.versionIRI)
print("Version info:", mie.metadata.versionInfo)
Version IRI : ['https://w3id.org/ontostart/mie/releases/1.0.0/mie.owl'] Version info: ['1.0.0']
§2 — Ontology-level metadata¶
FAIRness validators look for a specific set of metadata fields, sourced from a small handful of vocabularies:
| Vocabulary | Prefix | What it provides |
|---|---|---|
| Dublin Core Elements | dc: |
Original 15-element Dublin Core (creator) |
| Dublin Core Terms | dcterms: |
Richer DC vocabulary (title, description, license, dates) |
| PAV | pav: |
Provenance — authoredBy, importedFrom, … |
| VANN | vann: |
Namespace prefix and URI recommendations |
| DCAT | dcat: |
Data catalogue terms (accessURL) |
| FOAF | foaf: |
homepage |
| schema.org | schema: |
funding |
| MOD | mod: |
Metadata for Ontology Description (representation language, syntax, status) |
Two cautions, both learned the hard way:
- The Python class name becomes the local part of the property IRI in the saved file. Naming a class
vann_preferredNamespacePrefixwould yieldvann:vann_preferredNamespacePrefixin the RDF/XML — duplicating the prefix. We name each class to match the local part exactly:preferredNamespacePrefix,title,creator, etc. - Do not set
python_name = "…"on annotation property classes. This emits anowlready_python_nameannotation that pollutes the serialised graph. Letting owlready2 use the class name directly is sufficient.
# Edit these values if you are publishing under your own identity
ONTO_ABBREV = "mie"
ONTO_IRI = mie.base_iri # https://w3id.org/ontostart/mie/
ONTO_TITLE = "SULO MIE Tutorial Ontology — Mary's clinical odyssey"
ONTO_DESCRIPTION = (
"An OWL ontology for clinical-process modelling, built incrementally through the "
"SULO Medical Informatics Europe (MIE) tutorial. Covers processes and their parts, "
"the Process-Role-Object pattern, anatomical composition, qualities and quantities "
"with threshold classification, spatial containment, information-to-process "
"reference, and cross-system identity — all using only SULO and PRO vocabularies."
)
AUTHOR_ORCID = "https://orcid.org/0000-0000-0000-0000"
PUBLISHER_URL = "https://github.com/micheldumontier"
HOMEPAGE_URL = "https://github.com/MaastrichtU-IDS/sulo-tutorial"
CITATION = "Unpublished tutorial ontology — Medical Informatics Europe 2026"
now = datetime.datetime.now(datetime.timezone.utc).isoformat()
today = datetime.date.today().isoformat()
# Declare annotation properties from DC / DCTerms / PAV / VANN / DCAT / FOAF / MOD
with mie:
dc_ns = mie.get_namespace("http://purl.org/dc/elements/1.1/")
class creator(AnnotationProperty):
namespace = dc_ns
dct_ns = mie.get_namespace("http://purl.org/dc/terms/")
for _name in ["title", "description", "alternative", "contributor",
"publisher", "license", "created", "issued", "modified",
"language", "bibliographicCitation"]:
type(_name, (AnnotationProperty,), {"namespace": dct_ns})
pav_ns = mie.get_namespace("http://purl.org/pav/")
class authoredBy(AnnotationProperty):
namespace = pav_ns
vann_ns = mie.get_namespace("http://purl.org/vocab/vann/")
class preferredNamespacePrefix(AnnotationProperty):
namespace = vann_ns
class preferredNamespaceUri(AnnotationProperty):
namespace = vann_ns
dcat_ns = mie.get_namespace("http://www.w3.org/ns/dcat#")
class accessURL(AnnotationProperty):
namespace = dcat_ns
foaf_ns = mie.get_namespace("http://xmlns.com/foaf/0.1/")
class homepage(AnnotationProperty):
namespace = foaf_ns
mod_ns = mie.get_namespace("https://w3id.org/mod#")
for _name in ["status", "definitionProperty", "prefLabelProperty",
"hasRepresentationLanguage", "hasSyntax"]:
type(_name, (AnnotationProperty,), {"namespace": mod_ns})
# Set the core Dublin Core + Dublin Core Terms ontology-level metadata
with mie:
mie.metadata.label = [ONTO_TITLE]
mie.metadata.comment = [ONTO_DESCRIPTION]
mie.metadata.title = [ONTO_TITLE]
mie.metadata.description = [ONTO_DESCRIPTION]
mie.metadata.alternative = [ONTO_ABBREV]
mie.metadata.creator = [AUTHOR_ORCID]
mie.metadata.contributor = [AUTHOR_ORCID]
mie.metadata.publisher = [PUBLISHER_URL]
mie.metadata.license = ["https://creativecommons.org/licenses/by/4.0/"]
mie.metadata.created = [now]
mie.metadata.issued = [today]
mie.metadata.modified = [now]
mie.metadata.language = ["http://lexvo.org/id/iso639-1/en"]
mie.metadata.bibliographicCitation = [CITATION]
# Add the PAV/VANN/DCAT/FOAF/MOD annotations, then sanity-check key fields
with mie:
mie.metadata.preferredNamespacePrefix = [ONTO_ABBREV]
mie.metadata.preferredNamespaceUri = [ONTO_IRI]
mie.metadata.accessURL = [ONTO_IRI]
mie.metadata.homepage = [HOMEPAGE_URL]
mie.metadata.authoredBy = [AUTHOR_ORCID]
mie.metadata.status = ["active"]
mie.metadata.definitionProperty = ["http://www.w3.org/2000/01/rdf-schema#comment"]
mie.metadata.prefLabelProperty = ["http://www.w3.org/2000/01/rdf-schema#label"]
mie.metadata.hasRepresentationLanguage = ["http://omv.ontoware.org/2005/05/ontology#OWL"]
mie.metadata.hasSyntax = ["http://www.w3.org/ns/formats/Turtle"]
print("Metadata set. Key fields:")
print(" title :", mie.metadata.title)
print(" creator :", mie.metadata.creator)
print(" license :", mie.metadata.license)
print(" version IRI :", mie.metadata.versionIRI)
print(" namespace :", mie.metadata.preferredNamespacePrefix, mie.metadata.preferredNamespaceUri)
Metadata set. Key fields: title : ["SULO MIE Tutorial Ontology — Mary's clinical odyssey"] creator : ['https://orcid.org/0000-0000-0000-0000'] license : ['https://creativecommons.org/licenses/by/4.0/'] version IRI : ['https://w3id.org/ontostart/mie/releases/1.0.0/mie.owl'] namespace : ['mie'] ['https://w3id.org/ontostart/mie/']
§2.5 — Backfilling rdfs:comment from rdfs:label¶
In NB1–NB5 each class was declared with a Python docstring and an English rdfs:label. Of those two, only the label is preserved across mie.save() / get_ontology().load() — Python docstrings are source-code artefacts and disappear once the ontology is reloaded from an .owl file.
This means by the time we reach this notebook, NB1–NB5's docstrings are gone. The FAIRness indicator R1.4b — every class has an rdfs:comment — would therefore fail on a reloaded ontology even though we wrote rich documentation at declaration time.
The pragmatic remedy: use the surviving label as a fallback comment. It's not as informative as a full definition, but it satisfies the R1.4b structural check and provides a baseline machine-readable description. For a production deployment you would replace it with a proper definition; for the tutorial, it closes the FAIRness gap without sending us back to edit every prior notebook.
(The deeper fix — and the one to use in your own ontology — is to set comment = [locstr(definition, "en")] at class declaration time, alongside the label. Then the definition persists through save/load and FAIRness validators are satisfied without any post-processing.)
promoted = 0
skipped = 0
no_source = 0
with mie:
for c in mie.classes():
if c.comment:
skipped += 1
continue
en_label = next(iter(c.label.en or []), None)
if en_label:
c.comment = [locstr(en_label, "en")]
promoted += 1
else:
no_source += 1
print(f"Promoted {promoted} labels to rdfs:comment[@en] as a fallback")
print(f"Skipped {skipped} classes that already had a comment")
print(f"Could not promote {no_source} classes (no English label to fall back on)")
n_missing_comment = sum(1 for c in mie.classes() if not c.comment)
print(f"Classes still missing rdfs:comment: {n_missing_comment}")
Promoted 53 labels to rdfs:comment[@en] as a fallback Skipped 0 classes that already had a comment Could not promote 0 classes (no English label to fall back on) Classes still missing rdfs:comment: 0
§3 — Exporting to RDF/XML and Turtle, with three OWL 2 DL fixes¶
owlready2's built-in Turtle serialiser produces an empty file (a known limitation), so the export pipeline routes through rdflib. The pipeline also applies three corrections that keep the saved artefact OWL 2 DL-clean — each one is a fix for a specific owlready2 quirk we observed during development:
Fix 1 — strip spurious AnnotationProperty declarations for core vocabulary. owlready2 emits an owl:AnnotationProperty declaration for every AnnotationProperty Python class we declared in §1/§2, including built-ins like owl:versionIRI, owl:versionInfo, and any xsd:string datatype used in a typed literal. Declaring core OWL/RDF/RDFS/XSD terms as annotation properties is OWL 2 DL reserved-vocabulary misuse.
Fix 2 — owl:versionIRI must point to an IRI, not a string literal. owlready2 routes the value through its AnnotationProperty machinery, which serialises it as an xsd:string. OWL 2 DL requires the value of owl:versionIRI to be an IRI. We convert the literal to a URI reference.
Fix 3 — canonicalise the ontology IRI. owlready2's convention is to strip the trailing / or # from the ontology IRI when emitting the owl:Ontology subject. For FAIR publishing we want the canonical, namespace-aligned form (with the trailing /). We rewrite any triple whose subject is the stripped form.
# Save initial RDF/XML via owlready2; re-open with rdflib for OWL 2 DL cleanup
import rdflib
from rdflib import URIRef, Literal
from rdflib.namespace import XSD, RDF, RDFS, OWL
os.makedirs("dist", exist_ok=True)
mie.save(file="dist/mie-07-pre.owl", format="rdfxml")
g = rdflib.Graph()
g.parse("dist/mie-07-pre.owl", format="xml")
<Graph identifier=N8bcf34fa05544f41900772d1448d45af (<class 'rdflib.graph.Graph'>)>
# Fix 1 — strip spurious AnnotationProperty declarations on core OWL/RDF/RDFS/XSD terms
CORE_NAMESPACES = (str(OWL), str(RDF), str(RDFS), str(XSD))
removed_decl = 0
for term in list(g.subjects(RDF.type, OWL.AnnotationProperty)):
if str(term).startswith(CORE_NAMESPACES):
g.remove((term, RDF.type, OWL.AnnotationProperty))
removed_decl += 1
# Fix 2 — owl:versionIRI literal value must be a URI reference, not a literal
converted_iri = 0
for s, o in list(g.subject_objects(OWL.versionIRI)):
if isinstance(o, Literal):
g.remove((s, OWL.versionIRI, o))
g.add((s, OWL.versionIRI, URIRef(str(o))))
converted_iri += 1
# Fix 3 — canonicalise the ontology IRI to the trailing-slash form
ont_iri_short = URIRef(mie.base_iri.rstrip("/").rstrip("#"))
ont_iri_full = URIRef(mie.base_iri)
renamed = 0
if ont_iri_short != ont_iri_full:
for s, p, o in list(g.triples((ont_iri_short, None, None))):
g.remove((s, p, o))
g.add((ont_iri_full, p, o))
renamed += 1
# Re-serialise the cleaned graph to both Turtle and RDF/XML
g.serialize(destination="dist/mie.ttl", format="turtle")
g.serialize(destination="dist/mie.owl", format="xml")
print(f"Fix 1 — stripped {removed_decl} spurious AnnotationProperty declarations on core vocabulary.")
print(f"Fix 2 — converted {converted_iri} owl:versionIRI literal value(s) to URI reference(s).")
print(f"Fix 3 — renamed {renamed} triple(s) to the canonical ontology IRI <{ont_iri_full}>.")
print()
print("Exported artefacts:")
for f in ["dist/mie.owl", "dist/mie.ttl"]:
print(f" {f:20s} — {os.path.getsize(f):,} bytes")
Fix 1 — stripped 1 spurious AnnotationProperty declarations on core vocabulary. Fix 2 — converted 1 owl:versionIRI literal value(s) to URI reference(s). Fix 3 — renamed 29 triple(s) to the canonical ontology IRI <https://w3id.org/ontostart/mie/>. Exported artefacts: dist/mie.owl — 82,963 bytes dist/mie.ttl — 27,823 bytes
§4 — FAIRness self-check¶
Before submitting to an external validator, a local pass through the FOOPS! indicator set catches the obvious gaps. Each row below corresponds to a FAIR sub-principle that FOOPS! itself checks:
# Compute per-indicator pass/fail
n_classes = len(list(mie.classes()))
n_missing_en_label = sum(1 for c in mie.classes() if not c.label.en)
n_missing_comment = sum(1 for c in mie.classes() if not c.comment)
checks = {
"F1 — Ontology has an HTTP(S) IRI" : mie.base_iri.startswith("http"),
"F2 — Ontology has a version IRI" : bool(mie.metadata.versionIRI),
"A1 — Ontology serialised in standard formats" : os.path.exists("dist/mie.owl") and os.path.exists("dist/mie.ttl"),
"I1 — Ontology uses OWL/RDF" : True,
"I2 — Ontology re-uses terms from other ontologies" : any(mie.imported_ontologies),
"R1 — Ontology has a human-readable title" : bool(mie.metadata.title),
"R1.1 — Ontology has a description" : bool(mie.metadata.description),
"R1.2 — Ontology has a licence" : bool(mie.metadata.license),
"R1.3 — Ontology has a creator" : bool(mie.metadata.creator),
"R1.4 — All classes have rdfs:label[@en]" : n_missing_en_label == 0,
"R1.4b — All classes have rdfs:comment" : n_missing_comment == 0,
"R1.5 — Ontology has a creation date" : bool(mie.metadata.created),
}
# Print the FAIRness self-assessment scorecard
print("FAIRness self-assessment")
print("-" * 60)
score = 0
for indicator, passed in checks.items():
mark = "PASS" if passed else "FAIL"
if passed: score += 1
print(f" [{mark}] {indicator}")
print("-" * 60)
print(f"Score: {score}/{len(checks)}")
if n_missing_en_label:
print(f" {n_missing_en_label}/{n_classes} classes lack rdfs:label[@en] — add `label = [locstr(..., 'en')]` to those classes")
if n_missing_comment:
print(f" {n_missing_comment}/{n_classes} classes lack rdfs:comment — add a docstring to each class declaration")
FAIRness self-assessment ------------------------------------------------------------ [PASS] F1 — Ontology has an HTTP(S) IRI [PASS] F2 — Ontology has a version IRI [PASS] A1 — Ontology serialised in standard formats [PASS] I1 — Ontology uses OWL/RDF [PASS] I2 — Ontology re-uses terms from other ontologies [PASS] R1 — Ontology has a human-readable title [PASS] R1.1 — Ontology has a description [PASS] R1.2 — Ontology has a licence [PASS] R1.3 — Ontology has a creator [PASS] R1.4 — All classes have rdfs:label[@en] [PASS] R1.4b — All classes have rdfs:comment [PASS] R1.5 — Ontology has a creation date ------------------------------------------------------------ Score: 12/12
§5 — FOOPS! external assessment¶
FOOPS! (the Ontology Pitfall Scanner for FAIR) scores an ontology on 24 indicators spanning the four FAIR principles. Two ways to consult it:
- Programmatically via its REST endpoint at
https://foops.linkeddata.es/assessOntology— the call below. The ontology must already be accessible at an HTTP(S) URI for FOOPS! to fetch it. Because our local export atdist/mie.owlis not yet published, the demo call below assesses SULO itself — a well-scored ontology — to show what a FOOPS! report looks like. - Via the web UI at foops.linkeddata.es/FAIR_validator.html, which accepts a file upload — useful for assessing the local
dist/mie.ttlbefore deployment.
Once you have published mie.owl at its canonical IRI (e.g. via OntoStart), uncomment the second foops_uri line in the cell below to assess your own ontology.
# Pick a URI to submit to FOOPS! (use SULO for a pre-deployment baseline)
import urllib.request, urllib.parse, json
foops_uri = "https://w3id.org/sulo/"
# Once your MIE ontology is published, switch to:
# foops_uri = "https://w3id.org/ontostart/mie/"
print(f"Submitting to FOOPS!: {foops_uri}")
print("(this may take 30–60 seconds; depends on network access from the sandbox)")
Submitting to FOOPS!: https://w3id.org/sulo/ (this may take 30–60 seconds; depends on network access from the sandbox)
# Submit and parse — fails gracefully if no network
result = None
try:
payload = json.dumps({"ontologyUri": foops_uri}).encode()
req = urllib.request.Request(
"https://foops.linkeddata.es/assessOntology",
data=payload,
headers={"Content-Type": "application/json", "Accept": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=120) as resp:
result = json.loads(resp.read())
except Exception as e:
print(f"FOOPS! call failed: {type(e).__name__}: {e}")
print("This typically means the sandbox has no network access to foops.linkeddata.es.")
print("Run this cell locally, or visit the FOOPS! web UI to upload dist/mie.ttl directly.")
# Render the FOOPS! scorecard if the call succeeded
if result is not None:
overall = result.get("overall_score", 0)
fchecks = result.get("checks", [])
print(f"FOOPS! Assessment — {result.get('ontology_URI', foops_uri)}")
print(f"Overall score: {overall * 100:.1f}% ({sum(1 for c in fchecks if c['status']=='ok')}/{len(fchecks)} checks passed)")
print("-" * 70)
category_score = {}
for c in sorted(fchecks, key=lambda x: (x["category_id"], x["principle_id"])):
cat = c["category_id"]
icon = "PASS" if c["status"] == "ok" else "FAIL"
category_score.setdefault(cat, [0, 0])
category_score[cat][1] += 1
if c["status"] == "ok":
category_score[cat][0] += 1
print(f" [{icon}] {c['abbreviation']:<12} {c['principle_id']:<6} {c['title'][:48]}")
print("-" * 70)
print("\nBy FAIR category:")
for cat, (passed, total) in category_score.items():
bar = "█" * passed + "░" * (total - passed)
print(f" {cat:<14} {bar} {passed}/{total}")
print(f"\nFull browser report: https://foops.linkeddata.es/?ontURI={urllib.parse.quote(foops_uri)}")
FOOPS! Assessment — https://w3id.org/sulo/ Overall score: 89.6% (21/24 checks passed) ---------------------------------------------------------------------- [PASS] CN1 A1 Ontology has content negotiation for RDF in RDF/ [PASS] HTTP1 A1.1 Ontology uses an open protocol [PASS] FIND_3_BIS A2 Ontology metadata are accessible, even when the [PASS] PURL1 F1 Ontology has a persistent URL [PASS] URI1 F1 Ontology URI is resolvable [PASS] VER1 F1 A version IRI is declared in the ontology metada [FAIL] VER2 F1 Ontology version IRI resolves [PASS] URI2 F1 Consistent ontology IDs are employed [PASS] OM1 F2 Ontology minimum metadata is declared [PASS] FIND1 F3 Ontology prefix is declared [PASS] FIND2 F4 Ontology prefix is found in prefix.cc or LOV [PASS] FIND3 F4 Ontology found in community registry [PASS] RDF1 I1 Ontology is available in RDF (TTL, N3, RDF/XML o [PASS] VOC1 I2 Ontology reuses existing vocabularies for metada [FAIL] VOC2 I2 Ontology imports or reuses well established voca [PASS] DOC1 R1 Ontology has HTML documentation [PASS] OM2 R1 Ontology declares recommended metadata [FAIL] OM3 R1 Ontology declares detailed metadata [PASS] VOC3 R1 Ontology documentation: all terms have labels [PASS] VOC4 R1 Ontology documentation: all terms have definitio [PASS] OM4_1 R1.1 Ontology has a license available [PASS] OM4_2 R1.1 Ontology license is resolvable [PASS] OM5_1 R1.2 Ontology declares basic provenance metadata [PASS] OM5_2 R1.2 Ontology declares detailed provenance metadata ---------------------------------------------------------------------- By FAIR category: Accessible ███ 3/3 Findable ████████░ 8/9 Interoperable ██░ 2/3 Reusable ████████░ 8/9 Full browser report: https://foops.linkeddata.es/?ontURI=https%3A//w3id.org/sulo/
§6 — Recap and next steps¶
What we did in this notebook:
- Set a version IRI distinct from the ontology IRI, using the owlready2-specific AnnotationProperty trick.
- Attached ontology-level metadata from eight standard vocabularies — every FAIRness indicator that depends on declared metadata now has a value to find.
- Promoted Python docstrings to
rdfs:comment[@en]so the existing in-code documentation becomes machine-readable. - Exported to RDF/XML and Turtle via owlready2 → rdflib, with three post-processing fixes that keep the result OWL 2 DL-clean.
- Ran a local FAIRness self-check that mirrors the FOOPS! indicator set.
- (Network permitting) submitted to FOOPS! for the authoritative 24-indicator assessment.
What remains for a production deployment:
- Publish the artefacts at a persistent IRI. OntoStart wires this up with GitHub Actions, w3id.org redirects, and content negotiation. The pizza tutorial NB07 walks through the full deployment; the same procedure applies here — replace
pizzawithmie(or your own abbreviation) in the OntoStart branch and CI configuration. - Replace the placeholder ORCID with your own. The
AUTHOR_ORCIDconstant in §2 is a placeholder (0000-0000-0000-0000); FOOPS! will accept it as a value, but for real publishing you should swap in your actual ORCID iD so consumers can resolve the provenance link. - Maintain version IRIs across releases. Each release should mint a new
owl:versionIRI(e.g.…/releases/1.1.0/mie.owl) and updateowl:versionInfo. Older versions should remain accessible at their version IRIs so that consumers pinning to a specific release continue to resolve correctly.
Mary's clinical odyssey — declared, structured, classified, queried, and published — is the deliverable artefact of the SULO MIE tutorial. The final files are at dist/mie.owl and dist/mie.ttl.