OntoMeter
Ontology quality assessment. Point it at an OWL/RDF/Turtle file and it computes the sixteen OQuaRE metrics, scales each to 1–5, rolls them up into quality characteristics, and — where a language model is configured — adds a review of the things structural metrics cannot see.
pip install ontometer
ontometer eval my-ontology.ttl
1. What you get
$ ontometer eval my-ontology.ttl --no-review
# Ontology evaluation — `my-ontology.ttl`
**Overall 3.41/5** across 7 characteristics, aggregated by `mean`.
## Characteristics
| Characteristic | Score | |
|---------------------|-------|---------|
| Functional adequacy | 2.73 | `███··` |
| Structural | 4.12 | `████·` |
| Maintainability | 5.00 | `█████` |
## Metrics
| Metric | Name | Value | Score | Formula |
|-----------|--------------------|-------|-------|-----------------------------------------------|
| `AROnto` | Attribute richness | 0.167 | 1/5 | datatype-property domain axioms / \|classes\| |
| `CROnto` | Class population | 0.167 | 1/5 | \|individuals\| / \|classes\| |
| `DITOnto` | Depth of hierarchy | 2.000 | 5/5 | max longest-path depth from a local root |
## Weakest metrics, and what drives them
**AROnto** (Attribute richness) — 1/5
- `http://example.org/#hasOwner`
- `http://example.org/#worksFor`
Every metric reports the formula it actually computed. That is not decoration: the published OQuaRE sources disagree with each other on several metrics, and one of them gives the same definition twice for two different metrics. Rather than pick a reading and leave you to discover the difference from a diverging number, each one states its own.
2. The review
Structural metrics cannot tell you whether an ontology models its domain correctly, whether its terms mean what their names suggest, or whether it is internally consistent. OQuaRE knows this — a large minority of its sub-characteristics have no supporting metric at all:
ontometer model --what unsupported
That gap is what the review is for. It receives the weakest metrics and the specific entities driving them, and returns findings ranked by what they actually break:
defect— changes what a reasoner concludes. A property declaredrdfs:subPropertyOfa datatype; twordfs:domaintriples on one property, which OWL reads as conjunction so every subject is inferred into both classes at once; a cycle in the subsumption hierarchy.weakness— sound but will cause trouble. Undeclared domains, absent inverse properties on a graph built for navigation.suggestion— style and documentation.
The model never produces a number. Metrics are computed; the review explains and recommends.
export LLM_PROVIDER=ollama
export LLM_MODEL_NAME=llama3.1
ontometer eval my-ontology.ttl
Configuration is read from the process environment:
| Variable | Meaning | Default |
|---|---|---|
LLM_PROVIDER |
openai or ollama |
openai |
LLM_MODEL_NAME |
Model identifier | gpt-4o-mini |
LLM_BASE_URL |
Endpoint override; any OpenAI-compatible server works | provider default |
LLM_API_KEY |
Required for openai, unused for ollama |
— |
LLM_TEMPERATURE |
Sampling temperature | 0.0 |
LLM_PARSE_RETRIES |
Retries when the reply does not validate | 2 |
Metrics compute with or without a provider. With none configured the report still renders and says why the review is missing, because a quality checker that fails closed on an absent API key is useless in the CI pipelines most of these will run in.
3. Using it in CI
ontometer eval ontology.ttl --no-review --format json -o report.json
ontometer eval ontology.ttl --no-review --fail-under 3.5
--fail-under exits non-zero when the overall score drops below a threshold. Treat the
overall number as a tripwire for regressions, not as a verdict — OQuaRE defines no overall
score, and this one is an unweighted mean of characteristic scores.
4. The quality model is configuration
Thresholds and the characteristic → sub-characteristic → metric map ship as YAML read at runtime, not compiled in. OQuaRE's own thresholds live in online resources rather than the archival record and were never fixed in a citable form, so they are editable rather than authoritative.
ontometer model --what metrics # every metric and its formula
ontometer model --what scales # threshold families and cut points
ontometer model --what characteristics # the full rollup map
ontometer model --what unsupported # what no metric covers
Aggregation from metrics to a sub-characteristic score is the one thing OQuaRE never
specifies. This build uses the unweighted mean — the weakest available assumption, declared
in quality_model.yaml rather than hidden in code.
5. As a library
from ontometer import evaluate
result = evaluate("my-ontology.ttl", with_review=False)
result.assessment.metrics["DITOnto"].score # 5
result.assessment.characteristics["structural"] # 4.12
result.assessment.worst_metrics(3) # the three weakest
result.assessment.seeds["ANOnto"] # undocumented classes
result.view.external_namespaces # vocabulary borrowed, not declared
6. The corpus half
OntoMeter is also a research project on ontologies as a population — how they depend on
each other, how attention to them is distributed across domains, how they change. That half
lives in ontometer.corpus and needs a heavier dependency set, so it sits behind an extra:
pip install "ontometer[corpus]"
It fetches from ten registries (six OntoPortal Alliance instances, OBO Foundry, Ontohub,
OKG, LOV), parses each file for its structural and metadata footprint, deduplicates across
sources and versions, and assembles a dependency network distinguishing declared reuse
(owl:imports) from actual reuse — terms used from a namespace that was never
imported. The gap between those two is a measurement in its own right.
ontometer/corpus/README.md— reproducing the pipeline from a clean checkoutontometer/corpus/pipeline/README.md— fetch → parse → dedup, phase by phaseontometer/corpus/network/README.md— the node and edge model, precisely
export ONTOLOGY_DIR="$HOME/ontometer-data" # raw files and structural sidecars
uv run python run/fetch_ontologies.py --sources lov --ontoportal BioPortal
uv run python run/process.py --phases seed,parse,dedup
uv run python -m ontometer.corpus.network.construction
Corpus artifacts live in data/ (override with ONTOMETER_DATA_DIR) and are tracked in
git, because they cannot be re-fetched into existence: registries move underneath you, and
a crawl run today produces a different corpus rather than this one. They are kept compact
deliberately — the registry metadata is projected to a field allowlist before storage, which
is what keeps the database around 34 MiB rather than the 98 MiB it reached when the verbatim
API responses were retained. What is not tracked is the raw object store of ontology files
itself. See ontometer/corpus/README.md for what reproducing the corpus does and does not
mean.
7. Prior art
OntoMeter is not the first tool to compute OQuaRE. OntoInsight pairs the metrics with
LLM recommendations over a Java OWL API engine; oquare-metrics wraps the same engine
as a CI action; NEOntometrics computes OQuaRE and OntoQA at scale with version tracking.
What is different here: pure Python, so there is no JVM to install; external-namespace coupling, which OQuaRE does not model at all; and the population dimension — every tool in that list is strictly single-ontology, so none of them can tell you whether a score is unusual.
8. Development
uv sync --extra dev # add --extra corpus for the research half
uv run pytest test
uv run ruff check && uv run ruff format
uv run pre-commit install
CI on every pull request and every push to main runs pre-commit (ruff and
ty) and pytest with the dev and corpus extras. Publishing to PyPI is a
GitHub Release whose tag matches version in pyproject.toml (for example
v0.1.0 for the current version).
License
Release files for ontometer 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ontometer-0.1.1.tar.gz | 135.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ontometer-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 279.6 kB
Release files / ontometer-0.1.1.tar.gz
| Download URL | ontometer-0.1.1.tar.gz |
|---|---|
| Size | 135.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
63d6aa1963ffdcc86c07a364811e2a8927bfcad9474bb80669c1d08c3526a9ad
|
|
BLAKE2b-256 checksum How to use checksums |
8a33262b0897e133a4158c8ef4d555a184017a033fee7efdebcdae89142be608
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / ontometer-0.1.1-py3-none-any.whl
| Download URL | ontometer-0.1.1-py3-none-any.whl |
|---|---|
| Size | 144.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cad0dbc067d34ecb4afae0ef0f50e0bb545e679e5eb2f490957789d98ccddac2
|
|
BLAKE2b-256 checksum How to use checksums |
74dc36034810beee8db8dcbb1fc0d8b02626f6ec5184cf2daba2c3bf684a5cf8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|