Metadata-Version: 2.5
Name: ontometer
Version: 0.1.0
Summary: Ontology quality assessment
Project-URL: Changelog, https://github.com/growgraph/OntoMeter/blob/main/CHANGELOG.md
Project-URL: Homepage, https://github.com/growgraph/OntoMeter
Project-URL: Issues, https://github.com/growgraph/OntoMeter/issues
Project-URL: Repository, https://github.com/growgraph/OntoMeter
Author-email: Alexander Belikov <alexander@growgraph.dev>, David Bareham <d.bareham@liverpool.ac.uk>
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ontology,owl,quality,rdf,semantic-web
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Requires-Dist: click<9,>=8.1
Requires-Dist: langchain-core<2,>=0.3
Requires-Dist: langchain-ollama<2,>=0.2
Requires-Dist: langchain-openai<2,>=0.2
Requires-Dist: owlready2<1,>=0.47
Requires-Dist: pydantic-settings<3,>=2.5
Requires-Dist: pydantic<3,>=2.9
Requires-Dist: pyyaml<7,>=6.0
Requires-Dist: rdflib<9,>=7.0
Provides-Extra: corpus
Requires-Dist: beautifulsoup4<5,>=4.12; extra == 'corpus'
Requires-Dist: networkx<4,>=3.2; extra == 'corpus'
Requires-Dist: pandas<3,>=2.2; extra == 'corpus'
Requires-Dist: pyarrow>=17.0; extra == 'corpus'
Requires-Dist: requests<3,>=2.32; extra == 'corpus'
Provides-Extra: dev
Requires-Dist: pre-commit>=4.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.14; extra == 'dev'
Requires-Dist: ty>=0.0.14; extra == 'dev'
Provides-Extra: research
Requires-Dist: beautifulsoup4<5,>=4.12; extra == 'research'
Requires-Dist: ipykernel>=6.29; extra == 'research'
Requires-Dist: matplotlib<4,>=3.9; extra == 'research'
Requires-Dist: nbclient>=0.10; extra == 'research'
Requires-Dist: nbformat>=5.10; extra == 'research'
Requires-Dist: networkx<4,>=3.2; extra == 'research'
Requires-Dist: pandas<3,>=2.2; extra == 'research'
Requires-Dist: powerlaw>=1.5; extra == 'research'
Requires-Dist: pyarrow>=17.0; extra == 'research'
Requires-Dist: requests<3,>=2.32; extra == 'research'
Requires-Dist: scikit-learn<2,>=1.9; extra == 'research'
Requires-Dist: seaborn<1,>=0.13; extra == 'research'
Description-Content-Type: text/markdown

# 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.

```bash
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**:

```bash
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 declared
  `rdfs:subPropertyOf` a *datatype*; two `rdfs:domain` triples 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.

```bash
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

```bash
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.

```bash
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

```python
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:

```bash
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 checkout
- `ontometer/corpus/pipeline/README.md` — fetch → parse → dedup, phase by phase
- `ontometer/corpus/network/README.md` — the node and edge model, precisely

```bash
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][oi] pairs the metrics with
LLM recommendations over a Java OWL API engine; [`oquare-metrics`][om] 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*.

[oi]: https://github.com/kracr/onto-insight
[om]: https://github.com/tecnomod-um/oquare-metrics

## 8. Development

```bash
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
```

## License

Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
